github.com/argoproj/argo-cd/v3@v3.2.1/test/e2e/project_management_test.go (about)

     1  package e2e
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strconv"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  	"k8s.io/apimachinery/pkg/api/errors"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/fields"
    17  	"k8s.io/utils/ptr"
    18  
    19  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    20  	"github.com/argoproj/argo-cd/v3/test/e2e/fixture"
    21  	"github.com/argoproj/argo-cd/v3/util/argo"
    22  )
    23  
    24  func assertProjHasEvent(t *testing.T, a *v1alpha1.AppProject, message string, reason string) {
    25  	t.Helper()
    26  	list, err := fixture.KubeClientset.CoreV1().Events(fixture.TestNamespace()).List(t.Context(), metav1.ListOptions{
    27  		FieldSelector: fields.SelectorFromSet(map[string]string{
    28  			"involvedObject.name":      a.Name,
    29  			"involvedObject.uid":       string(a.UID),
    30  			"involvedObject.namespace": fixture.TestNamespace(),
    31  		}).String(),
    32  	})
    33  	require.NoError(t, err)
    34  
    35  	for i := range list.Items {
    36  		event := list.Items[i]
    37  		if event.Reason == reason && strings.Contains(event.Message, message) {
    38  			return
    39  		}
    40  	}
    41  	t.Errorf("Unable to find event with reason=%s; message=%s", reason, message)
    42  }
    43  
    44  func TestProjectCreation(t *testing.T) {
    45  	fixture.EnsureCleanState(t)
    46  
    47  	projectName := "proj-" + fixture.Name()
    48  	_, err := fixture.RunCli("proj", "create", projectName,
    49  		"--description", "Test description",
    50  		"-d", "https://192.168.99.100:8443,default",
    51  		"-d", "https://192.168.99.100:8443,service",
    52  		"-s", "https://github.com/argoproj/argo-cd.git",
    53  		"--orphaned-resources")
    54  	require.NoError(t, err)
    55  
    56  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
    57  	require.NoError(t, err)
    58  	assert.Equal(t, projectName, proj.Name)
    59  	assert.Len(t, proj.Spec.Destinations, 2)
    60  
    61  	assert.Equal(t, "https://192.168.99.100:8443", proj.Spec.Destinations[0].Server)
    62  	assert.Equal(t, "default", proj.Spec.Destinations[0].Namespace)
    63  
    64  	assert.Equal(t, "https://192.168.99.100:8443", proj.Spec.Destinations[1].Server)
    65  	assert.Equal(t, "service", proj.Spec.Destinations[1].Namespace)
    66  
    67  	assert.Len(t, proj.Spec.SourceRepos, 1)
    68  	assert.Equal(t, "https://github.com/argoproj/argo-cd.git", proj.Spec.SourceRepos[0])
    69  
    70  	assert.NotNil(t, proj.Spec.OrphanedResources)
    71  	assert.False(t, proj.Spec.OrphanedResources.IsWarn())
    72  
    73  	assertProjHasEvent(t, proj, "create", argo.EventReasonResourceCreated)
    74  
    75  	// create a manifest with the same name to upsert
    76  	newDescription := "Upserted description"
    77  	proj.Spec.Description = newDescription
    78  	proj.ResourceVersion = ""
    79  	data, err := json.Marshal(proj)
    80  	stdinString := string(data)
    81  	require.NoError(t, err)
    82  
    83  	// fail without upsert flag
    84  	_, err = fixture.RunCliWithStdin(stdinString, false, "proj", "create",
    85  		"-f", "-")
    86  	require.Error(t, err)
    87  
    88  	// succeed with the upsert flag
    89  	_, err = fixture.RunCliWithStdin(stdinString, false, "proj", "create",
    90  		"-f", "-", "--upsert")
    91  	require.NoError(t, err)
    92  	proj, err = fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
    93  	require.NoError(t, err)
    94  	assert.Equal(t, newDescription, proj.Spec.Description)
    95  }
    96  
    97  func TestProjectDeletion(t *testing.T) {
    98  	fixture.EnsureCleanState(t)
    99  
   100  	projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
   101  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Create(
   102  		t.Context(), &v1alpha1.AppProject{ObjectMeta: metav1.ObjectMeta{Name: projectName}}, metav1.CreateOptions{})
   103  	require.NoError(t, err)
   104  
   105  	_, err = fixture.RunCli("proj", "delete", projectName)
   106  	require.NoError(t, err)
   107  
   108  	_, err = fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   109  	assert.True(t, errors.IsNotFound(err))
   110  	assertProjHasEvent(t, proj, "delete", argo.EventReasonResourceDeleted)
   111  }
   112  
   113  func TestSetProject(t *testing.T) {
   114  	fixture.EnsureCleanState(t)
   115  
   116  	projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
   117  	_, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Create(
   118  		t.Context(), &v1alpha1.AppProject{ObjectMeta: metav1.ObjectMeta{Name: projectName}}, metav1.CreateOptions{})
   119  	require.NoError(t, err)
   120  
   121  	_, err = fixture.RunCli("proj", "set", projectName,
   122  		"--description", "updated description",
   123  		"-d", "https://192.168.99.100:8443,default",
   124  		"-d", "https://192.168.99.100:8443,service",
   125  		"--orphaned-resources-warn=false")
   126  	require.NoError(t, err)
   127  
   128  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   129  	require.NoError(t, err)
   130  	assert.Equal(t, projectName, proj.Name)
   131  	assert.Len(t, proj.Spec.Destinations, 2)
   132  
   133  	assert.Equal(t, "https://192.168.99.100:8443", proj.Spec.Destinations[0].Server)
   134  	assert.Equal(t, "default", proj.Spec.Destinations[0].Namespace)
   135  
   136  	assert.Equal(t, "https://192.168.99.100:8443", proj.Spec.Destinations[1].Server)
   137  	assert.Equal(t, "service", proj.Spec.Destinations[1].Namespace)
   138  
   139  	assert.NotNil(t, proj.Spec.OrphanedResources)
   140  	assert.False(t, proj.Spec.OrphanedResources.IsWarn())
   141  
   142  	assertProjHasEvent(t, proj, "update", argo.EventReasonResourceUpdated)
   143  }
   144  
   145  func TestAddProjectDestination(t *testing.T) {
   146  	fixture.EnsureCleanState(t)
   147  
   148  	projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
   149  	_, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Create(
   150  		t.Context(), &v1alpha1.AppProject{ObjectMeta: metav1.ObjectMeta{Name: projectName}}, metav1.CreateOptions{})
   151  	require.NoError(t, err, "Unable to create project")
   152  
   153  	_, err = fixture.RunCli("proj", "add-destination", projectName,
   154  		"https://192.168.99.100:8443",
   155  		"test1",
   156  	)
   157  	require.NoError(t, err, "Unable to add project destination")
   158  
   159  	_, err = fixture.RunCli("proj", "add-destination", projectName,
   160  		"https://192.168.99.100:8443",
   161  		"test1",
   162  	)
   163  	require.ErrorContains(t, err, "already defined")
   164  
   165  	_, err = fixture.RunCli("proj", "add-destination", projectName,
   166  		"!*",
   167  		"test1",
   168  	)
   169  	require.ErrorContains(t, err, "server has an invalid format, '!*'")
   170  
   171  	_, err = fixture.RunCli("proj", "add-destination", projectName,
   172  		"https://192.168.99.100:8443",
   173  		"!*",
   174  	)
   175  	require.ErrorContains(t, err, "namespace has an invalid format, '!*'")
   176  
   177  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   178  	require.NoError(t, err)
   179  	assert.Equal(t, projectName, proj.Name)
   180  	assert.Len(t, proj.Spec.Destinations, 1)
   181  
   182  	assert.Equal(t, "https://192.168.99.100:8443", proj.Spec.Destinations[0].Server)
   183  	assert.Equal(t, "test1", proj.Spec.Destinations[0].Namespace)
   184  	assertProjHasEvent(t, proj, "update", argo.EventReasonResourceUpdated)
   185  }
   186  
   187  func TestAddProjectDestinationWithName(t *testing.T) {
   188  	fixture.EnsureCleanState(t)
   189  
   190  	projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
   191  	_, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Create(
   192  		t.Context(), &v1alpha1.AppProject{ObjectMeta: metav1.ObjectMeta{Name: projectName}}, metav1.CreateOptions{})
   193  	require.NoError(t, err, "Unable to create project")
   194  
   195  	_, err = fixture.RunCli("proj", "add-destination", projectName,
   196  		"in-cluster",
   197  		"test1",
   198  		"--name",
   199  	)
   200  	require.NoError(t, err, "Unable to add project destination")
   201  
   202  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   203  	require.NoError(t, err)
   204  	assert.Equal(t, projectName, proj.Name)
   205  	assert.Len(t, proj.Spec.Destinations, 1)
   206  
   207  	assert.Empty(t, proj.Spec.Destinations[0].Server)
   208  	assert.Equal(t, "in-cluster", proj.Spec.Destinations[0].Name)
   209  	assert.Equal(t, "test1", proj.Spec.Destinations[0].Namespace)
   210  	assertProjHasEvent(t, proj, "update", argo.EventReasonResourceUpdated)
   211  }
   212  
   213  func TestRemoveProjectDestination(t *testing.T) {
   214  	fixture.EnsureCleanState(t)
   215  
   216  	projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
   217  	_, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Create(t.Context(), &v1alpha1.AppProject{
   218  		ObjectMeta: metav1.ObjectMeta{Name: projectName},
   219  		Spec: v1alpha1.AppProjectSpec{
   220  			Destinations: []v1alpha1.ApplicationDestination{{
   221  				Server:    "https://192.168.99.100:8443",
   222  				Namespace: "test",
   223  			}},
   224  		},
   225  	}, metav1.CreateOptions{})
   226  	require.NoError(t, err, "Unable to create project")
   227  
   228  	_, err = fixture.RunCli("proj", "remove-destination", projectName,
   229  		"https://192.168.99.100:8443",
   230  		"test",
   231  	)
   232  	require.NoError(t, err, "Unable to remove project destination")
   233  
   234  	_, err = fixture.RunCli("proj", "remove-destination", projectName,
   235  		"https://192.168.99.100:8443",
   236  		"test1",
   237  	)
   238  	require.ErrorContains(t, err, "does not exist")
   239  
   240  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   241  	require.NoError(t, err, "Unable to get project")
   242  	assert.Equal(t, projectName, proj.Name)
   243  	assert.Empty(t, proj.Spec.Destinations)
   244  	assertProjHasEvent(t, proj, "update", argo.EventReasonResourceUpdated)
   245  }
   246  
   247  func TestAddProjectSource(t *testing.T) {
   248  	fixture.EnsureCleanState(t)
   249  
   250  	projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
   251  	_, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Create(
   252  		t.Context(), &v1alpha1.AppProject{ObjectMeta: metav1.ObjectMeta{Name: projectName}}, metav1.CreateOptions{})
   253  	require.NoError(t, err, "Unable to create project")
   254  
   255  	_, err = fixture.RunCli("proj", "add-source", projectName, "https://github.com/argoproj/argo-cd.git")
   256  	require.NoError(t, err, "Unable to add project source")
   257  
   258  	_, err = fixture.RunCli("proj", "add-source", projectName, "https://github.com/argoproj/argo-cd.git")
   259  	require.NoError(t, err)
   260  
   261  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   262  	require.NoError(t, err)
   263  	assert.Equal(t, projectName, proj.Name)
   264  	assert.Len(t, proj.Spec.SourceRepos, 1)
   265  
   266  	assert.Equal(t, "https://github.com/argoproj/argo-cd.git", proj.Spec.SourceRepos[0])
   267  }
   268  
   269  func TestRemoveProjectSource(t *testing.T) {
   270  	fixture.EnsureCleanState(t)
   271  
   272  	projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
   273  	_, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Create(t.Context(), &v1alpha1.AppProject{
   274  		ObjectMeta: metav1.ObjectMeta{Name: projectName},
   275  		Spec: v1alpha1.AppProjectSpec{
   276  			SourceRepos: []string{"https://github.com/argoproj/argo-cd.git"},
   277  		},
   278  	}, metav1.CreateOptions{})
   279  
   280  	require.NoError(t, err)
   281  
   282  	_, err = fixture.RunCli("proj", "remove-source", projectName, "https://github.com/argoproj/argo-cd.git")
   283  
   284  	require.NoError(t, err)
   285  
   286  	_, err = fixture.RunCli("proj", "remove-source", projectName, "https://github.com/argoproj/argo-cd.git")
   287  	require.NoError(t, err)
   288  
   289  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   290  	require.NoError(t, err)
   291  	assert.Equal(t, projectName, proj.Name)
   292  	assert.Empty(t, proj.Spec.SourceRepos)
   293  	assertProjHasEvent(t, proj, "update", argo.EventReasonResourceUpdated)
   294  }
   295  
   296  func TestUseJWTToken(t *testing.T) {
   297  	fixture.EnsureCleanState(t)
   298  
   299  	projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
   300  	appName := "app-" + strconv.FormatInt(time.Now().Unix(), 10)
   301  	roleName := "roleTest"
   302  	roleName2 := "roleTest2"
   303  	testApp := &v1alpha1.Application{
   304  		ObjectMeta: metav1.ObjectMeta{
   305  			Name: appName,
   306  		},
   307  		Spec: v1alpha1.ApplicationSpec{
   308  			Source: &v1alpha1.ApplicationSource{
   309  				RepoURL: fixture.RepoURL(fixture.RepoURLTypeFile),
   310  				Path:    "guestbook",
   311  			},
   312  			Destination: v1alpha1.ApplicationDestination{
   313  				Server:    v1alpha1.KubernetesInternalAPIServerAddr,
   314  				Namespace: fixture.TestNamespace(),
   315  			},
   316  			Project: projectName,
   317  		},
   318  	}
   319  	_, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Create(t.Context(), &v1alpha1.AppProject{
   320  		ObjectMeta: metav1.ObjectMeta{Name: projectName},
   321  		Spec: v1alpha1.AppProjectSpec{
   322  			Destinations: []v1alpha1.ApplicationDestination{{
   323  				Server:    v1alpha1.KubernetesInternalAPIServerAddr,
   324  				Namespace: fixture.TestNamespace(),
   325  			}},
   326  			SourceRepos: []string{"*"},
   327  		},
   328  	}, metav1.CreateOptions{})
   329  	require.NoError(t, err)
   330  
   331  	_, err = fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.TestNamespace()).Create(t.Context(), testApp, metav1.CreateOptions{})
   332  	require.NoError(t, err)
   333  
   334  	_, err = fixture.RunCli("proj", "role", "create", projectName, roleName)
   335  	require.NoError(t, err)
   336  
   337  	roleGetResult, err := fixture.RunCli("proj", "role", "get", projectName, roleName)
   338  	require.NoError(t, err)
   339  	assert.True(t, strings.HasSuffix(roleGetResult, "ID  ISSUED-AT  EXPIRES-AT"))
   340  
   341  	_, err = fixture.RunCli("proj", "role", "create-token", projectName, roleName)
   342  	require.NoError(t, err)
   343  
   344  	// Create second role with kubectl, to test that it will not affect 1st role
   345  	_, err = fixture.Run("", "kubectl", "patch", "appproject", projectName, "--type", "merge",
   346  		"-n", fixture.TestNamespace(),
   347  		"-p", fmt.Sprintf(`{"spec":{"roles":[{"name":%q},{"name":%q}]}}`, roleName, roleName2))
   348  	require.NoError(t, err)
   349  
   350  	_, err = fixture.RunCli("proj", "role", "create-token", projectName, roleName2)
   351  	require.NoError(t, err)
   352  
   353  	for _, action := range []string{"get", "update", "sync", "create", "override", "*"} {
   354  		_, err = fixture.RunCli("proj", "role", "add-policy", projectName, roleName, "-a", action, "-o", "*", "-p", "allow")
   355  		require.NoError(t, err)
   356  	}
   357  
   358  	newProj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   359  	require.NoError(t, err)
   360  	assert.Len(t, newProj.Status.JWTTokensByRole[roleName].Items, 1)
   361  	assert.ElementsMatch(t, newProj.Status.JWTTokensByRole[roleName].Items, newProj.Spec.Roles[0].JWTTokens)
   362  
   363  	roleGetResult, err = fixture.RunCli("proj", "role", "get", projectName, roleName)
   364  	require.NoError(t, err)
   365  	assert.Contains(t, roleGetResult, strconv.FormatInt(newProj.Status.JWTTokensByRole[roleName].Items[0].IssuedAt, 10))
   366  
   367  	_, err = fixture.RunCli("proj", "role", "delete-token", projectName, roleName, strconv.FormatInt(newProj.Status.JWTTokensByRole[roleName].Items[0].IssuedAt, 10))
   368  	require.NoError(t, err)
   369  	newProj, err = fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   370  	require.NoError(t, err)
   371  	assert.Nil(t, newProj.Status.JWTTokensByRole[roleName].Items)
   372  	assert.Nil(t, newProj.Spec.Roles[0].JWTTokens)
   373  }
   374  
   375  func TestAddOrphanedIgnore(t *testing.T) {
   376  	fixture.EnsureCleanState(t)
   377  
   378  	projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
   379  	_, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Create(
   380  		t.Context(), &v1alpha1.AppProject{ObjectMeta: metav1.ObjectMeta{Name: projectName}}, metav1.CreateOptions{})
   381  	require.NoError(t, err, "Unable to create project")
   382  
   383  	_, err = fixture.RunCli("proj", "add-orphaned-ignore", projectName,
   384  		"group",
   385  		"kind",
   386  		"--name",
   387  		"name",
   388  	)
   389  	require.NoError(t, err, "Unable to add resource to orphaned ignore")
   390  
   391  	_, err = fixture.RunCli("proj", "add-orphaned-ignore", projectName,
   392  		"group",
   393  		"kind",
   394  		"--name",
   395  		"name",
   396  	)
   397  	require.ErrorContains(t, err, "already defined")
   398  
   399  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   400  	require.NoError(t, err)
   401  	assert.Equal(t, projectName, proj.Name)
   402  	assert.Len(t, proj.Spec.OrphanedResources.Ignore, 1)
   403  
   404  	assert.Equal(t, "group", proj.Spec.OrphanedResources.Ignore[0].Group)
   405  	assert.Equal(t, "kind", proj.Spec.OrphanedResources.Ignore[0].Kind)
   406  	assert.Equal(t, "name", proj.Spec.OrphanedResources.Ignore[0].Name)
   407  	assertProjHasEvent(t, proj, "update", argo.EventReasonResourceUpdated)
   408  }
   409  
   410  func TestRemoveOrphanedIgnore(t *testing.T) {
   411  	fixture.EnsureCleanState(t)
   412  
   413  	projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
   414  	_, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Create(t.Context(), &v1alpha1.AppProject{
   415  		ObjectMeta: metav1.ObjectMeta{Name: projectName},
   416  		Spec: v1alpha1.AppProjectSpec{
   417  			OrphanedResources: &v1alpha1.OrphanedResourcesMonitorSettings{
   418  				Warn:   ptr.To(true),
   419  				Ignore: []v1alpha1.OrphanedResourceKey{{Group: "group", Kind: "kind", Name: "name"}},
   420  			},
   421  		},
   422  	}, metav1.CreateOptions{})
   423  	require.NoError(t, err, "Unable to create project")
   424  
   425  	_, err = fixture.RunCli("proj", "remove-orphaned-ignore", projectName,
   426  		"group",
   427  		"kind",
   428  		"--name",
   429  		"name",
   430  	)
   431  	require.NoError(t, err, "Unable to remove resource from orphaned ignore list")
   432  
   433  	_, err = fixture.RunCli("proj", "remove-orphaned-ignore", projectName,
   434  		"group",
   435  		"kind",
   436  		"--name",
   437  		"name",
   438  	)
   439  	require.ErrorContains(t, err, "does not exist")
   440  
   441  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   442  	require.NoError(t, err, "Unable to get project")
   443  	assert.Equal(t, projectName, proj.Name)
   444  	assert.Empty(t, proj.Spec.OrphanedResources.Ignore)
   445  	assertProjHasEvent(t, proj, "update", argo.EventReasonResourceUpdated)
   446  }
   447  
   448  func createAndConfigGlobalProject() error {
   449  	// Create global project
   450  	projectGlobalName := "proj-g-" + fixture.Name()
   451  	_, err := fixture.RunCli("proj", "create", projectGlobalName,
   452  		"--description", "Test description",
   453  		"-d", "https://192.168.99.100:8443,default",
   454  		"-d", "https://192.168.99.100:8443,service",
   455  		"-s", "https://github.com/argoproj/argo-cd.git",
   456  		"--orphaned-resources")
   457  	if err != nil {
   458  		return err
   459  	}
   460  
   461  	projGlobal, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(context.Background(), projectGlobalName, metav1.GetOptions{})
   462  	if err != nil {
   463  		return err
   464  	}
   465  
   466  	projGlobal.Spec.NamespaceResourceBlacklist = []metav1.GroupKind{
   467  		{Group: "", Kind: "Service"},
   468  	}
   469  
   470  	projGlobal.Spec.NamespaceResourceWhitelist = []metav1.GroupKind{
   471  		{Group: "", Kind: "Deployment"},
   472  	}
   473  
   474  	projGlobal.Spec.ClusterResourceWhitelist = []metav1.GroupKind{
   475  		{Group: "", Kind: "Job"},
   476  	}
   477  
   478  	projGlobal.Spec.ClusterResourceBlacklist = []metav1.GroupKind{
   479  		{Group: "", Kind: "Pod"},
   480  	}
   481  
   482  	projGlobal.Spec.SyncWindows = v1alpha1.SyncWindows{}
   483  	win := &v1alpha1.SyncWindow{Kind: "deny", Schedule: "* * * * *", Duration: "1h", Applications: []string{"*"}}
   484  	projGlobal.Spec.SyncWindows = append(projGlobal.Spec.SyncWindows, win)
   485  
   486  	_, err = fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Update(context.Background(), projGlobal, metav1.UpdateOptions{})
   487  	if err != nil {
   488  		return err
   489  	}
   490  
   491  	// Configure global project settings
   492  	globalProjectsSettings := `data:
   493    accounts.config-service: apiKey
   494    globalProjects: |
   495      - labelSelector:
   496          matchExpressions:
   497            - key: opt
   498              operator: In
   499              values:
   500                - me
   501                - you
   502        projectName: %s`
   503  
   504  	_, err = fixture.Run("", "kubectl", "patch", "cm", "argocd-cm",
   505  		"-n", fixture.TestNamespace(),
   506  		"-p", fmt.Sprintf(globalProjectsSettings, projGlobal.Name))
   507  	if err != nil {
   508  		return err
   509  	}
   510  
   511  	return nil
   512  }
   513  
   514  func TestGetVirtualProjectNoMatch(t *testing.T) {
   515  	fixture.EnsureCleanState(t)
   516  	err := createAndConfigGlobalProject()
   517  	require.NoError(t, err)
   518  
   519  	// Create project which does not match global project settings
   520  	projectName := "proj-" + fixture.Name()
   521  	_, err = fixture.RunCli("proj", "create", projectName,
   522  		"--description", "Test description",
   523  		"-d", v1alpha1.KubernetesInternalAPIServerAddr+",*",
   524  		"-s", "*",
   525  		"--orphaned-resources")
   526  	require.NoError(t, err)
   527  
   528  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   529  	require.NoError(t, err)
   530  
   531  	// Create an app belongs to proj project
   532  	_, err = fixture.RunCli("app", "create", fixture.Name(), "--repo", fixture.RepoURL(fixture.RepoURLTypeFile),
   533  		"--path", guestbookPath, "--project", proj.Name, "--dest-server", v1alpha1.KubernetesInternalAPIServerAddr, "--dest-namespace", fixture.DeploymentNamespace())
   534  	require.NoError(t, err)
   535  
   536  	// App trying to sync a resource which is not blacked listed anywhere
   537  	_, err = fixture.RunCli("app", "sync", fixture.Name(), "--resource", "apps:Deployment:guestbook-ui", "--timeout", strconv.Itoa(10))
   538  	require.NoError(t, err)
   539  
   540  	// app trying to sync a resource which is black listed by global project
   541  	_, err = fixture.RunCli("app", "sync", fixture.Name(), "--resource", ":Service:guestbook-ui", "--timeout", strconv.Itoa(10))
   542  	require.NoError(t, err)
   543  }
   544  
   545  func TestGetVirtualProjectMatch(t *testing.T) {
   546  	fixture.EnsureCleanState(t)
   547  	err := createAndConfigGlobalProject()
   548  	require.NoError(t, err)
   549  
   550  	// Create project which matches global project settings
   551  	projectName := "proj-" + fixture.Name()
   552  	_, err = fixture.RunCli("proj", "create", projectName,
   553  		"--description", "Test description",
   554  		"-d", v1alpha1.KubernetesInternalAPIServerAddr+",*",
   555  		"-s", "*",
   556  		"--orphaned-resources")
   557  	require.NoError(t, err)
   558  
   559  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   560  	require.NoError(t, err)
   561  
   562  	// Add a label to this project so that this project match global project selector
   563  	proj.Labels = map[string]string{"opt": "me"}
   564  	_, err = fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Update(t.Context(), proj, metav1.UpdateOptions{})
   565  	require.NoError(t, err)
   566  
   567  	// Create an app belongs to proj project
   568  	_, err = fixture.RunCli("app", "create", fixture.Name(), "--repo", fixture.RepoURL(fixture.RepoURLTypeFile),
   569  		"--path", guestbookPath, "--project", proj.Name, "--dest-server", v1alpha1.KubernetesInternalAPIServerAddr, "--dest-namespace", fixture.DeploymentNamespace())
   570  	require.NoError(t, err)
   571  
   572  	// App trying to sync a resource which is not blacked listed anywhere
   573  	_, err = fixture.RunCli("app", "sync", fixture.Name(), "--resource", "apps:Deployment:guestbook-ui", "--timeout", strconv.Itoa(10))
   574  	require.ErrorContains(t, err, "blocked by sync window")
   575  
   576  	// app trying to sync a resource which is black listed by global project
   577  	_, err = fixture.RunCli("app", "sync", fixture.Name(), "--resource", ":Service:guestbook-ui", "--timeout", strconv.Itoa(10))
   578  	assert.ErrorContains(t, err, "blocked by sync window")
   579  }
   580  
   581  func TestAddProjectDestinationServiceAccount(t *testing.T) {
   582  	fixture.EnsureCleanState(t)
   583  
   584  	projectName := "proj-" + strconv.FormatInt(time.Now().Unix(), 10)
   585  	_, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Create(
   586  		t.Context(), &v1alpha1.AppProject{ObjectMeta: metav1.ObjectMeta{Name: projectName}}, metav1.CreateOptions{})
   587  	require.NoError(t, err, "Unable to create project")
   588  
   589  	// Given, an existing project
   590  	// When, a default destination service account with all valid fields is added to it,
   591  	// Then, there is no error.
   592  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   593  		"https://192.168.99.100:8443",
   594  		"test-ns",
   595  		"test-sa",
   596  	)
   597  	require.NoError(t, err, "Unable to add project destination service account")
   598  
   599  	// Given, an existing project
   600  	// When, a default destination service account with empty namespace is added to it,
   601  	// Then, there is no error.
   602  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   603  		"https://192.168.99.100:8443",
   604  		"",
   605  		"test-sa",
   606  	)
   607  	require.NoError(t, err, "Unable to add project destination service account")
   608  
   609  	// Given, an existing project,
   610  	// When, a default destination service account is added with a custom service account namespace,
   611  	// Then, there is no error.
   612  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   613  		"https://192.168.99.100:8443",
   614  		"test-ns1",
   615  		"test-sa",
   616  		"--service-account-namespace",
   617  		"default",
   618  	)
   619  	require.NoError(t, err, "Unable to add project destination service account")
   620  
   621  	// Given, an existing project,
   622  	// When, a duplicate default destination service account is added,
   623  	// Then, there is an error with appropriate message.
   624  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   625  		"https://192.168.99.100:8443",
   626  		"test-ns",
   627  		"test-sa",
   628  	)
   629  	require.ErrorContains(t, err, "already defined")
   630  
   631  	// Given, an existing project,
   632  	// When, a duplicate default destination service account is added,
   633  	// Then, there is an error with appropriate message.
   634  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   635  		"https://192.168.99.100:8443",
   636  		"test-ns",
   637  		"asdf",
   638  	)
   639  	require.ErrorContains(t, err, "already added")
   640  
   641  	// Given, an existing project,
   642  	// When, a default destination service account with negation glob pattern for server is added,
   643  	// Then, there is an error with appropriate message.
   644  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   645  		"!*",
   646  		"test-ns",
   647  		"test-sa",
   648  	)
   649  	require.ErrorContains(t, err, "server has an invalid format, '!*'")
   650  
   651  	// Given, an existing project,
   652  	// When, a default destination service account with negation glob pattern for server is added,
   653  	// Then, there is an error with appropriate message.
   654  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   655  		"!abc",
   656  		"test-ns",
   657  		"test-sa",
   658  	)
   659  	require.ErrorContains(t, err, "server has an invalid format, '!abc'")
   660  
   661  	// Given, an existing project,
   662  	// When, a default destination service account with negation glob pattern for namespace is added,
   663  	// Then, there is an error with appropriate message.
   664  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   665  		"https://192.168.99.100:8443",
   666  		"!*",
   667  		"test-sa",
   668  	)
   669  	require.ErrorContains(t, err, "namespace has an invalid format, '!*'")
   670  
   671  	// Given, an existing project,
   672  	// When, a default destination service account with negation glob pattern for namespace is added,
   673  	// Then, there is an error with appropriate message.
   674  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   675  		"https://192.168.99.100:8443",
   676  		"!abc",
   677  		"test-sa",
   678  	)
   679  	require.ErrorContains(t, err, "namespace has an invalid format, '!abc'")
   680  
   681  	// Given, an existing project,
   682  	// When, a default destination service account with empty service account is added,
   683  	// Then, there is an error with appropriate message.
   684  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   685  		"https://192.168.99.100:8443",
   686  		"test-ns",
   687  		"",
   688  	)
   689  	require.ErrorContains(t, err, "defaultServiceAccount has an invalid format, ''")
   690  
   691  	// Given, an existing project,
   692  	// When, a default destination service account with service account having just white spaces is added,
   693  	// Then, there is an error with appropriate message.
   694  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   695  		"https://192.168.99.100:8443",
   696  		"test-ns",
   697  		"   ",
   698  	)
   699  	require.ErrorContains(t, err, "defaultServiceAccount has an invalid format, '   '")
   700  
   701  	// Given, an existing project,
   702  	// When, a default destination service account with service account having backwards slash char is added,
   703  	// Then, there is an error with appropriate message.
   704  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   705  		"https://192.168.99.100:8443",
   706  		"test-ns",
   707  		"test\\sa",
   708  	)
   709  	require.ErrorContains(t, err, "defaultServiceAccount has an invalid format, 'test\\\\sa'")
   710  
   711  	// Given, an existing project,
   712  	// When, a default destination service account with service account having forward slash char is added,
   713  	// Then, there is an error with appropriate message.
   714  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   715  		"https://192.168.99.100:8443",
   716  		"test-ns",
   717  		"test/sa",
   718  	)
   719  	require.ErrorContains(t, err, "defaultServiceAccount has an invalid format, 'test/sa'")
   720  
   721  	// Given, an existing project,
   722  	// When, a default destination service account with service account having square braces char is added,
   723  	// Then, there is an error with appropriate message.
   724  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   725  		"https://192.168.99.100:8443",
   726  		"test-ns",
   727  		"[test-sa]",
   728  	)
   729  	require.ErrorContains(t, err, "defaultServiceAccount has an invalid format, '[test-sa]'")
   730  
   731  	// Given, an existing project,
   732  	// When, a default destination service account with service account having curly braces char is added,
   733  	// Then, there is an error with appropriate message.
   734  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   735  		"https://192.168.99.100:8443",
   736  		"test-ns",
   737  		"{test-sa}",
   738  	)
   739  	require.ErrorContains(t, err, "defaultServiceAccount has an invalid format, '{test-sa}'")
   740  
   741  	// Given, an existing project,
   742  	// When, a default destination service account with service account having curly braces char is added,
   743  	// Then, there is an error with appropriate message.
   744  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   745  		"[[ech*",
   746  		"test-ns",
   747  		"test-sa",
   748  	)
   749  	require.ErrorContains(t, err, "server has an invalid format, '[[ech*'")
   750  
   751  	// Given, an existing project,
   752  	// When, a default destination service account with service account having curly braces char is added,
   753  	// Then, there is an error with appropriate message.
   754  	_, err = fixture.RunCli("proj", "add-destination-service-account", projectName,
   755  		"https://192.168.99.100:8443",
   756  		"[[ech*",
   757  		"test-sa",
   758  	)
   759  	require.ErrorContains(t, err, "namespace has an invalid format, '[[ech*'")
   760  
   761  	proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(t.Context(), projectName, metav1.GetOptions{})
   762  	require.NoError(t, err)
   763  	assert.Equal(t, projectName, proj.Name)
   764  	assert.Len(t, proj.Spec.DestinationServiceAccounts, 3)
   765  
   766  	assert.Equal(t, "https://192.168.99.100:8443", proj.Spec.DestinationServiceAccounts[0].Server)
   767  	assert.Equal(t, "test-ns", proj.Spec.DestinationServiceAccounts[0].Namespace)
   768  	assert.Equal(t, "test-sa", proj.Spec.DestinationServiceAccounts[0].DefaultServiceAccount)
   769  
   770  	assert.Equal(t, "https://192.168.99.100:8443", proj.Spec.DestinationServiceAccounts[1].Server)
   771  	assert.Empty(t, proj.Spec.DestinationServiceAccounts[1].Namespace)
   772  	assert.Equal(t, "test-sa", proj.Spec.DestinationServiceAccounts[1].DefaultServiceAccount)
   773  
   774  	assert.Equal(t, "https://192.168.99.100:8443", proj.Spec.DestinationServiceAccounts[2].Server)
   775  	assert.Equal(t, "test-ns1", proj.Spec.DestinationServiceAccounts[2].Namespace)
   776  	assert.Equal(t, "default:test-sa", proj.Spec.DestinationServiceAccounts[2].DefaultServiceAccount)
   777  
   778  	assertProjHasEvent(t, proj, "update", argo.EventReasonResourceUpdated)
   779  }