github.com/argoproj-labs/argocd-operator@v0.10.0/controllers/argocd/argocd_controller_test.go (about) 1 // Copyright 2020 ArgoCD Operator Developers 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package argocd 16 17 import ( 18 "context" 19 "fmt" 20 "testing" 21 "time" 22 23 corev1 "k8s.io/api/core/v1" 24 "sigs.k8s.io/controller-runtime/pkg/client" 25 26 "github.com/stretchr/testify/assert" 27 appsv1 "k8s.io/api/apps/v1" 28 v1 "k8s.io/api/rbac/v1" 29 apierrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/types" 33 logf "sigs.k8s.io/controller-runtime/pkg/log" 34 "sigs.k8s.io/controller-runtime/pkg/reconcile" 35 36 argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" 37 "github.com/argoproj-labs/argocd-operator/common" 38 "github.com/argoproj-labs/argocd-operator/controllers/argoutil" 39 ) 40 41 var _ reconcile.Reconciler = &ReconcileArgoCD{} 42 43 // When the ArgoCD object has been marked as deleting, we should not reconcile, 44 // and trigger the creation of new objects. 45 // 46 // We have owner references set on created resources, this triggers automatic 47 // deletion of the associated objects. 48 func TestReconcileArgoCD_Reconcile_with_deleted(t *testing.T) { 49 logf.SetLogger(ZapLogger(true)) 50 a := makeTestArgoCD(deletedAt(time.Now())) 51 52 resObjs := []client.Object{a} 53 subresObjs := []client.Object{a} 54 runtimeObjs := []runtime.Object{} 55 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 56 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 57 r := makeTestReconciler(cl, sch) 58 59 assert.NoError(t, createNamespace(r, a.Namespace, "")) 60 61 req := reconcile.Request{ 62 NamespacedName: types.NamespacedName{ 63 Name: a.Name, 64 Namespace: a.Namespace, 65 }, 66 } 67 res, err := r.Reconcile(context.TODO(), req) 68 assert.NoError(t, err) 69 if res.Requeue { 70 t.Fatal("reconcile requeued request") 71 } 72 73 deployment := &appsv1.Deployment{} 74 if !apierrors.IsNotFound(r.Client.Get(context.TODO(), types.NamespacedName{ 75 Name: "argocd-redis", 76 Namespace: testNamespace, 77 }, deployment)) { 78 t.Fatalf("expected not found error, got %#v\n", err) 79 } 80 } 81 82 func TestReconcileArgoCD_Reconcile(t *testing.T) { 83 logf.SetLogger(ZapLogger(true)) 84 a := makeTestArgoCD() 85 86 resObjs := []client.Object{a} 87 subresObjs := []client.Object{a} 88 runtimeObjs := []runtime.Object{} 89 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 90 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 91 r := makeTestReconciler(cl, sch) 92 93 assert.NoError(t, createNamespace(r, a.Namespace, "")) 94 95 req := reconcile.Request{ 96 NamespacedName: types.NamespacedName{ 97 Name: a.Name, 98 Namespace: a.Namespace, 99 }, 100 } 101 102 res, err := r.Reconcile(context.TODO(), req) 103 assert.NoError(t, err) 104 if res.Requeue { 105 t.Fatal("reconcile requeued request") 106 } 107 108 deployment := &appsv1.Deployment{} 109 if err = r.Client.Get(context.TODO(), types.NamespacedName{ 110 Name: "argocd-redis", 111 Namespace: testNamespace, 112 }, deployment); err != nil { 113 t.Fatalf("failed to find the redis deployment: %#v\n", err) 114 } 115 } 116 117 func TestReconcileArgoCD_LabelSelector(t *testing.T) { 118 logf.SetLogger(ZapLogger(true)) 119 //ctx := context.Background() 120 a := makeTestArgoCD(func(ac *argoproj.ArgoCD) { 121 ac.Name = "argo-test-1" 122 ac.Labels = map[string]string{"foo": "bar"} 123 }) 124 b := makeTestArgoCD(func(ac *argoproj.ArgoCD) { 125 ac.Name = "argo-test-2" 126 ac.Labels = map[string]string{"testfoo": "testbar"} 127 }) 128 c := makeTestArgoCD(func(ac *argoproj.ArgoCD) { 129 ac.Name = "argo-test-3" 130 }) 131 132 resObjs := []client.Object{a, b, c} 133 subresObjs := []client.Object{a, b, c} 134 runtimeObjs := []runtime.Object{} 135 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 136 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 137 rt := makeTestReconciler(cl, sch) 138 139 assert.NoError(t, createNamespace(rt, a.Namespace, "")) 140 141 // All ArgoCD instances should be reconciled if no label-selctor is applied to the operator. 142 143 // Instance 'a' 144 req1 := reconcile.Request{ 145 NamespacedName: types.NamespacedName{ 146 Name: a.Name, 147 Namespace: a.Namespace, 148 }, 149 } 150 res1, err := rt.Reconcile(context.TODO(), req1) 151 assert.NoError(t, err) 152 if res1.Requeue { 153 t.Fatal("reconcile requeued request") 154 } 155 156 //Instance 'b' 157 req2 := reconcile.Request{ 158 NamespacedName: types.NamespacedName{ 159 Name: b.Name, 160 Namespace: b.Namespace, 161 }, 162 } 163 res2, err := rt.Reconcile(context.TODO(), req2) 164 assert.NoError(t, err) 165 if res2.Requeue { 166 t.Fatal("reconcile requeued request") 167 } 168 169 //Instance 'c' 170 req3 := reconcile.Request{ 171 NamespacedName: types.NamespacedName{ 172 Name: c.Name, 173 Namespace: c.Namespace, 174 }, 175 } 176 res3, err := rt.Reconcile(context.TODO(), req3) 177 assert.NoError(t, err) 178 if res3.Requeue { 179 t.Fatal("reconcile requeued request") 180 } 181 182 // Apply label-selector foo=bar to the operator. 183 // Only Instance a should reconcile with matching label "foo=bar" 184 // No reconciliation is expected for instance b and c and an error is expected. 185 rt.LabelSelector = "foo=bar" 186 reqTest := reconcile.Request{ 187 NamespacedName: types.NamespacedName{ 188 Name: a.Name, 189 Namespace: a.Namespace, 190 }, 191 } 192 resTest, err := rt.Reconcile(context.TODO(), reqTest) 193 assert.NoError(t, err) 194 if resTest.Requeue { 195 t.Fatal("reconcile requeued request") 196 } 197 198 // Instance 'b' is not reconciled as the label does not match, error expected 199 reqTest2 := reconcile.Request{ 200 NamespacedName: types.NamespacedName{ 201 Name: b.Name, 202 Namespace: b.Namespace, 203 }, 204 } 205 resTest2, err := rt.Reconcile(context.TODO(), reqTest2) 206 assert.Error(t, err) 207 if resTest2.Requeue { 208 t.Fatal("reconcile requeued request") 209 } 210 211 //Instance 'c' is not reconciled as there is no label, error expected 212 reqTest3 := reconcile.Request{ 213 NamespacedName: types.NamespacedName{ 214 Name: c.Name, 215 Namespace: c.Namespace, 216 }, 217 } 218 resTest3, err := rt.Reconcile(context.TODO(), reqTest3) 219 assert.Error(t, err) 220 if resTest3.Requeue { 221 t.Fatal("reconcile requeued request") 222 } 223 } 224 225 func TestReconcileArgoCD_Reconcile_RemoveManagedByLabelOnArgocdDeletion(t *testing.T) { 226 logf.SetLogger(ZapLogger(true)) 227 228 tests := []struct { 229 testName string 230 nsName string 231 isRemoveManagedByLabelOnArgoCDDeletionSet bool 232 }{ 233 { 234 testName: "Without REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION set", 235 nsName: "newNamespaceTest1", 236 isRemoveManagedByLabelOnArgoCDDeletionSet: false, 237 }, 238 { 239 testName: "With REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION set", 240 nsName: "newNamespaceTest2", 241 isRemoveManagedByLabelOnArgoCDDeletionSet: true, 242 }, 243 } 244 245 for _, test := range tests { 246 t.Run(test.testName, func(t *testing.T) { 247 a := makeTestArgoCD(deletedAt(time.Now()), addFinalizer(common.ArgoCDDeletionFinalizer)) 248 249 resObjs := []client.Object{a} 250 subresObjs := []client.Object{a} 251 runtimeObjs := []runtime.Object{} 252 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 253 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 254 r := makeTestReconciler(cl, sch) 255 256 nsArgocd := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ 257 Name: a.Namespace, 258 }} 259 err := r.Client.Create(context.TODO(), nsArgocd) 260 assert.NoError(t, err) 261 262 if test.isRemoveManagedByLabelOnArgoCDDeletionSet { 263 t.Setenv("REMOVE_MANAGED_BY_LABEL_ON_ARGOCD_DELETION", "true") 264 } 265 266 ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ 267 Name: test.nsName, 268 Labels: map[string]string{ 269 common.ArgoCDManagedByLabel: a.Namespace, 270 }}, 271 } 272 err = r.Client.Create(context.TODO(), ns) 273 assert.NoError(t, err) 274 275 req := reconcile.Request{ 276 NamespacedName: types.NamespacedName{ 277 Name: a.Name, 278 Namespace: a.Namespace, 279 }, 280 } 281 282 _, err = r.Reconcile(context.TODO(), req) 283 assert.NoError(t, err) 284 285 assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{Name: ns.Name}, ns)) 286 if test.isRemoveManagedByLabelOnArgoCDDeletionSet { 287 // Check if the managed-by label gets removed from the new namespace 288 if _, ok := ns.Labels[common.ArgoCDManagedByLabel]; ok { 289 t.Errorf("Expected the label[%v] to be removed from the namespace[%v]", common.ArgoCDManagedByLabel, ns.Name) 290 } 291 } else { 292 // Check if the managed-by label still exists in the new namespace 293 assert.Equal(t, ns.Labels[common.ArgoCDManagedByLabel], a.Namespace) 294 } 295 }) 296 } 297 } 298 299 func deletedAt(now time.Time) argoCDOpt { 300 return func(a *argoproj.ArgoCD) { 301 wrapped := metav1.NewTime(now) 302 a.ObjectMeta.DeletionTimestamp = &wrapped 303 a.Finalizers = []string{"test: finalizaer"} 304 } 305 } 306 307 func TestReconcileArgoCD_CleanUp(t *testing.T) { 308 logf.SetLogger(ZapLogger(true)) 309 a := makeTestArgoCD(deletedAt(time.Now()), addFinalizer(common.ArgoCDDeletionFinalizer)) 310 311 resources := []client.Object{a} 312 resources = append(resources, clusterResources(a)...) 313 314 subresObjs := []client.Object{a} 315 runtimeObjs := []runtime.Object{} 316 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 317 cl := makeTestReconcilerClient(sch, resources, subresObjs, runtimeObjs) 318 r := makeTestReconciler(cl, sch) 319 320 assert.NoError(t, createNamespace(r, a.Namespace, "")) 321 322 req := reconcile.Request{ 323 NamespacedName: types.NamespacedName{ 324 Name: a.Name, 325 Namespace: a.Namespace, 326 }, 327 } 328 res, err := r.Reconcile(context.TODO(), req) 329 assert.NoError(t, err) 330 if res.Requeue { 331 t.Fatal("reconcile requeued request") 332 } 333 334 // check if cluster resources are deleted 335 tt := []struct { 336 name string 337 resource client.Object 338 }{ 339 { 340 fmt.Sprintf("ClusterRole %s", common.ArgoCDApplicationControllerComponent), 341 newClusterRole(common.ArgoCDApplicationControllerComponent, []v1.PolicyRule{}, a), 342 }, 343 { 344 fmt.Sprintf("ClusterRole %s", common.ArgoCDServerComponent), 345 newClusterRole(common.ArgoCDServerComponent, []v1.PolicyRule{}, a), 346 }, 347 { 348 fmt.Sprintf("ClusterRoleBinding %s", common.ArgoCDApplicationControllerComponent), 349 newClusterRoleBinding(a), 350 }, 351 { 352 fmt.Sprintf("ClusterRoleBinding %s", common.ArgoCDServerComponent), 353 newClusterRoleBinding(a), 354 }, 355 } 356 357 for _, test := range tt { 358 t.Run(test.name, func(t *testing.T) { 359 if argoutil.IsObjectFound(r.Client, "", test.name, test.resource) { 360 t.Errorf("Expected %s to be deleted", test.name) 361 } 362 }) 363 } 364 365 // check if namespace label was removed 366 ns := &corev1.Namespace{} 367 assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{Name: a.Namespace}, ns)) 368 if _, ok := ns.Labels[common.ArgoCDManagedByLabel]; ok { 369 t.Errorf("Expected the label[%v] to be removed from the namespace[%v]", common.ArgoCDManagedByLabel, a.Namespace) 370 } 371 } 372 373 func addFinalizer(finalizer string) argoCDOpt { 374 return func(a *argoproj.ArgoCD) { 375 a.Finalizers = append(a.Finalizers, finalizer) 376 } 377 } 378 379 func clusterResources(argocd *argoproj.ArgoCD) []client.Object { 380 return []client.Object{ 381 newClusterRole(common.ArgoCDApplicationControllerComponent, []v1.PolicyRule{}, argocd), 382 newClusterRole(common.ArgoCDServerComponent, []v1.PolicyRule{}, argocd), 383 newClusterRoleBindingWithname(common.ArgoCDApplicationControllerComponent, argocd), 384 newClusterRoleBindingWithname(common.ArgoCDServerComponent, argocd), 385 } 386 }