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  }