github.com/argoproj/argo-cd/v2@v2.10.5/test/e2e/app_management_test.go (about)

     1  package e2e
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"regexp"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/argoproj/gitops-engine/pkg/diff"
    12  	"github.com/argoproj/gitops-engine/pkg/health"
    13  	. "github.com/argoproj/gitops-engine/pkg/sync/common"
    14  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    15  	"github.com/argoproj/pkg/errors"
    16  	log "github.com/sirupsen/logrus"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  	v1 "k8s.io/api/core/v1"
    20  	networkingv1 "k8s.io/api/networking/v1"
    21  	rbacv1 "k8s.io/api/rbac/v1"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    24  	"k8s.io/apimachinery/pkg/runtime/schema"
    25  	"k8s.io/apimachinery/pkg/types"
    26  	"k8s.io/apimachinery/pkg/util/intstr"
    27  	"k8s.io/utils/pointer"
    28  
    29  	"github.com/argoproj/argo-cd/v2/common"
    30  	applicationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
    31  	. "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    32  	"github.com/argoproj/argo-cd/v2/test/e2e/fixture"
    33  	. "github.com/argoproj/argo-cd/v2/test/e2e/fixture"
    34  	accountFixture "github.com/argoproj/argo-cd/v2/test/e2e/fixture/account"
    35  	. "github.com/argoproj/argo-cd/v2/test/e2e/fixture/app"
    36  	projectFixture "github.com/argoproj/argo-cd/v2/test/e2e/fixture/project"
    37  	repoFixture "github.com/argoproj/argo-cd/v2/test/e2e/fixture/repos"
    38  	"github.com/argoproj/argo-cd/v2/test/e2e/testdata"
    39  	"github.com/argoproj/argo-cd/v2/util/argo"
    40  	. "github.com/argoproj/argo-cd/v2/util/argo"
    41  	. "github.com/argoproj/argo-cd/v2/util/errors"
    42  	"github.com/argoproj/argo-cd/v2/util/io"
    43  	"github.com/argoproj/argo-cd/v2/util/settings"
    44  
    45  	"github.com/argoproj/argo-cd/v2/pkg/apis/application"
    46  )
    47  
    48  const (
    49  	guestbookPath          = "guestbook"
    50  	guestbookPathLocal     = "./testdata/guestbook_local"
    51  	globalWithNoNameSpace  = "global-with-no-namespace"
    52  	guestbookWithNamespace = "guestbook-with-namespace"
    53  	resourceActions        = "resource-actions"
    54  	appLogsRetryCount      = 5
    55  )
    56  
    57  // This empty test is here only for clarity, to conform to logs rbac tests structure in account. This exact usecase is covered in the TestAppLogs test
    58  func TestGetLogsAllowNoSwitch(t *testing.T) {
    59  }
    60  
    61  func TestGetLogsDenySwitchOn(t *testing.T) {
    62  	SkipOnEnv(t, "OPENSHIFT")
    63  
    64  	accountFixture.Given(t).
    65  		Name("test").
    66  		When().
    67  		Create().
    68  		Login().
    69  		SetPermissions([]fixture.ACL{
    70  			{
    71  				Resource: "applications",
    72  				Action:   "create",
    73  				Scope:    "*",
    74  			},
    75  			{
    76  				Resource: "applications",
    77  				Action:   "get",
    78  				Scope:    "*",
    79  			},
    80  			{
    81  				Resource: "applications",
    82  				Action:   "sync",
    83  				Scope:    "*",
    84  			},
    85  			{
    86  				Resource: "projects",
    87  				Action:   "get",
    88  				Scope:    "*",
    89  			},
    90  		}, "app-creator")
    91  
    92  	GivenWithSameState(t).
    93  		Path("guestbook-logs").
    94  		When().
    95  		CreateApp().
    96  		Sync().
    97  		SetParamInSettingConfigMap("server.rbac.log.enforce.enable", "true").
    98  		Then().
    99  		Expect(HealthIs(health.HealthStatusHealthy)).
   100  		And(func(app *Application) {
   101  			_, err := RunCliWithRetry(appLogsRetryCount, "app", "logs", app.Name, "--kind", "Deployment", "--group", "", "--name", "guestbook-ui")
   102  			assert.Error(t, err)
   103  			assert.Contains(t, err.Error(), "permission denied")
   104  		})
   105  }
   106  
   107  func TestGetLogsAllowSwitchOn(t *testing.T) {
   108  	SkipOnEnv(t, "OPENSHIFT")
   109  
   110  	accountFixture.Given(t).
   111  		Name("test").
   112  		When().
   113  		Create().
   114  		Login().
   115  		SetPermissions([]fixture.ACL{
   116  			{
   117  				Resource: "applications",
   118  				Action:   "create",
   119  				Scope:    "*",
   120  			},
   121  			{
   122  				Resource: "applications",
   123  				Action:   "get",
   124  				Scope:    "*",
   125  			},
   126  			{
   127  				Resource: "applications",
   128  				Action:   "sync",
   129  				Scope:    "*",
   130  			},
   131  			{
   132  				Resource: "projects",
   133  				Action:   "get",
   134  				Scope:    "*",
   135  			},
   136  			{
   137  				Resource: "logs",
   138  				Action:   "get",
   139  				Scope:    "*",
   140  			},
   141  		}, "app-creator")
   142  
   143  	GivenWithSameState(t).
   144  		Path("guestbook-logs").
   145  		When().
   146  		CreateApp().
   147  		Sync().
   148  		SetParamInSettingConfigMap("server.rbac.log.enforce.enable", "true").
   149  		Then().
   150  		Expect(HealthIs(health.HealthStatusHealthy)).
   151  		And(func(app *Application) {
   152  			out, err := RunCliWithRetry(appLogsRetryCount, "app", "logs", app.Name, "--kind", "Deployment", "--group", "", "--name", "guestbook-ui")
   153  			assert.NoError(t, err)
   154  			assert.Contains(t, out, "Hi")
   155  		}).
   156  		And(func(app *Application) {
   157  			out, err := RunCliWithRetry(appLogsRetryCount, "app", "logs", app.Name, "--kind", "Pod")
   158  			assert.NoError(t, err)
   159  			assert.Contains(t, out, "Hi")
   160  		}).
   161  		And(func(app *Application) {
   162  			out, err := RunCliWithRetry(appLogsRetryCount, "app", "logs", app.Name, "--kind", "Service")
   163  			assert.NoError(t, err)
   164  			assert.NotContains(t, out, "Hi")
   165  		})
   166  
   167  }
   168  
   169  func TestGetLogsAllowSwitchOff(t *testing.T) {
   170  	SkipOnEnv(t, "OPENSHIFT")
   171  
   172  	accountFixture.Given(t).
   173  		Name("test").
   174  		When().
   175  		Create().
   176  		Login().
   177  		SetPermissions([]fixture.ACL{
   178  			{
   179  				Resource: "applications",
   180  				Action:   "create",
   181  				Scope:    "*",
   182  			},
   183  			{
   184  				Resource: "applications",
   185  				Action:   "get",
   186  				Scope:    "*",
   187  			},
   188  			{
   189  				Resource: "applications",
   190  				Action:   "sync",
   191  				Scope:    "*",
   192  			},
   193  			{
   194  				Resource: "projects",
   195  				Action:   "get",
   196  				Scope:    "*",
   197  			},
   198  		}, "app-creator")
   199  
   200  	Given(t).
   201  		Path("guestbook-logs").
   202  		When().
   203  		CreateApp().
   204  		Sync().
   205  		SetParamInSettingConfigMap("server.rbac.log.enforce.enable", "false").
   206  		Then().
   207  		Expect(HealthIs(health.HealthStatusHealthy)).
   208  		And(func(app *Application) {
   209  			out, err := RunCliWithRetry(appLogsRetryCount, "app", "logs", app.Name, "--kind", "Deployment", "--group", "", "--name", "guestbook-ui")
   210  			assert.NoError(t, err)
   211  			assert.Contains(t, out, "Hi")
   212  		}).
   213  		And(func(app *Application) {
   214  			out, err := RunCliWithRetry(appLogsRetryCount, "app", "logs", app.Name, "--kind", "Pod")
   215  			assert.NoError(t, err)
   216  			assert.Contains(t, out, "Hi")
   217  		}).
   218  		And(func(app *Application) {
   219  			out, err := RunCliWithRetry(appLogsRetryCount, "app", "logs", app.Name, "--kind", "Service")
   220  			assert.NoError(t, err)
   221  			assert.NotContains(t, out, "Hi")
   222  		})
   223  }
   224  
   225  func TestSyncToUnsignedCommit(t *testing.T) {
   226  	SkipOnEnv(t, "GPG")
   227  	Given(t).
   228  		Project("gpg").
   229  		Path(guestbookPath).
   230  		When().
   231  		IgnoreErrors().
   232  		CreateApp().
   233  		Sync().
   234  		Then().
   235  		Expect(OperationPhaseIs(OperationError)).
   236  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   237  		Expect(HealthIs(health.HealthStatusMissing))
   238  }
   239  
   240  func TestSyncToSignedCommitWithoutKnownKey(t *testing.T) {
   241  	SkipOnEnv(t, "GPG")
   242  	Given(t).
   243  		Project("gpg").
   244  		Path(guestbookPath).
   245  		When().
   246  		AddSignedFile("test.yaml", "null").
   247  		IgnoreErrors().
   248  		CreateApp().
   249  		Sync().
   250  		Then().
   251  		Expect(OperationPhaseIs(OperationError)).
   252  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   253  		Expect(HealthIs(health.HealthStatusMissing))
   254  }
   255  
   256  func TestSyncToSignedCommitWithKnownKey(t *testing.T) {
   257  	SkipOnEnv(t, "GPG")
   258  	Given(t).
   259  		Project("gpg").
   260  		Path(guestbookPath).
   261  		GPGPublicKeyAdded().
   262  		Sleep(2).
   263  		When().
   264  		AddSignedFile("test.yaml", "null").
   265  		IgnoreErrors().
   266  		CreateApp().
   267  		Sync().
   268  		Then().
   269  		Expect(OperationPhaseIs(OperationSucceeded)).
   270  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   271  		Expect(HealthIs(health.HealthStatusHealthy))
   272  }
   273  
   274  func TestSyncToSignedBranchWithKnownKey(t *testing.T) {
   275  	SkipOnEnv(t, "GPG")
   276  	Given(t).
   277  		Project("gpg").
   278  		Path(guestbookPath).
   279  		Revision("master").
   280  		GPGPublicKeyAdded().
   281  		Sleep(2).
   282  		When().
   283  		AddSignedFile("test.yaml", "null").
   284  		IgnoreErrors().
   285  		CreateApp().
   286  		Sync().
   287  		Then().
   288  		Expect(OperationPhaseIs(OperationSucceeded)).
   289  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   290  		Expect(HealthIs(health.HealthStatusHealthy))
   291  }
   292  
   293  func TestSyncToSignedBranchWithUnknownKey(t *testing.T) {
   294  	SkipOnEnv(t, "GPG")
   295  	Given(t).
   296  		Project("gpg").
   297  		Path(guestbookPath).
   298  		Revision("master").
   299  		Sleep(2).
   300  		When().
   301  		AddSignedFile("test.yaml", "null").
   302  		IgnoreErrors().
   303  		CreateApp().
   304  		Sync().
   305  		Then().
   306  		Expect(OperationPhaseIs(OperationError)).
   307  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   308  		Expect(HealthIs(health.HealthStatusMissing))
   309  }
   310  
   311  func TestSyncToUnsignedBranch(t *testing.T) {
   312  	SkipOnEnv(t, "GPG")
   313  	Given(t).
   314  		Project("gpg").
   315  		Revision("master").
   316  		Path(guestbookPath).
   317  		GPGPublicKeyAdded().
   318  		Sleep(2).
   319  		When().
   320  		IgnoreErrors().
   321  		CreateApp().
   322  		Sync().
   323  		Then().
   324  		Expect(OperationPhaseIs(OperationError)).
   325  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   326  		Expect(HealthIs(health.HealthStatusMissing))
   327  }
   328  
   329  func TestSyncToSignedTagWithKnownKey(t *testing.T) {
   330  	SkipOnEnv(t, "GPG")
   331  	Given(t).
   332  		Project("gpg").
   333  		Revision("signed-tag").
   334  		Path(guestbookPath).
   335  		GPGPublicKeyAdded().
   336  		Sleep(2).
   337  		When().
   338  		AddSignedTag("signed-tag").
   339  		IgnoreErrors().
   340  		CreateApp().
   341  		Sync().
   342  		Then().
   343  		Expect(OperationPhaseIs(OperationSucceeded)).
   344  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   345  		Expect(HealthIs(health.HealthStatusHealthy))
   346  }
   347  
   348  func TestSyncToSignedTagWithUnknownKey(t *testing.T) {
   349  	SkipOnEnv(t, "GPG")
   350  	Given(t).
   351  		Project("gpg").
   352  		Revision("signed-tag").
   353  		Path(guestbookPath).
   354  		Sleep(2).
   355  		When().
   356  		AddSignedTag("signed-tag").
   357  		IgnoreErrors().
   358  		CreateApp().
   359  		Sync().
   360  		Then().
   361  		Expect(OperationPhaseIs(OperationError)).
   362  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   363  		Expect(HealthIs(health.HealthStatusMissing))
   364  }
   365  
   366  func TestSyncToUnsignedTag(t *testing.T) {
   367  	SkipOnEnv(t, "GPG")
   368  	Given(t).
   369  		Project("gpg").
   370  		Revision("unsigned-tag").
   371  		Path(guestbookPath).
   372  		GPGPublicKeyAdded().
   373  		Sleep(2).
   374  		When().
   375  		AddTag("unsigned-tag").
   376  		IgnoreErrors().
   377  		CreateApp().
   378  		Sync().
   379  		Then().
   380  		Expect(OperationPhaseIs(OperationError)).
   381  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   382  		Expect(HealthIs(health.HealthStatusMissing))
   383  }
   384  
   385  func TestAppCreation(t *testing.T) {
   386  	ctx := Given(t)
   387  	ctx.
   388  		Path(guestbookPath).
   389  		When().
   390  		CreateApp().
   391  		Then().
   392  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   393  		And(func(app *Application) {
   394  			assert.Equal(t, Name(), app.Name)
   395  			assert.Equal(t, RepoURL(RepoURLTypeFile), app.Spec.GetSource().RepoURL)
   396  			assert.Equal(t, guestbookPath, app.Spec.GetSource().Path)
   397  			assert.Equal(t, DeploymentNamespace(), app.Spec.Destination.Namespace)
   398  			assert.Equal(t, KubernetesInternalAPIServerAddr, app.Spec.Destination.Server)
   399  		}).
   400  		Expect(Event(EventReasonResourceCreated, "create")).
   401  		And(func(_ *Application) {
   402  			// app should be listed
   403  			output, err := RunCli("app", "list")
   404  			assert.NoError(t, err)
   405  			assert.Contains(t, output, Name())
   406  		}).
   407  		When().
   408  		// ensure that create is idempotent
   409  		CreateApp().
   410  		Then().
   411  		Given().
   412  		Revision("master").
   413  		When().
   414  		// ensure that update replaces spec and merge labels and annotations
   415  		And(func() {
   416  			FailOnErr(AppClientset.ArgoprojV1alpha1().Applications(TestNamespace()).Patch(context.Background(),
   417  				ctx.GetName(), types.MergePatchType, []byte(`{"metadata": {"labels": { "test": "label" }, "annotations": { "test": "annotation" }}}`), metav1.PatchOptions{}))
   418  		}).
   419  		CreateApp("--upsert").
   420  		Then().
   421  		And(func(app *Application) {
   422  			assert.Equal(t, "label", app.Labels["test"])
   423  			assert.Equal(t, "annotation", app.Annotations["test"])
   424  			assert.Equal(t, "master", app.Spec.GetSource().TargetRevision)
   425  		})
   426  }
   427  
   428  func TestAppCreationWithoutForceUpdate(t *testing.T) {
   429  	ctx := Given(t)
   430  
   431  	ctx.
   432  		Path(guestbookPath).
   433  		DestName("in-cluster").
   434  		When().
   435  		CreateApp().
   436  		Then().
   437  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   438  		And(func(app *Application) {
   439  			assert.Equal(t, ctx.AppName(), app.Name)
   440  			assert.Equal(t, RepoURL(RepoURLTypeFile), app.Spec.GetSource().RepoURL)
   441  			assert.Equal(t, guestbookPath, app.Spec.GetSource().Path)
   442  			assert.Equal(t, DeploymentNamespace(), app.Spec.Destination.Namespace)
   443  			assert.Equal(t, "in-cluster", app.Spec.Destination.Name)
   444  		}).
   445  		Expect(Event(EventReasonResourceCreated, "create")).
   446  		And(func(_ *Application) {
   447  			// app should be listed
   448  			output, err := RunCli("app", "list")
   449  			assert.NoError(t, err)
   450  			assert.Contains(t, output, Name())
   451  		}).
   452  		When().
   453  		IgnoreErrors().
   454  		CreateApp().
   455  		Then().
   456  		Expect(Error("", "existing application spec is different, use upsert flag to force update"))
   457  }
   458  
   459  func TestDeleteAppResource(t *testing.T) {
   460  	ctx := Given(t)
   461  
   462  	ctx.
   463  		Path(guestbookPath).
   464  		When().
   465  		CreateApp().
   466  		Sync().
   467  		Then().
   468  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   469  		And(func(_ *Application) {
   470  			// app should be listed
   471  			if _, err := RunCli("app", "delete-resource", Name(), "--kind", "Service", "--resource-name", "guestbook-ui"); err != nil {
   472  				assert.NoError(t, err)
   473  			}
   474  		}).
   475  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   476  		Expect(HealthIs(health.HealthStatusMissing))
   477  }
   478  
   479  // Fix for issue #2677, support PATCH in HTTP service
   480  func TestPatchHttp(t *testing.T) {
   481  	ctx := Given(t)
   482  
   483  	ctx.
   484  		Path(guestbookPath).
   485  		When().
   486  		CreateApp().
   487  		Sync().
   488  		PatchAppHttp(`{"metadata": {"labels": { "test": "patch" }, "annotations": { "test": "patch" }}}`).
   489  		Then().
   490  		And(func(app *Application) {
   491  			assert.Equal(t, "patch", app.Labels["test"])
   492  			assert.Equal(t, "patch", app.Annotations["test"])
   493  		})
   494  
   495  }
   496  
   497  // demonstrate that we cannot use a standard sync when an immutable field is changed, we must use "force"
   498  func TestImmutableChange(t *testing.T) {
   499  	SkipOnEnv(t, "OPENSHIFT")
   500  	Given(t).
   501  		Path("secrets").
   502  		When().
   503  		CreateApp().
   504  		PatchFile("secrets.yaml", `[{"op": "add", "path": "/data/new-field", "value": "dGVzdA=="}, {"op": "add", "path": "/immutable", "value": true}]`).
   505  		Sync().
   506  		Then().
   507  		Expect(OperationPhaseIs(OperationSucceeded)).
   508  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   509  		Expect(HealthIs(health.HealthStatusHealthy)).
   510  		When().
   511  		PatchFile("secrets.yaml", `[{"op": "add", "path": "/data/new-field", "value": "dGVzdDI="}]`).
   512  		IgnoreErrors().
   513  		Sync().
   514  		DoNotIgnoreErrors().
   515  		Then().
   516  		Expect(OperationPhaseIs(OperationFailed)).
   517  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   518  		Expect(ResourceResultNumbering(1)).
   519  		Expect(ResourceResultMatches(ResourceResult{
   520  			Kind:      "Secret",
   521  			Version:   "v1",
   522  			Namespace: DeploymentNamespace(),
   523  			Name:      "test-secret",
   524  			SyncPhase: "Sync",
   525  			Status:    "SyncFailed",
   526  			HookPhase: "Failed",
   527  			Message:   `Secret "test-secret" is invalid`,
   528  		})).
   529  		// now we can do this will a force
   530  		Given().
   531  		Force().
   532  		When().
   533  		Sync().
   534  		Then().
   535  		Expect(OperationPhaseIs(OperationSucceeded)).
   536  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   537  		Expect(HealthIs(health.HealthStatusHealthy))
   538  }
   539  
   540  func TestInvalidAppProject(t *testing.T) {
   541  	Given(t).
   542  		Path(guestbookPath).
   543  		Project("does-not-exist").
   544  		When().
   545  		IgnoreErrors().
   546  		CreateApp().
   547  		Then().
   548  		// We're not allowed to infer whether the project exists based on this error message. Instead, we get a generic
   549  		// permission denied error.
   550  		Expect(Error("", "permission denied"))
   551  }
   552  
   553  func TestAppDeletion(t *testing.T) {
   554  	Given(t).
   555  		Path(guestbookPath).
   556  		When().
   557  		CreateApp().
   558  		Then().
   559  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   560  		When().
   561  		Delete(true).
   562  		Then().
   563  		Expect(DoesNotExist()).
   564  		Expect(Event(EventReasonResourceDeleted, "delete"))
   565  
   566  	output, err := RunCli("app", "list")
   567  	assert.NoError(t, err)
   568  	assert.NotContains(t, output, Name())
   569  }
   570  
   571  func TestAppLabels(t *testing.T) {
   572  	Given(t).
   573  		Path("config-map").
   574  		When().
   575  		CreateApp("-l", "foo=bar").
   576  		Then().
   577  		And(func(app *Application) {
   578  			assert.Contains(t, FailOnErr(RunCli("app", "list")), Name())
   579  			assert.Contains(t, FailOnErr(RunCli("app", "list", "-l", "foo=bar")), Name())
   580  			assert.NotContains(t, FailOnErr(RunCli("app", "list", "-l", "foo=rubbish")), Name())
   581  		}).
   582  		Given().
   583  		// remove both name and replace labels means nothing will sync
   584  		Name("").
   585  		When().
   586  		IgnoreErrors().
   587  		Sync("-l", "foo=rubbish").
   588  		DoNotIgnoreErrors().
   589  		Then().
   590  		Expect(Error("", "No matching apps found for filter: selector foo=rubbish")).
   591  		// check we can update the app and it is then sync'd
   592  		Given().
   593  		When().
   594  		Sync("-l", "foo=bar")
   595  }
   596  
   597  func TestTrackAppStateAndSyncApp(t *testing.T) {
   598  	Given(t).
   599  		Path(guestbookPath).
   600  		When().
   601  		CreateApp().
   602  		Sync().
   603  		Then().
   604  		Expect(OperationPhaseIs(OperationSucceeded)).
   605  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   606  		Expect(HealthIs(health.HealthStatusHealthy)).
   607  		Expect(Success(fmt.Sprintf("Service     %s  guestbook-ui  Synced ", DeploymentNamespace()))).
   608  		Expect(Success(fmt.Sprintf("apps   Deployment  %s  guestbook-ui  Synced", DeploymentNamespace()))).
   609  		Expect(Event(EventReasonResourceUpdated, "sync")).
   610  		And(func(app *Application) {
   611  			assert.NotNil(t, app.Status.OperationState.SyncResult)
   612  		})
   613  }
   614  
   615  func TestAppRollbackSuccessful(t *testing.T) {
   616  	Given(t).
   617  		Path(guestbookPath).
   618  		When().
   619  		CreateApp().
   620  		Sync().
   621  		Then().
   622  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   623  		And(func(app *Application) {
   624  			assert.NotEmpty(t, app.Status.Sync.Revision)
   625  		}).
   626  		And(func(app *Application) {
   627  			appWithHistory := app.DeepCopy()
   628  			appWithHistory.Status.History = []RevisionHistory{{
   629  				ID:         1,
   630  				Revision:   app.Status.Sync.Revision,
   631  				DeployedAt: metav1.Time{Time: metav1.Now().UTC().Add(-1 * time.Minute)},
   632  				Source:     app.Spec.GetSource(),
   633  			}, {
   634  				ID:         2,
   635  				Revision:   "cdb",
   636  				DeployedAt: metav1.Time{Time: metav1.Now().UTC().Add(-2 * time.Minute)},
   637  				Source:     app.Spec.GetSource(),
   638  			}}
   639  			patch, _, err := diff.CreateTwoWayMergePatch(app, appWithHistory, &Application{})
   640  			require.NoError(t, err)
   641  			app, err = AppClientset.ArgoprojV1alpha1().Applications(TestNamespace()).Patch(context.Background(), app.Name, types.MergePatchType, patch, metav1.PatchOptions{})
   642  			require.NoError(t, err)
   643  
   644  			// sync app and make sure it reaches InSync state
   645  			_, err = RunCli("app", "rollback", app.Name, "1")
   646  			require.NoError(t, err)
   647  
   648  		}).
   649  		Expect(Event(EventReasonOperationStarted, "rollback")).
   650  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   651  		And(func(app *Application) {
   652  			assert.Equal(t, SyncStatusCodeSynced, app.Status.Sync.Status)
   653  			require.NotNil(t, app.Status.OperationState.SyncResult)
   654  			assert.Equal(t, 2, len(app.Status.OperationState.SyncResult.Resources))
   655  			assert.Equal(t, OperationSucceeded, app.Status.OperationState.Phase)
   656  			assert.Equal(t, 3, len(app.Status.History))
   657  		})
   658  }
   659  
   660  func TestComparisonFailsIfClusterNotAdded(t *testing.T) {
   661  	Given(t).
   662  		Path(guestbookPath).
   663  		DestServer("https://not-registered-cluster/api").
   664  		When().
   665  		IgnoreErrors().
   666  		CreateApp().
   667  		Then().
   668  		Expect(DoesNotExist())
   669  }
   670  
   671  func TestCannotSetInvalidPath(t *testing.T) {
   672  	Given(t).
   673  		Path(guestbookPath).
   674  		When().
   675  		CreateApp().
   676  		IgnoreErrors().
   677  		AppSet("--path", "garbage").
   678  		Then().
   679  		Expect(Error("", "app path does not exist"))
   680  }
   681  
   682  func TestManipulateApplicationResources(t *testing.T) {
   683  	Given(t).
   684  		Path(guestbookPath).
   685  		When().
   686  		CreateApp().
   687  		Sync().
   688  		Then().
   689  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   690  		And(func(app *Application) {
   691  			manifests, err := RunCli("app", "manifests", app.Name, "--source", "live")
   692  			assert.NoError(t, err)
   693  			resources, err := kube.SplitYAML([]byte(manifests))
   694  			assert.NoError(t, err)
   695  
   696  			index := -1
   697  			for i := range resources {
   698  				if resources[i].GetKind() == kube.DeploymentKind {
   699  					index = i
   700  					break
   701  				}
   702  			}
   703  			assert.True(t, index > -1)
   704  
   705  			deployment := resources[index]
   706  
   707  			closer, client, err := ArgoCDClientset.NewApplicationClient()
   708  			assert.NoError(t, err)
   709  			defer io.Close(closer)
   710  
   711  			_, err = client.DeleteResource(context.Background(), &applicationpkg.ApplicationResourceDeleteRequest{
   712  				Name:         &app.Name,
   713  				Group:        pointer.String(deployment.GroupVersionKind().Group),
   714  				Kind:         pointer.String(deployment.GroupVersionKind().Kind),
   715  				Version:      pointer.String(deployment.GroupVersionKind().Version),
   716  				Namespace:    pointer.String(deployment.GetNamespace()),
   717  				ResourceName: pointer.String(deployment.GetName()),
   718  			})
   719  			assert.NoError(t, err)
   720  		}).
   721  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync))
   722  }
   723  
   724  func assetSecretDataHidden(t *testing.T, manifest string) {
   725  	secret, err := UnmarshalToUnstructured(manifest)
   726  	assert.NoError(t, err)
   727  
   728  	_, hasStringData, err := unstructured.NestedMap(secret.Object, "stringData")
   729  	assert.NoError(t, err)
   730  	assert.False(t, hasStringData)
   731  
   732  	secretData, hasData, err := unstructured.NestedMap(secret.Object, "data")
   733  	assert.NoError(t, err)
   734  	assert.True(t, hasData)
   735  	for _, v := range secretData {
   736  		assert.Regexp(t, regexp.MustCompile(`[*]*`), v)
   737  	}
   738  	var lastAppliedConfigAnnotation string
   739  	annotations := secret.GetAnnotations()
   740  	if annotations != nil {
   741  		lastAppliedConfigAnnotation = annotations[v1.LastAppliedConfigAnnotation]
   742  	}
   743  	if lastAppliedConfigAnnotation != "" {
   744  		assetSecretDataHidden(t, lastAppliedConfigAnnotation)
   745  	}
   746  }
   747  
   748  func TestAppWithSecrets(t *testing.T) {
   749  	closer, client, err := ArgoCDClientset.NewApplicationClient()
   750  	assert.NoError(t, err)
   751  	defer io.Close(closer)
   752  
   753  	Given(t).
   754  		Path("secrets").
   755  		When().
   756  		CreateApp().
   757  		Sync().
   758  		Then().
   759  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   760  		And(func(app *Application) {
   761  			res := FailOnErr(client.GetResource(context.Background(), &applicationpkg.ApplicationResourceRequest{
   762  				Namespace:    &app.Spec.Destination.Namespace,
   763  				Kind:         pointer.String(kube.SecretKind),
   764  				Group:        pointer.String(""),
   765  				Name:         &app.Name,
   766  				Version:      pointer.String("v1"),
   767  				ResourceName: pointer.String("test-secret"),
   768  			})).(*applicationpkg.ApplicationResourceResponse)
   769  			assetSecretDataHidden(t, res.GetManifest())
   770  
   771  			manifests, err := client.GetManifests(context.Background(), &applicationpkg.ApplicationManifestQuery{Name: &app.Name})
   772  			errors.CheckError(err)
   773  
   774  			for _, manifest := range manifests.Manifests {
   775  				assetSecretDataHidden(t, manifest)
   776  			}
   777  
   778  			diffOutput := FailOnErr(RunCli("app", "diff", app.Name)).(string)
   779  			assert.Empty(t, diffOutput)
   780  
   781  			// make sure resource update error does not print secret details
   782  			_, err = RunCli("app", "patch-resource", "test-app-with-secrets", "--resource-name", "test-secret",
   783  				"--kind", "Secret", "--patch", `{"op": "add", "path": "/data", "value": "hello"}'`,
   784  				"--patch-type", "application/json-patch+json")
   785  			require.Error(t, err)
   786  			assert.Contains(t, err.Error(), fmt.Sprintf("failed to patch Secret %s/test-secret", DeploymentNamespace()))
   787  			assert.NotContains(t, err.Error(), "username")
   788  			assert.NotContains(t, err.Error(), "password")
   789  
   790  			// patch secret and make sure app is out of sync and diff detects the change
   791  			FailOnErr(KubeClientset.CoreV1().Secrets(DeploymentNamespace()).Patch(context.Background(),
   792  				"test-secret", types.JSONPatchType, []byte(`[
   793  	{"op": "remove", "path": "/data/username"},
   794  	{"op": "add", "path": "/stringData", "value": {"password": "foo"}}
   795  ]`), metav1.PatchOptions{}))
   796  		}).
   797  		When().
   798  		Refresh(RefreshTypeNormal).
   799  		Then().
   800  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   801  		And(func(app *Application) {
   802  			diffOutput, err := RunCli("app", "diff", app.Name)
   803  			assert.Error(t, err)
   804  			assert.Contains(t, diffOutput, "username: ++++++++")
   805  			assert.Contains(t, diffOutput, "password: ++++++++++++")
   806  
   807  			// local diff should ignore secrets
   808  			diffOutput = FailOnErr(RunCli("app", "diff", app.Name, "--local", "testdata", "--server-side-generate")).(string)
   809  			assert.Empty(t, diffOutput)
   810  
   811  			// ignore missing field and make sure diff shows no difference
   812  			app.Spec.IgnoreDifferences = []ResourceIgnoreDifferences{{
   813  				Kind: kube.SecretKind, JSONPointers: []string{"/data"},
   814  			}}
   815  			FailOnErr(client.UpdateSpec(context.Background(), &applicationpkg.ApplicationUpdateSpecRequest{Name: &app.Name, Spec: &app.Spec}))
   816  		}).
   817  		When().
   818  		Refresh(RefreshTypeNormal).
   819  		Then().
   820  		Expect(OperationPhaseIs(OperationSucceeded)).
   821  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   822  		And(func(app *Application) {
   823  			diffOutput := FailOnErr(RunCli("app", "diff", app.Name)).(string)
   824  			assert.Empty(t, diffOutput)
   825  		}).
   826  		// verify not committed secret also ignore during diffing
   827  		When().
   828  		WriteFile("secret3.yaml", `
   829  apiVersion: v1
   830  kind: Secret
   831  metadata:
   832    name: test-secret3
   833  stringData:
   834    username: test-username`).
   835  		Then().
   836  		And(func(app *Application) {
   837  			diffOutput := FailOnErr(RunCli("app", "diff", app.Name, "--local", "testdata", "--server-side-generate")).(string)
   838  			assert.Empty(t, diffOutput)
   839  		})
   840  }
   841  
   842  func TestResourceDiffing(t *testing.T) {
   843  	Given(t).
   844  		Path(guestbookPath).
   845  		When().
   846  		CreateApp().
   847  		Sync().
   848  		Then().
   849  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   850  		And(func(app *Application) {
   851  			// Patch deployment
   852  			_, err := KubeClientset.AppsV1().Deployments(DeploymentNamespace()).Patch(context.Background(),
   853  				"guestbook-ui", types.JSONPatchType, []byte(`[{ "op": "replace", "path": "/spec/template/spec/containers/0/image", "value": "test" }]`), metav1.PatchOptions{})
   854  			assert.NoError(t, err)
   855  		}).
   856  		When().
   857  		Refresh(RefreshTypeNormal).
   858  		Then().
   859  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   860  		And(func(app *Application) {
   861  			diffOutput, err := RunCli("app", "diff", app.Name, "--local", "testdata", "--server-side-generate")
   862  			assert.Error(t, err)
   863  			assert.Contains(t, diffOutput, fmt.Sprintf("===== apps/Deployment %s/guestbook-ui ======", DeploymentNamespace()))
   864  		}).
   865  		Given().
   866  		ResourceOverrides(map[string]ResourceOverride{"apps/Deployment": {
   867  			IgnoreDifferences: OverrideIgnoreDiff{JSONPointers: []string{"/spec/template/spec/containers/0/image"}},
   868  		}}).
   869  		When().
   870  		Refresh(RefreshTypeNormal).
   871  		Then().
   872  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   873  		And(func(app *Application) {
   874  			diffOutput, err := RunCli("app", "diff", app.Name, "--local", "testdata", "--server-side-generate")
   875  			assert.NoError(t, err)
   876  			assert.Empty(t, diffOutput)
   877  		}).
   878  		Given().
   879  		When().
   880  		// Now we migrate from client-side apply to server-side apply
   881  		// This is necessary, as starting with kubectl 1.26, all previously
   882  		// client-side owned fields have ownership migrated to the manager from
   883  		// the first ssa.
   884  		// More details: https://github.com/kubernetes/kubectl/issues/1337
   885  		PatchApp(`[{
   886  			"op": "add",
   887  			"path": "/spec/syncPolicy",
   888  			"value": { "syncOptions": ["ServerSideApply=true"] }
   889  			}]`).
   890  		Sync().
   891  		And(func() {
   892  			output, err := RunWithStdin(testdata.SSARevisionHistoryDeployment, "", "kubectl", "apply", "-n", DeploymentNamespace(), "--server-side=true", "--field-manager=revision-history-manager", "--validate=false", "--force-conflicts", "-f", "-")
   893  			assert.NoError(t, err)
   894  			assert.Contains(t, output, "serverside-applied")
   895  		}).
   896  		Refresh(RefreshTypeNormal).
   897  		Then().
   898  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   899  		Given().
   900  		ResourceOverrides(map[string]ResourceOverride{"apps/Deployment": {
   901  			IgnoreDifferences: OverrideIgnoreDiff{
   902  				ManagedFieldsManagers: []string{"revision-history-manager"},
   903  				JSONPointers:          []string{"/spec/template/spec/containers/0/image"},
   904  			},
   905  		}}).
   906  		When().
   907  		Refresh(RefreshTypeNormal).
   908  		Then().
   909  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   910  		Given().
   911  		When().
   912  		Sync().
   913  		PatchApp(`[{
   914  			"op": "add",
   915  			"path": "/spec/syncPolicy",
   916  			"value": { "syncOptions": ["RespectIgnoreDifferences=true"] }
   917  			}]`).
   918  		And(func() {
   919  			deployment, err := KubeClientset.AppsV1().Deployments(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{})
   920  			assert.NoError(t, err)
   921  			assert.Equal(t, int32(3), *deployment.Spec.RevisionHistoryLimit)
   922  		}).
   923  		And(func() {
   924  			output, err := RunWithStdin(testdata.SSARevisionHistoryDeployment, "", "kubectl", "apply", "-n", DeploymentNamespace(), "--server-side=true", "--field-manager=revision-history-manager", "--validate=false", "--force-conflicts", "-f", "-")
   925  			assert.NoError(t, err)
   926  			assert.Contains(t, output, "serverside-applied")
   927  		}).
   928  		Then().
   929  		When().Refresh(RefreshTypeNormal).
   930  		Then().
   931  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
   932  		And(func(app *Application) {
   933  			deployment, err := KubeClientset.AppsV1().Deployments(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{})
   934  			assert.NoError(t, err)
   935  			assert.Equal(t, int32(1), *deployment.Spec.RevisionHistoryLimit)
   936  		}).
   937  		When().Sync().Then().Expect(SyncStatusIs(SyncStatusCodeSynced)).
   938  		And(func(app *Application) {
   939  			deployment, err := KubeClientset.AppsV1().Deployments(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{})
   940  			assert.NoError(t, err)
   941  			assert.Equal(t, int32(1), *deployment.Spec.RevisionHistoryLimit)
   942  		})
   943  }
   944  
   945  func TestCRDs(t *testing.T) {
   946  	testEdgeCasesApplicationResources(t, "crd-creation", health.HealthStatusHealthy)
   947  }
   948  
   949  func TestKnownTypesInCRDDiffing(t *testing.T) {
   950  	dummiesGVR := schema.GroupVersionResource{Group: application.Group, Version: "v1alpha1", Resource: "dummies"}
   951  
   952  	Given(t).
   953  		Path("crd-creation").
   954  		When().CreateApp().Sync().Then().
   955  		Expect(OperationPhaseIs(OperationSucceeded)).Expect(SyncStatusIs(SyncStatusCodeSynced)).
   956  		When().
   957  		And(func() {
   958  			dummyResIf := DynamicClientset.Resource(dummiesGVR).Namespace(DeploymentNamespace())
   959  			patchData := []byte(`{"spec":{"cpu": "2"}}`)
   960  			FailOnErr(dummyResIf.Patch(context.Background(), "dummy-crd-instance", types.MergePatchType, patchData, metav1.PatchOptions{}))
   961  		}).Refresh(RefreshTypeNormal).
   962  		Then().
   963  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
   964  		When().
   965  		And(func() {
   966  			SetResourceOverrides(map[string]ResourceOverride{
   967  				"argoproj.io/Dummy": {
   968  					KnownTypeFields: []KnownTypeField{{
   969  						Field: "spec",
   970  						Type:  "core/v1/ResourceList",
   971  					}},
   972  				},
   973  			})
   974  		}).
   975  		Refresh(RefreshTypeNormal).
   976  		Then().
   977  		Expect(SyncStatusIs(SyncStatusCodeSynced))
   978  }
   979  
   980  func TestDuplicatedResources(t *testing.T) {
   981  	testEdgeCasesApplicationResources(t, "duplicated-resources", health.HealthStatusHealthy)
   982  }
   983  
   984  func TestConfigMap(t *testing.T) {
   985  	testEdgeCasesApplicationResources(t, "config-map", health.HealthStatusHealthy, "my-map  Synced                configmap/my-map created")
   986  }
   987  
   988  func testEdgeCasesApplicationResources(t *testing.T, appPath string, statusCode health.HealthStatusCode, message ...string) {
   989  	expect := Given(t).
   990  		Path(appPath).
   991  		When().
   992  		CreateApp().
   993  		Sync().
   994  		Then().
   995  		Expect(OperationPhaseIs(OperationSucceeded)).
   996  		Expect(SyncStatusIs(SyncStatusCodeSynced))
   997  	for i := range message {
   998  		expect = expect.Expect(Success(message[i]))
   999  	}
  1000  	expect.
  1001  		Expect(HealthIs(statusCode)).
  1002  		And(func(app *Application) {
  1003  			diffOutput, err := RunCli("app", "diff", app.Name, "--local", "testdata", "--server-side-generate")
  1004  			assert.Empty(t, diffOutput)
  1005  			assert.NoError(t, err)
  1006  		})
  1007  }
  1008  
  1009  const actionsConfig = `discovery.lua: return { sample = {} }
  1010  definitions:
  1011  - name: sample
  1012    action.lua: |
  1013      obj.metadata.labels.sample = 'test'
  1014      return obj`
  1015  
  1016  func TestOldStyleResourceAction(t *testing.T) {
  1017  	Given(t).
  1018  		Path(guestbookPath).
  1019  		ResourceOverrides(map[string]ResourceOverride{"apps/Deployment": {Actions: actionsConfig}}).
  1020  		When().
  1021  		CreateApp().
  1022  		Sync().
  1023  		Then().
  1024  		And(func(app *Application) {
  1025  
  1026  			closer, client, err := ArgoCDClientset.NewApplicationClient()
  1027  			assert.NoError(t, err)
  1028  			defer io.Close(closer)
  1029  
  1030  			actions, err := client.ListResourceActions(context.Background(), &applicationpkg.ApplicationResourceRequest{
  1031  				Name:         &app.Name,
  1032  				Group:        pointer.String("apps"),
  1033  				Kind:         pointer.String("Deployment"),
  1034  				Version:      pointer.String("v1"),
  1035  				Namespace:    pointer.String(DeploymentNamespace()),
  1036  				ResourceName: pointer.String("guestbook-ui"),
  1037  			})
  1038  			assert.NoError(t, err)
  1039  			assert.Equal(t, []*ResourceAction{{Name: "sample", Disabled: false}}, actions.Actions)
  1040  
  1041  			_, err = client.RunResourceAction(context.Background(), &applicationpkg.ResourceActionRunRequest{Name: &app.Name,
  1042  				Group:        pointer.String("apps"),
  1043  				Kind:         pointer.String("Deployment"),
  1044  				Version:      pointer.String("v1"),
  1045  				Namespace:    pointer.String(DeploymentNamespace()),
  1046  				ResourceName: pointer.String("guestbook-ui"),
  1047  				Action:       pointer.String("sample"),
  1048  			})
  1049  			assert.NoError(t, err)
  1050  
  1051  			deployment, err := KubeClientset.AppsV1().Deployments(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{})
  1052  			assert.NoError(t, err)
  1053  
  1054  			assert.Equal(t, "test", deployment.Labels["sample"])
  1055  		})
  1056  }
  1057  
  1058  const newStyleActionsConfig = `discovery.lua: return { sample = {} }
  1059  definitions:
  1060  - name: sample
  1061    action.lua: |
  1062      local os = require("os")
  1063  
  1064      function deepCopy(object)
  1065        local lookup_table = {}
  1066        local function _copy(obj)
  1067          if type(obj) ~= "table" then
  1068            return obj
  1069          elseif lookup_table[obj] then
  1070            return lookup_table[obj]
  1071          elseif next(obj) == nil then
  1072            return nil
  1073          else
  1074            local new_table = {}
  1075            lookup_table[obj] = new_table
  1076            for key, value in pairs(obj) do
  1077              new_table[_copy(key)] = _copy(value)
  1078            end
  1079            return setmetatable(new_table, getmetatable(obj))
  1080          end
  1081        end
  1082        return _copy(object)
  1083      end
  1084    
  1085      job = {}
  1086      job.apiVersion = "batch/v1"
  1087      job.kind = "Job"
  1088    
  1089      job.metadata = {}
  1090      job.metadata.name = obj.metadata.name .. "-123"
  1091      job.metadata.namespace = obj.metadata.namespace
  1092    
  1093      ownerRef = {}
  1094      ownerRef.apiVersion = obj.apiVersion
  1095      ownerRef.kind = obj.kind
  1096      ownerRef.name = obj.metadata.name
  1097      ownerRef.uid = obj.metadata.uid
  1098      job.metadata.ownerReferences = {}
  1099      job.metadata.ownerReferences[1] = ownerRef
  1100    
  1101      job.spec = {}
  1102      job.spec.suspend = false
  1103      job.spec.template = {}
  1104      job.spec.template.spec = deepCopy(obj.spec.jobTemplate.spec.template.spec)
  1105    
  1106      impactedResource = {}
  1107      impactedResource.operation = "create"
  1108      impactedResource.resource = job
  1109      result = {}
  1110      result[1] = impactedResource
  1111    
  1112      return result`
  1113  
  1114  func TestNewStyleResourceActionPermitted(t *testing.T) {
  1115  	Given(t).
  1116  		Path(resourceActions).
  1117  		ResourceOverrides(map[string]ResourceOverride{"batch/CronJob": {Actions: newStyleActionsConfig}}).
  1118  		ProjectSpec(AppProjectSpec{
  1119  			SourceRepos:  []string{"*"},
  1120  			Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}},
  1121  			NamespaceResourceWhitelist: []metav1.GroupKind{
  1122  				{Group: "batch", Kind: "Job"},
  1123  				{Group: "batch", Kind: "CronJob"},
  1124  			}}).
  1125  		When().
  1126  		CreateApp().
  1127  		Sync().
  1128  		Then().
  1129  		And(func(app *Application) {
  1130  
  1131  			closer, client, err := ArgoCDClientset.NewApplicationClient()
  1132  			assert.NoError(t, err)
  1133  			defer io.Close(closer)
  1134  
  1135  			actions, err := client.ListResourceActions(context.Background(), &applicationpkg.ApplicationResourceRequest{
  1136  				Name:         &app.Name,
  1137  				Group:        pointer.String("batch"),
  1138  				Kind:         pointer.String("CronJob"),
  1139  				Version:      pointer.String("v1"),
  1140  				Namespace:    pointer.String(DeploymentNamespace()),
  1141  				ResourceName: pointer.String("hello"),
  1142  			})
  1143  			assert.NoError(t, err)
  1144  			assert.Equal(t, []*ResourceAction{{Name: "sample", Disabled: false}}, actions.Actions)
  1145  
  1146  			_, err = client.RunResourceAction(context.Background(), &applicationpkg.ResourceActionRunRequest{Name: &app.Name,
  1147  				Group:        pointer.String("batch"),
  1148  				Kind:         pointer.String("CronJob"),
  1149  				Version:      pointer.String("v1"),
  1150  				Namespace:    pointer.String(DeploymentNamespace()),
  1151  				ResourceName: pointer.String("hello"),
  1152  				Action:       pointer.String("sample"),
  1153  			})
  1154  			assert.NoError(t, err)
  1155  
  1156  			_, err = KubeClientset.BatchV1().Jobs(DeploymentNamespace()).Get(context.Background(), "hello-123", metav1.GetOptions{})
  1157  			assert.NoError(t, err)
  1158  		})
  1159  }
  1160  
  1161  const newStyleActionsConfigMixedOk = `discovery.lua: return { sample = {} }
  1162  definitions:
  1163  - name: sample
  1164    action.lua: |
  1165      local os = require("os")
  1166  
  1167      function deepCopy(object)
  1168        local lookup_table = {}
  1169        local function _copy(obj)
  1170          if type(obj) ~= "table" then
  1171            return obj
  1172          elseif lookup_table[obj] then
  1173            return lookup_table[obj]
  1174          elseif next(obj) == nil then
  1175            return nil
  1176          else
  1177            local new_table = {}
  1178            lookup_table[obj] = new_table
  1179            for key, value in pairs(obj) do
  1180              new_table[_copy(key)] = _copy(value)
  1181            end
  1182            return setmetatable(new_table, getmetatable(obj))
  1183          end
  1184        end
  1185        return _copy(object)
  1186      end
  1187    
  1188      job = {}
  1189      job.apiVersion = "batch/v1"
  1190      job.kind = "Job"
  1191    
  1192      job.metadata = {}
  1193      job.metadata.name = obj.metadata.name .. "-123"
  1194      job.metadata.namespace = obj.metadata.namespace
  1195    
  1196      ownerRef = {}
  1197      ownerRef.apiVersion = obj.apiVersion
  1198      ownerRef.kind = obj.kind
  1199      ownerRef.name = obj.metadata.name
  1200      ownerRef.uid = obj.metadata.uid
  1201      job.metadata.ownerReferences = {}
  1202      job.metadata.ownerReferences[1] = ownerRef
  1203    
  1204      job.spec = {}
  1205      job.spec.suspend = false
  1206      job.spec.template = {}
  1207      job.spec.template.spec = deepCopy(obj.spec.jobTemplate.spec.template.spec)
  1208    
  1209      impactedResource1 = {}
  1210      impactedResource1.operation = "create"
  1211      impactedResource1.resource = job
  1212      result = {}
  1213      result[1] = impactedResource1
  1214  
  1215      obj.metadata.labels["aKey"] = 'aValue'
  1216      impactedResource2 = {}
  1217      impactedResource2.operation = "patch"
  1218      impactedResource2.resource = obj
  1219  
  1220      result[2] = impactedResource2
  1221    
  1222      return result`
  1223  
  1224  func TestNewStyleResourceActionMixedOk(t *testing.T) {
  1225  	Given(t).
  1226  		Path(resourceActions).
  1227  		ResourceOverrides(map[string]ResourceOverride{"batch/CronJob": {Actions: newStyleActionsConfigMixedOk}}).
  1228  		ProjectSpec(AppProjectSpec{
  1229  			SourceRepos:  []string{"*"},
  1230  			Destinations: []ApplicationDestination{{Namespace: "*", Server: "*"}},
  1231  			NamespaceResourceWhitelist: []metav1.GroupKind{
  1232  				{Group: "batch", Kind: "Job"},
  1233  				{Group: "batch", Kind: "CronJob"},
  1234  			}}).
  1235  		When().
  1236  		CreateApp().
  1237  		Sync().
  1238  		Then().
  1239  		And(func(app *Application) {
  1240  
  1241  			closer, client, err := ArgoCDClientset.NewApplicationClient()
  1242  			assert.NoError(t, err)
  1243  			defer io.Close(closer)
  1244  
  1245  			actions, err := client.ListResourceActions(context.Background(), &applicationpkg.ApplicationResourceRequest{
  1246  				Name:         &app.Name,
  1247  				Group:        pointer.String("batch"),
  1248  				Kind:         pointer.String("CronJob"),
  1249  				Version:      pointer.String("v1"),
  1250  				Namespace:    pointer.String(DeploymentNamespace()),
  1251  				ResourceName: pointer.String("hello"),
  1252  			})
  1253  			assert.NoError(t, err)
  1254  			assert.Equal(t, []*ResourceAction{{Name: "sample", Disabled: false}}, actions.Actions)
  1255  
  1256  			_, err = client.RunResourceAction(context.Background(), &applicationpkg.ResourceActionRunRequest{Name: &app.Name,
  1257  				Group:        pointer.String("batch"),
  1258  				Kind:         pointer.String("CronJob"),
  1259  				Version:      pointer.String("v1"),
  1260  				Namespace:    pointer.String(DeploymentNamespace()),
  1261  				ResourceName: pointer.String("hello"),
  1262  				Action:       pointer.String("sample"),
  1263  			})
  1264  			assert.NoError(t, err)
  1265  
  1266  			// Assert new Job was created
  1267  			_, err = KubeClientset.BatchV1().Jobs(DeploymentNamespace()).Get(context.Background(), "hello-123", metav1.GetOptions{})
  1268  			assert.NoError(t, err)
  1269  			// Assert the original CronJob was patched
  1270  			cronJob, err := KubeClientset.BatchV1().CronJobs(DeploymentNamespace()).Get(context.Background(), "hello", metav1.GetOptions{})
  1271  			assert.Equal(t, "aValue", cronJob.Labels["aKey"])
  1272  			assert.NoError(t, err)
  1273  		})
  1274  }
  1275  
  1276  func TestSyncResourceByLabel(t *testing.T) {
  1277  	Given(t).
  1278  		Path(guestbookPath).
  1279  		When().
  1280  		CreateApp().
  1281  		Sync().
  1282  		Then().
  1283  		And(func(app *Application) {
  1284  			_, _ = RunCli("app", "sync", app.Name, "--label", fmt.Sprintf("app.kubernetes.io/instance=%s", app.Name))
  1285  		}).
  1286  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1287  		And(func(app *Application) {
  1288  			_, err := RunCli("app", "sync", app.Name, "--label", "this-label=does-not-exist")
  1289  			assert.Error(t, err)
  1290  			assert.Contains(t, err.Error(), "level=fatal")
  1291  		})
  1292  }
  1293  
  1294  func TestSyncResourceByProject(t *testing.T) {
  1295  	Given(t).
  1296  		Path(guestbookPath).
  1297  		When().
  1298  		CreateApp().
  1299  		Sync().
  1300  		Then().
  1301  		And(func(app *Application) {
  1302  			_, _ = RunCli("app", "sync", app.Name, "--project", app.Spec.Project)
  1303  		}).
  1304  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1305  		And(func(app *Application) {
  1306  			_, err := RunCli("app", "sync", app.Name, "--project", "this-project-does-not-exist")
  1307  			assert.Error(t, err)
  1308  			assert.Contains(t, err.Error(), "level=fatal")
  1309  		})
  1310  }
  1311  
  1312  func TestLocalManifestSync(t *testing.T) {
  1313  	Given(t).
  1314  		Path(guestbookPath).
  1315  		When().
  1316  		CreateApp().
  1317  		Sync().
  1318  		Then().
  1319  		And(func(app *Application) {
  1320  			res, _ := RunCli("app", "manifests", app.Name)
  1321  			assert.Contains(t, res, "containerPort: 80")
  1322  			assert.Contains(t, res, "image: quay.io/argoprojlabs/argocd-e2e-container:0.2")
  1323  		}).
  1324  		Given().
  1325  		LocalPath(guestbookPathLocal).
  1326  		When().
  1327  		Sync("--local-repo-root", ".").
  1328  		Then().
  1329  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1330  		And(func(app *Application) {
  1331  			res, _ := RunCli("app", "manifests", app.Name)
  1332  			assert.Contains(t, res, "containerPort: 81")
  1333  			assert.Contains(t, res, "image: quay.io/argoprojlabs/argocd-e2e-container:0.3")
  1334  		}).
  1335  		Given().
  1336  		LocalPath("").
  1337  		When().
  1338  		Sync().
  1339  		Then().
  1340  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1341  		And(func(app *Application) {
  1342  			res, _ := RunCli("app", "manifests", app.Name)
  1343  			assert.Contains(t, res, "containerPort: 80")
  1344  			assert.Contains(t, res, "image: quay.io/argoprojlabs/argocd-e2e-container:0.2")
  1345  		})
  1346  }
  1347  
  1348  func TestLocalSync(t *testing.T) {
  1349  	Given(t).
  1350  		// we've got to use Helm as this uses kubeVersion
  1351  		Path("helm").
  1352  		When().
  1353  		CreateApp().
  1354  		Then().
  1355  		And(func(app *Application) {
  1356  			FailOnErr(RunCli("app", "sync", app.Name, "--local", "testdata/helm"))
  1357  		})
  1358  }
  1359  
  1360  func TestNoLocalSyncWithAutosyncEnabled(t *testing.T) {
  1361  	Given(t).
  1362  		Path(guestbookPath).
  1363  		When().
  1364  		CreateApp().
  1365  		Sync().
  1366  		Then().
  1367  		And(func(app *Application) {
  1368  			_, err := RunCli("app", "set", app.Name, "--sync-policy", "automated")
  1369  			assert.NoError(t, err)
  1370  
  1371  			_, err = RunCli("app", "sync", app.Name, "--local", guestbookPathLocal)
  1372  			assert.Error(t, err)
  1373  		})
  1374  }
  1375  
  1376  func TestLocalSyncDryRunWithAutosyncEnabled(t *testing.T) {
  1377  	Given(t).
  1378  		Path(guestbookPath).
  1379  		When().
  1380  		CreateApp().
  1381  		Sync().
  1382  		Then().
  1383  		And(func(app *Application) {
  1384  			_, err := RunCli("app", "set", app.Name, "--sync-policy", "automated")
  1385  			assert.NoError(t, err)
  1386  
  1387  			appBefore := app.DeepCopy()
  1388  			_, err = RunCli("app", "sync", app.Name, "--dry-run", "--local-repo-root", ".", "--local", guestbookPathLocal)
  1389  			assert.NoError(t, err)
  1390  
  1391  			appAfter := app.DeepCopy()
  1392  			assert.True(t, reflect.DeepEqual(appBefore, appAfter))
  1393  		})
  1394  }
  1395  
  1396  func TestSyncAsync(t *testing.T) {
  1397  	Given(t).
  1398  		Path(guestbookPath).
  1399  		Async(true).
  1400  		When().
  1401  		CreateApp().
  1402  		Sync().
  1403  		Then().
  1404  		Expect(Success("")).
  1405  		Expect(OperationPhaseIs(OperationSucceeded)).
  1406  		Expect(SyncStatusIs(SyncStatusCodeSynced))
  1407  }
  1408  
  1409  // assertResourceActions verifies if view/modify resource actions are successful/failing for given application
  1410  func assertResourceActions(t *testing.T, appName string, successful bool) {
  1411  	assertError := func(err error, message string) {
  1412  		if successful {
  1413  			assert.NoError(t, err)
  1414  		} else {
  1415  			if assert.Error(t, err) {
  1416  				assert.Contains(t, err.Error(), message)
  1417  			}
  1418  		}
  1419  	}
  1420  
  1421  	closer, cdClient := ArgoCDClientset.NewApplicationClientOrDie()
  1422  	defer io.Close(closer)
  1423  
  1424  	deploymentResource, err := KubeClientset.AppsV1().Deployments(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{})
  1425  	require.NoError(t, err)
  1426  
  1427  	logs, err := cdClient.PodLogs(context.Background(), &applicationpkg.ApplicationPodLogsQuery{
  1428  		Group:        pointer.String("apps"),
  1429  		Kind:         pointer.String("Deployment"),
  1430  		Name:         &appName,
  1431  		Namespace:    pointer.String(DeploymentNamespace()),
  1432  		Container:    pointer.String(""),
  1433  		SinceSeconds: pointer.Int64(0),
  1434  		TailLines:    pointer.Int64(0),
  1435  		Follow:       pointer.Bool(false),
  1436  	})
  1437  	require.NoError(t, err)
  1438  	_, err = logs.Recv()
  1439  	assertError(err, "EOF")
  1440  
  1441  	expectedError := fmt.Sprintf("Deployment apps guestbook-ui not found as part of application %s", appName)
  1442  
  1443  	_, err = cdClient.ListResourceEvents(context.Background(), &applicationpkg.ApplicationResourceEventsQuery{
  1444  		Name:              &appName,
  1445  		ResourceName:      pointer.String("guestbook-ui"),
  1446  		ResourceNamespace: pointer.String(DeploymentNamespace()),
  1447  		ResourceUID:       pointer.String(string(deploymentResource.UID)),
  1448  	})
  1449  	assertError(err, fmt.Sprintf("%s not found as part of application %s", "guestbook-ui", appName))
  1450  
  1451  	_, err = cdClient.GetResource(context.Background(), &applicationpkg.ApplicationResourceRequest{
  1452  		Name:         &appName,
  1453  		ResourceName: pointer.String("guestbook-ui"),
  1454  		Namespace:    pointer.String(DeploymentNamespace()),
  1455  		Version:      pointer.String("v1"),
  1456  		Group:        pointer.String("apps"),
  1457  		Kind:         pointer.String("Deployment"),
  1458  	})
  1459  	assertError(err, expectedError)
  1460  
  1461  	_, err = cdClient.RunResourceAction(context.Background(), &applicationpkg.ResourceActionRunRequest{
  1462  		Name:         &appName,
  1463  		ResourceName: pointer.String("guestbook-ui"),
  1464  		Namespace:    pointer.String(DeploymentNamespace()),
  1465  		Version:      pointer.String("v1"),
  1466  		Group:        pointer.String("apps"),
  1467  		Kind:         pointer.String("Deployment"),
  1468  		Action:       pointer.String("restart"),
  1469  	})
  1470  	assertError(err, expectedError)
  1471  
  1472  	_, err = cdClient.DeleteResource(context.Background(), &applicationpkg.ApplicationResourceDeleteRequest{
  1473  		Name:         &appName,
  1474  		ResourceName: pointer.String("guestbook-ui"),
  1475  		Namespace:    pointer.String(DeploymentNamespace()),
  1476  		Version:      pointer.String("v1"),
  1477  		Group:        pointer.String("apps"),
  1478  		Kind:         pointer.String("Deployment"),
  1479  	})
  1480  	assertError(err, expectedError)
  1481  }
  1482  
  1483  func TestPermissions(t *testing.T) {
  1484  	appCtx := Given(t)
  1485  	projName := "argo-project"
  1486  	projActions := projectFixture.
  1487  		Given(t).
  1488  		Name(projName).
  1489  		When().
  1490  		Create()
  1491  
  1492  	sourceError := fmt.Sprintf("application repo %s is not permitted in project 'argo-project'", RepoURL(RepoURLTypeFile))
  1493  	destinationError := fmt.Sprintf("application destination server '%s' and namespace '%s' do not match any of the allowed destinations in project 'argo-project'", KubernetesInternalAPIServerAddr, DeploymentNamespace())
  1494  
  1495  	appCtx.
  1496  		Path("guestbook-logs").
  1497  		Project(projName).
  1498  		When().
  1499  		IgnoreErrors().
  1500  		// ensure app is not created if project permissions are missing
  1501  		CreateApp().
  1502  		Then().
  1503  		Expect(Error("", sourceError)).
  1504  		Expect(Error("", destinationError)).
  1505  		When().
  1506  		DoNotIgnoreErrors().
  1507  		// add missing permissions, create and sync app
  1508  		And(func() {
  1509  			projActions.AddDestination("*", "*")
  1510  			projActions.AddSource("*")
  1511  		}).
  1512  		CreateApp().
  1513  		Sync().
  1514  		Then().
  1515  		// make sure application resource actiions are successful
  1516  		And(func(app *Application) {
  1517  			assertResourceActions(t, app.Name, true)
  1518  		}).
  1519  		When().
  1520  		// remove projet permissions and "refresh" app
  1521  		And(func() {
  1522  			projActions.UpdateProject(func(proj *AppProject) {
  1523  				proj.Spec.Destinations = nil
  1524  				proj.Spec.SourceRepos = nil
  1525  			})
  1526  		}).
  1527  		Refresh(RefreshTypeNormal).
  1528  		Then().
  1529  		// ensure app resource tree is empty when source/destination permissions are missing
  1530  		Expect(Condition(ApplicationConditionInvalidSpecError, destinationError)).
  1531  		Expect(Condition(ApplicationConditionInvalidSpecError, sourceError)).
  1532  		And(func(app *Application) {
  1533  			closer, cdClient := ArgoCDClientset.NewApplicationClientOrDie()
  1534  			defer io.Close(closer)
  1535  			appName, appNs := argo.ParseFromQualifiedName(app.Name, "")
  1536  			fmt.Printf("APP NAME: %s\n", appName)
  1537  			tree, err := cdClient.ResourceTree(context.Background(), &applicationpkg.ResourcesQuery{ApplicationName: &appName, AppNamespace: &appNs})
  1538  			require.NoError(t, err)
  1539  			assert.Len(t, tree.Nodes, 0)
  1540  			assert.Len(t, tree.OrphanedNodes, 0)
  1541  		}).
  1542  		When().
  1543  		// add missing permissions but deny management of Deployment kind
  1544  		And(func() {
  1545  			projActions.
  1546  				AddDestination("*", "*").
  1547  				AddSource("*").
  1548  				UpdateProject(func(proj *AppProject) {
  1549  					proj.Spec.NamespaceResourceBlacklist = []metav1.GroupKind{{Group: "*", Kind: "Deployment"}}
  1550  				})
  1551  		}).
  1552  		Refresh(RefreshTypeNormal).
  1553  		Then().
  1554  		// make sure application resource actiions are failing
  1555  		And(func(app *Application) {
  1556  			assertResourceActions(t, "test-permissions", false)
  1557  		})
  1558  }
  1559  
  1560  func TestPermissionWithScopedRepo(t *testing.T) {
  1561  	projName := "argo-project"
  1562  	fixture.EnsureCleanState(t)
  1563  	projectFixture.
  1564  		Given(t).
  1565  		Name(projName).
  1566  		Destination("*,*").
  1567  		When().
  1568  		Create().
  1569  		AddSource("*")
  1570  
  1571  	repoFixture.Given(t, true).
  1572  		When().
  1573  		Path(RepoURL(RepoURLTypeFile)).
  1574  		Project(projName).
  1575  		Create()
  1576  
  1577  	GivenWithSameState(t).
  1578  		Project(projName).
  1579  		RepoURLType(RepoURLTypeFile).
  1580  		Path("two-nice-pods").
  1581  		When().
  1582  		PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Prune=false"}}]`).
  1583  		CreateApp().
  1584  		Sync().
  1585  		Then().
  1586  		Expect(OperationPhaseIs(OperationSucceeded)).
  1587  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1588  		When().
  1589  		DeleteFile("pod-1.yaml").
  1590  		Refresh(RefreshTypeHard).
  1591  		IgnoreErrors().
  1592  		Sync().
  1593  		Then().
  1594  		Expect(OperationPhaseIs(OperationSucceeded)).
  1595  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
  1596  		Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeOutOfSync))
  1597  }
  1598  
  1599  func TestPermissionDeniedWithScopedRepo(t *testing.T) {
  1600  	projName := "argo-project"
  1601  	projectFixture.
  1602  		Given(t).
  1603  		Name(projName).
  1604  		Destination("*,*").
  1605  		When().
  1606  		Create()
  1607  
  1608  	repoFixture.Given(t, true).
  1609  		When().
  1610  		Path(RepoURL(RepoURLTypeFile)).
  1611  		Create()
  1612  
  1613  	GivenWithSameState(t).
  1614  		Project(projName).
  1615  		RepoURLType(RepoURLTypeFile).
  1616  		Path("two-nice-pods").
  1617  		When().
  1618  		PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Prune=false"}}]`).
  1619  		IgnoreErrors().
  1620  		CreateApp().
  1621  		Then().
  1622  		Expect(Error("", "is not permitted in project"))
  1623  }
  1624  
  1625  func TestPermissionDeniedWithNegatedNamespace(t *testing.T) {
  1626  	projName := "argo-project"
  1627  	projectFixture.
  1628  		Given(t).
  1629  		Name(projName).
  1630  		Destination("*,!*test-permission-denied-with-negated-namespace*").
  1631  		When().
  1632  		Create()
  1633  
  1634  	repoFixture.Given(t, true).
  1635  		When().
  1636  		Path(RepoURL(RepoURLTypeFile)).
  1637  		Project(projName).
  1638  		Create()
  1639  
  1640  	GivenWithSameState(t).
  1641  		Project(projName).
  1642  		RepoURLType(RepoURLTypeFile).
  1643  		Path("two-nice-pods").
  1644  		When().
  1645  		PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Prune=false"}}]`).
  1646  		IgnoreErrors().
  1647  		CreateApp().
  1648  		Then().
  1649  		Expect(Error("", "do not match any of the allowed destinations in project"))
  1650  }
  1651  
  1652  func TestPermissionDeniedWithNegatedServer(t *testing.T) {
  1653  	projName := "argo-project"
  1654  	projectFixture.
  1655  		Given(t).
  1656  		Name(projName).
  1657  		Destination("!https://kubernetes.default.svc,*").
  1658  		When().
  1659  		Create()
  1660  
  1661  	repoFixture.Given(t, true).
  1662  		When().
  1663  		Path(RepoURL(RepoURLTypeFile)).
  1664  		Project(projName).
  1665  		Create()
  1666  
  1667  	GivenWithSameState(t).
  1668  		Project(projName).
  1669  		RepoURLType(RepoURLTypeFile).
  1670  		Path("two-nice-pods").
  1671  		When().
  1672  		PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Prune=false"}}]`).
  1673  		IgnoreErrors().
  1674  		CreateApp().
  1675  		Then().
  1676  		Expect(Error("", "do not match any of the allowed destinations in project"))
  1677  }
  1678  
  1679  // make sure that if we deleted a resource from the app, it is not pruned if annotated with Prune=false
  1680  func TestSyncOptionPruneFalse(t *testing.T) {
  1681  	Given(t).
  1682  		Path("two-nice-pods").
  1683  		When().
  1684  		PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Prune=false"}}]`).
  1685  		CreateApp().
  1686  		Sync().
  1687  		Then().
  1688  		Expect(OperationPhaseIs(OperationSucceeded)).
  1689  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1690  		When().
  1691  		DeleteFile("pod-1.yaml").
  1692  		Refresh(RefreshTypeHard).
  1693  		IgnoreErrors().
  1694  		Sync().
  1695  		Then().
  1696  		Expect(OperationPhaseIs(OperationSucceeded)).
  1697  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
  1698  		Expect(ResourceSyncStatusIs("Pod", "pod-1", SyncStatusCodeOutOfSync))
  1699  }
  1700  
  1701  // make sure that if we have an invalid manifest, we can add it if we disable validation, we get a server error rather than a client error
  1702  func TestSyncOptionValidateFalse(t *testing.T) {
  1703  
  1704  	Given(t).
  1705  		Path("crd-validation").
  1706  		When().
  1707  		CreateApp().
  1708  		Then().
  1709  		Expect(Success("")).
  1710  		When().
  1711  		IgnoreErrors().
  1712  		Sync().
  1713  		Then().
  1714  		// client error. K8s API changed error message w/ 1.25, so for now, we need to check both
  1715  		Expect(ErrorRegex("error validating data|of type int32", "")).
  1716  		When().
  1717  		PatchFile("deployment.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Validate=false"}}]`).
  1718  		Sync().
  1719  		Then().
  1720  		// server error
  1721  		Expect(Error("cannot be handled as a Deployment", ""))
  1722  }
  1723  
  1724  // make sure that, if we have a resource that needs pruning, but we're ignoring it, the app is in-sync
  1725  func TestCompareOptionIgnoreExtraneous(t *testing.T) {
  1726  	Given(t).
  1727  		Prune(false).
  1728  		Path("two-nice-pods").
  1729  		When().
  1730  		PatchFile("pod-1.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/compare-options": "IgnoreExtraneous"}}]`).
  1731  		CreateApp().
  1732  		Sync().
  1733  		Then().
  1734  		Expect(OperationPhaseIs(OperationSucceeded)).
  1735  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1736  		When().
  1737  		DeleteFile("pod-1.yaml").
  1738  		Refresh(RefreshTypeHard).
  1739  		Then().
  1740  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1741  		And(func(app *Application) {
  1742  			assert.Len(t, app.Status.Resources, 2)
  1743  			statusByName := map[string]SyncStatusCode{}
  1744  			for _, r := range app.Status.Resources {
  1745  				statusByName[r.Name] = r.Status
  1746  			}
  1747  			assert.Equal(t, SyncStatusCodeOutOfSync, statusByName["pod-1"])
  1748  			assert.Equal(t, SyncStatusCodeSynced, statusByName["pod-2"])
  1749  		}).
  1750  		When().
  1751  		Sync().
  1752  		Then().
  1753  		Expect(OperationPhaseIs(OperationSucceeded)).
  1754  		Expect(SyncStatusIs(SyncStatusCodeSynced))
  1755  }
  1756  
  1757  func TestSourceNamespaceCanBeMigratedToManagedNamespaceWithoutBeingPrunedOrOutOfSync(t *testing.T) {
  1758  	Given(t).
  1759  		Prune(true).
  1760  		Path("guestbook-with-plain-namespace-manifest").
  1761  		When().
  1762  		PatchFile("guestbook-ui-namespace.yaml", fmt.Sprintf(`[{"op": "replace", "path": "/metadata/name", "value": "%s"}]`, DeploymentNamespace())).
  1763  		CreateApp().
  1764  		Sync().
  1765  		Then().
  1766  		Expect(OperationPhaseIs(OperationSucceeded)).
  1767  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1768  		When().
  1769  		PatchApp(`[{
  1770  				"op": "add",
  1771  				"path": "/spec/syncPolicy",
  1772  				"value": { "prune": true, "syncOptions": ["PrunePropagationPolicy=foreground"], "managedNamespaceMetadata": { "labels": { "foo": "bar" } } }
  1773  				}]`).
  1774  		Sync().
  1775  		Then().
  1776  		Expect(OperationPhaseIs(OperationSucceeded)).
  1777  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1778  		And(func(app *Application) {
  1779  			assert.Equal(t, &ManagedNamespaceMetadata{Labels: map[string]string{"foo": "bar"}}, app.Spec.SyncPolicy.ManagedNamespaceMetadata)
  1780  		}).
  1781  		When().
  1782  		DeleteFile("guestbook-ui-namespace.yaml").
  1783  		Refresh(RefreshTypeHard).
  1784  		Sync().
  1785  		Wait().
  1786  		Then().
  1787  		Expect(OperationPhaseIs(OperationSucceeded)).
  1788  		Expect(SyncStatusIs(SyncStatusCodeSynced))
  1789  }
  1790  
  1791  func TestSelfManagedApps(t *testing.T) {
  1792  
  1793  	Given(t).
  1794  		Path("self-managed-app").
  1795  		When().
  1796  		PatchFile("resources.yaml", fmt.Sprintf(`[{"op": "replace", "path": "/spec/source/repoURL", "value": "%s"}]`, RepoURL(RepoURLTypeFile))).
  1797  		CreateApp().
  1798  		Sync().
  1799  		Then().
  1800  		Expect(OperationPhaseIs(OperationSucceeded)).
  1801  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1802  		And(func(a *Application) {
  1803  			ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
  1804  			defer cancel()
  1805  
  1806  			reconciledCount := 0
  1807  			var lastReconciledAt *metav1.Time
  1808  			for event := range ArgoCDClientset.WatchApplicationWithRetry(ctx, a.Name, a.ResourceVersion) {
  1809  				reconciledAt := event.Application.Status.ReconciledAt
  1810  				if reconciledAt == nil {
  1811  					reconciledAt = &metav1.Time{}
  1812  				}
  1813  				if lastReconciledAt != nil && !lastReconciledAt.Equal(reconciledAt) {
  1814  					reconciledCount = reconciledCount + 1
  1815  				}
  1816  				lastReconciledAt = reconciledAt
  1817  			}
  1818  
  1819  			assert.True(t, reconciledCount < 3, "Application was reconciled too many times")
  1820  		})
  1821  }
  1822  
  1823  func TestExcludedResource(t *testing.T) {
  1824  	Given(t).
  1825  		ResourceOverrides(map[string]ResourceOverride{"apps/Deployment": {Actions: actionsConfig}}).
  1826  		Path(guestbookPath).
  1827  		ResourceFilter(settings.ResourcesFilter{
  1828  			ResourceExclusions: []settings.FilteredResource{{Kinds: []string{kube.DeploymentKind}}},
  1829  		}).
  1830  		When().
  1831  		CreateApp().
  1832  		Sync().
  1833  		Refresh(RefreshTypeNormal).
  1834  		Then().
  1835  		Expect(Condition(ApplicationConditionExcludedResourceWarning, "Resource apps/Deployment guestbook-ui is excluded in the settings"))
  1836  }
  1837  
  1838  func TestRevisionHistoryLimit(t *testing.T) {
  1839  	Given(t).
  1840  		Path("config-map").
  1841  		When().
  1842  		CreateApp().
  1843  		Sync().
  1844  		Then().
  1845  		Expect(OperationPhaseIs(OperationSucceeded)).
  1846  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1847  		And(func(app *Application) {
  1848  			assert.Len(t, app.Status.History, 1)
  1849  		}).
  1850  		When().
  1851  		AppSet("--revision-history-limit", "1").
  1852  		Sync().
  1853  		Then().
  1854  		Expect(OperationPhaseIs(OperationSucceeded)).
  1855  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1856  		And(func(app *Application) {
  1857  			assert.Len(t, app.Status.History, 1)
  1858  		})
  1859  }
  1860  
  1861  func TestOrphanedResource(t *testing.T) {
  1862  	SkipOnEnv(t, "OPENSHIFT")
  1863  	Given(t).
  1864  		ProjectSpec(AppProjectSpec{
  1865  			SourceRepos:       []string{"*"},
  1866  			Destinations:      []ApplicationDestination{{Namespace: "*", Server: "*"}},
  1867  			OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: pointer.Bool(true)},
  1868  		}).
  1869  		Path(guestbookPath).
  1870  		When().
  1871  		CreateApp().
  1872  		Sync().
  1873  		Then().
  1874  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1875  		Expect(NoConditions()).
  1876  		When().
  1877  		And(func() {
  1878  			FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
  1879  				ObjectMeta: metav1.ObjectMeta{
  1880  					Name: "orphaned-configmap",
  1881  				},
  1882  			}, metav1.CreateOptions{}))
  1883  		}).
  1884  		Refresh(RefreshTypeNormal).
  1885  		Then().
  1886  		Expect(Condition(ApplicationConditionOrphanedResourceWarning, "Application has 1 orphaned resources")).
  1887  		And(func(app *Application) {
  1888  			output, err := RunCli("app", "resources", app.Name)
  1889  			assert.NoError(t, err)
  1890  			assert.Contains(t, output, "orphaned-configmap")
  1891  		}).
  1892  		Given().
  1893  		ProjectSpec(AppProjectSpec{
  1894  			SourceRepos:       []string{"*"},
  1895  			Destinations:      []ApplicationDestination{{Namespace: "*", Server: "*"}},
  1896  			OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: pointer.Bool(true), Ignore: []OrphanedResourceKey{{Group: "Test", Kind: "ConfigMap"}}},
  1897  		}).
  1898  		When().
  1899  		Refresh(RefreshTypeNormal).
  1900  		Then().
  1901  		Expect(Condition(ApplicationConditionOrphanedResourceWarning, "Application has 1 orphaned resources")).
  1902  		And(func(app *Application) {
  1903  			output, err := RunCli("app", "resources", app.Name)
  1904  			assert.NoError(t, err)
  1905  			assert.Contains(t, output, "orphaned-configmap")
  1906  		}).
  1907  		Given().
  1908  		ProjectSpec(AppProjectSpec{
  1909  			SourceRepos:       []string{"*"},
  1910  			Destinations:      []ApplicationDestination{{Namespace: "*", Server: "*"}},
  1911  			OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: pointer.Bool(true), Ignore: []OrphanedResourceKey{{Kind: "ConfigMap"}}},
  1912  		}).
  1913  		When().
  1914  		Refresh(RefreshTypeNormal).
  1915  		Then().
  1916  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1917  		Expect(NoConditions()).
  1918  		And(func(app *Application) {
  1919  			output, err := RunCli("app", "resources", app.Name)
  1920  			assert.NoError(t, err)
  1921  			assert.NotContains(t, output, "orphaned-configmap")
  1922  		}).
  1923  		Given().
  1924  		ProjectSpec(AppProjectSpec{
  1925  			SourceRepos:       []string{"*"},
  1926  			Destinations:      []ApplicationDestination{{Namespace: "*", Server: "*"}},
  1927  			OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: pointer.Bool(true), Ignore: []OrphanedResourceKey{{Kind: "ConfigMap", Name: "orphaned-configmap"}}},
  1928  		}).
  1929  		When().
  1930  		Refresh(RefreshTypeNormal).
  1931  		Then().
  1932  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1933  		Expect(NoConditions()).
  1934  		And(func(app *Application) {
  1935  			output, err := RunCli("app", "resources", app.Name)
  1936  			assert.NoError(t, err)
  1937  			assert.NotContains(t, output, "orphaned-configmap")
  1938  		}).
  1939  		Given().
  1940  		ProjectSpec(AppProjectSpec{
  1941  			SourceRepos:       []string{"*"},
  1942  			Destinations:      []ApplicationDestination{{Namespace: "*", Server: "*"}},
  1943  			OrphanedResources: nil,
  1944  		}).
  1945  		When().
  1946  		Refresh(RefreshTypeNormal).
  1947  		Then().
  1948  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  1949  		Expect(NoConditions())
  1950  }
  1951  
  1952  func TestNotPermittedResources(t *testing.T) {
  1953  	ctx := Given(t)
  1954  
  1955  	pathType := networkingv1.PathTypePrefix
  1956  	ingress := &networkingv1.Ingress{
  1957  		ObjectMeta: metav1.ObjectMeta{
  1958  			Name: "sample-ingress",
  1959  			Labels: map[string]string{
  1960  				common.LabelKeyAppInstance: ctx.GetName(),
  1961  			},
  1962  		},
  1963  		Spec: networkingv1.IngressSpec{
  1964  			Rules: []networkingv1.IngressRule{{
  1965  				IngressRuleValue: networkingv1.IngressRuleValue{
  1966  					HTTP: &networkingv1.HTTPIngressRuleValue{
  1967  						Paths: []networkingv1.HTTPIngressPath{{
  1968  							Path: "/",
  1969  							Backend: networkingv1.IngressBackend{
  1970  								Service: &networkingv1.IngressServiceBackend{
  1971  									Name: "guestbook-ui",
  1972  									Port: networkingv1.ServiceBackendPort{Number: 80},
  1973  								},
  1974  							},
  1975  							PathType: &pathType,
  1976  						}},
  1977  					},
  1978  				},
  1979  			}},
  1980  		},
  1981  	}
  1982  	defer func() {
  1983  		log.Infof("Ingress 'sample-ingress' deleted from %s", TestNamespace())
  1984  		CheckError(KubeClientset.NetworkingV1().Ingresses(TestNamespace()).Delete(context.Background(), "sample-ingress", metav1.DeleteOptions{}))
  1985  	}()
  1986  
  1987  	svc := &v1.Service{
  1988  		ObjectMeta: metav1.ObjectMeta{
  1989  			Name: "guestbook-ui",
  1990  			Labels: map[string]string{
  1991  				common.LabelKeyAppInstance: ctx.GetName(),
  1992  			},
  1993  		},
  1994  		Spec: v1.ServiceSpec{
  1995  			Ports: []v1.ServicePort{{
  1996  				Port:       80,
  1997  				TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: 80},
  1998  			}},
  1999  			Selector: map[string]string{
  2000  				"app": "guestbook-ui",
  2001  			},
  2002  		},
  2003  	}
  2004  
  2005  	ctx.ProjectSpec(AppProjectSpec{
  2006  		SourceRepos:  []string{"*"},
  2007  		Destinations: []ApplicationDestination{{Namespace: DeploymentNamespace(), Server: "*"}},
  2008  		NamespaceResourceBlacklist: []metav1.GroupKind{
  2009  			{Group: "", Kind: "Service"},
  2010  		}}).
  2011  		And(func() {
  2012  			FailOnErr(KubeClientset.NetworkingV1().Ingresses(TestNamespace()).Create(context.Background(), ingress, metav1.CreateOptions{}))
  2013  			FailOnErr(KubeClientset.CoreV1().Services(DeploymentNamespace()).Create(context.Background(), svc, metav1.CreateOptions{}))
  2014  		}).
  2015  		Path(guestbookPath).
  2016  		When().
  2017  		CreateApp().
  2018  		Then().
  2019  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
  2020  		And(func(app *Application) {
  2021  			statusByKind := make(map[string]ResourceStatus)
  2022  			for _, res := range app.Status.Resources {
  2023  				statusByKind[res.Kind] = res
  2024  			}
  2025  			_, hasIngress := statusByKind[kube.IngressKind]
  2026  			assert.False(t, hasIngress, "Ingress is prohibited not managed object and should be even visible to user")
  2027  			serviceStatus := statusByKind[kube.ServiceKind]
  2028  			assert.Equal(t, serviceStatus.Status, SyncStatusCodeUnknown, "Service is prohibited managed resource so should be set to Unknown")
  2029  			deploymentStatus := statusByKind[kube.DeploymentKind]
  2030  			assert.Equal(t, deploymentStatus.Status, SyncStatusCodeOutOfSync)
  2031  		}).
  2032  		When().
  2033  		Delete(true).
  2034  		Then().
  2035  		Expect(DoesNotExist())
  2036  
  2037  	// Make sure prohibited resources are not deleted during application deletion
  2038  	FailOnErr(KubeClientset.NetworkingV1().Ingresses(TestNamespace()).Get(context.Background(), "sample-ingress", metav1.GetOptions{}))
  2039  	FailOnErr(KubeClientset.CoreV1().Services(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{}))
  2040  }
  2041  
  2042  func TestSyncWithInfos(t *testing.T) {
  2043  	expectedInfo := make([]*Info, 2)
  2044  	expectedInfo[0] = &Info{Name: "name1", Value: "val1"}
  2045  	expectedInfo[1] = &Info{Name: "name2", Value: "val2"}
  2046  
  2047  	Given(t).
  2048  		Path(guestbookPath).
  2049  		When().
  2050  		CreateApp().
  2051  		Then().
  2052  		And(func(app *Application) {
  2053  			_, err := RunCli("app", "sync", app.Name,
  2054  				"--info", fmt.Sprintf("%s=%s", expectedInfo[0].Name, expectedInfo[0].Value),
  2055  				"--info", fmt.Sprintf("%s=%s", expectedInfo[1].Name, expectedInfo[1].Value))
  2056  			assert.NoError(t, err)
  2057  		}).
  2058  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2059  		And(func(app *Application) {
  2060  			assert.ElementsMatch(t, app.Status.OperationState.Operation.Info, expectedInfo)
  2061  		})
  2062  }
  2063  
  2064  // Given: argocd app create does not provide --dest-namespace
  2065  //
  2066  //	Manifest contains resource console which does not require namespace
  2067  //
  2068  // Expect: no app.Status.Conditions
  2069  func TestCreateAppWithNoNameSpaceForGlobalResource(t *testing.T) {
  2070  	Given(t).
  2071  		Path(globalWithNoNameSpace).
  2072  		When().
  2073  		CreateWithNoNameSpace().
  2074  		Then().
  2075  		And(func(app *Application) {
  2076  			time.Sleep(500 * time.Millisecond)
  2077  			app, err := AppClientset.ArgoprojV1alpha1().Applications(TestNamespace()).Get(context.Background(), app.Name, metav1.GetOptions{})
  2078  			assert.NoError(t, err)
  2079  			assert.Len(t, app.Status.Conditions, 0)
  2080  		})
  2081  }
  2082  
  2083  // Given: argocd app create does not provide --dest-namespace
  2084  //
  2085  //	Manifest contains resource deployment, and service which requires namespace
  2086  //	Deployment and service do not have namespace in manifest
  2087  //
  2088  // Expect: app.Status.Conditions for deployment ans service which does not have namespace in manifest
  2089  func TestCreateAppWithNoNameSpaceWhenRequired(t *testing.T) {
  2090  	Given(t).
  2091  		Path(guestbookPath).
  2092  		When().
  2093  		CreateWithNoNameSpace().
  2094  		Refresh(RefreshTypeNormal).
  2095  		Then().
  2096  		And(func(app *Application) {
  2097  			updatedApp, err := AppClientset.ArgoprojV1alpha1().Applications(TestNamespace()).Get(context.Background(), app.Name, metav1.GetOptions{})
  2098  			require.NoError(t, err)
  2099  
  2100  			assert.Len(t, updatedApp.Status.Conditions, 2)
  2101  			assert.Equal(t, updatedApp.Status.Conditions[0].Type, ApplicationConditionInvalidSpecError)
  2102  			assert.Equal(t, updatedApp.Status.Conditions[1].Type, ApplicationConditionInvalidSpecError)
  2103  		})
  2104  }
  2105  
  2106  // Given: argocd app create does not provide --dest-namespace
  2107  //
  2108  //	Manifest contains resource deployment, and service which requires namespace
  2109  //	Some deployment and service has namespace in manifest
  2110  //	Some deployment and service does not have namespace in manifest
  2111  //
  2112  // Expect: app.Status.Conditions for deployment and service which does not have namespace in manifest
  2113  func TestCreateAppWithNoNameSpaceWhenRequired2(t *testing.T) {
  2114  	Given(t).
  2115  		Path(guestbookWithNamespace).
  2116  		When().
  2117  		CreateWithNoNameSpace().
  2118  		Refresh(RefreshTypeNormal).
  2119  		Then().
  2120  		And(func(app *Application) {
  2121  			updatedApp, err := AppClientset.ArgoprojV1alpha1().Applications(TestNamespace()).Get(context.Background(), app.Name, metav1.GetOptions{})
  2122  			require.NoError(t, err)
  2123  
  2124  			assert.Len(t, updatedApp.Status.Conditions, 2)
  2125  			assert.Equal(t, updatedApp.Status.Conditions[0].Type, ApplicationConditionInvalidSpecError)
  2126  			assert.Equal(t, updatedApp.Status.Conditions[1].Type, ApplicationConditionInvalidSpecError)
  2127  		})
  2128  }
  2129  
  2130  func TestListResource(t *testing.T) {
  2131  	SkipOnEnv(t, "OPENSHIFT")
  2132  	Given(t).
  2133  		ProjectSpec(AppProjectSpec{
  2134  			SourceRepos:       []string{"*"},
  2135  			Destinations:      []ApplicationDestination{{Namespace: "*", Server: "*"}},
  2136  			OrphanedResources: &OrphanedResourcesMonitorSettings{Warn: pointer.Bool(true)},
  2137  		}).
  2138  		Path(guestbookPath).
  2139  		When().
  2140  		CreateApp().
  2141  		Sync().
  2142  		Then().
  2143  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2144  		Expect(NoConditions()).
  2145  		When().
  2146  		And(func() {
  2147  			FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
  2148  				ObjectMeta: metav1.ObjectMeta{
  2149  					Name: "orphaned-configmap",
  2150  				},
  2151  			}, metav1.CreateOptions{}))
  2152  		}).
  2153  		Refresh(RefreshTypeNormal).
  2154  		Then().
  2155  		Expect(Condition(ApplicationConditionOrphanedResourceWarning, "Application has 1 orphaned resources")).
  2156  		And(func(app *Application) {
  2157  			output, err := RunCli("app", "resources", app.Name)
  2158  			assert.NoError(t, err)
  2159  			assert.Contains(t, output, "orphaned-configmap")
  2160  			assert.Contains(t, output, "guestbook-ui")
  2161  		}).
  2162  		And(func(app *Application) {
  2163  			output, err := RunCli("app", "resources", app.Name, "--orphaned=true")
  2164  			assert.NoError(t, err)
  2165  			assert.Contains(t, output, "orphaned-configmap")
  2166  			assert.NotContains(t, output, "guestbook-ui")
  2167  		}).
  2168  		And(func(app *Application) {
  2169  			output, err := RunCli("app", "resources", app.Name, "--orphaned=false")
  2170  			assert.NoError(t, err)
  2171  			assert.NotContains(t, output, "orphaned-configmap")
  2172  			assert.Contains(t, output, "guestbook-ui")
  2173  		}).
  2174  		Given().
  2175  		ProjectSpec(AppProjectSpec{
  2176  			SourceRepos:       []string{"*"},
  2177  			Destinations:      []ApplicationDestination{{Namespace: "*", Server: "*"}},
  2178  			OrphanedResources: nil,
  2179  		}).
  2180  		When().
  2181  		Refresh(RefreshTypeNormal).
  2182  		Then().
  2183  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2184  		Expect(NoConditions())
  2185  }
  2186  
  2187  // Given application is set with --sync-option CreateNamespace=true
  2188  //
  2189  //	application --dest-namespace does not exist
  2190  //
  2191  // Verity application --dest-namespace is created
  2192  //
  2193  //	application sync successful
  2194  //	when application is deleted, --dest-namespace is not deleted
  2195  func TestNamespaceAutoCreation(t *testing.T) {
  2196  	SkipOnEnv(t, "OPENSHIFT")
  2197  	updatedNamespace := getNewNamespace(t)
  2198  	defer func() {
  2199  		if !t.Skipped() {
  2200  			_, err := Run("", "kubectl", "delete", "namespace", updatedNamespace)
  2201  			assert.NoError(t, err)
  2202  		}
  2203  	}()
  2204  	Given(t).
  2205  		Timeout(30).
  2206  		Path("guestbook").
  2207  		When().
  2208  		CreateApp("--sync-option", "CreateNamespace=true").
  2209  		Then().
  2210  		And(func(app *Application) {
  2211  			// Make sure the namespace we are about to update to does not exist
  2212  			_, err := Run("", "kubectl", "get", "namespace", updatedNamespace)
  2213  			assert.Error(t, err)
  2214  			assert.Contains(t, err.Error(), "not found")
  2215  		}).
  2216  		When().
  2217  		AppSet("--dest-namespace", updatedNamespace).
  2218  		Sync().
  2219  		Then().
  2220  		Expect(Success("")).
  2221  		Expect(OperationPhaseIs(OperationSucceeded)).Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, health.HealthStatusHealthy)).
  2222  		Expect(ResourceHealthWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, health.HealthStatusHealthy)).
  2223  		Expect(ResourceSyncStatusWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, SyncStatusCodeSynced)).
  2224  		Expect(ResourceSyncStatusWithNamespaceIs("Deployment", "guestbook-ui", updatedNamespace, SyncStatusCodeSynced)).
  2225  		When().
  2226  		Delete(true).
  2227  		Then().
  2228  		Expect(Success("")).
  2229  		And(func(app *Application) {
  2230  			// Verify delete app does not delete the namespace auto created
  2231  			output, err := Run("", "kubectl", "get", "namespace", updatedNamespace)
  2232  			assert.NoError(t, err)
  2233  			assert.Contains(t, output, updatedNamespace)
  2234  		})
  2235  }
  2236  
  2237  func TestFailedSyncWithRetry(t *testing.T) {
  2238  	Given(t).
  2239  		Path("hook").
  2240  		When().
  2241  		PatchFile("hook.yaml", `[{"op": "replace", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/hook": "PreSync"}}]`).
  2242  		// make hook fail
  2243  		PatchFile("hook.yaml", `[{"op": "replace", "path": "/spec/containers/0/command", "value": ["false"]}]`).
  2244  		CreateApp().
  2245  		IgnoreErrors().
  2246  		Sync("--retry-limit=1", "--retry-backoff-duration=1s").
  2247  		Then().
  2248  		Expect(OperationPhaseIs(OperationFailed)).
  2249  		Expect(OperationMessageContains("retried 1 times"))
  2250  }
  2251  
  2252  func TestCreateDisableValidation(t *testing.T) {
  2253  	Given(t).
  2254  		Path("baddir").
  2255  		When().
  2256  		CreateApp("--validate=false").
  2257  		Then().
  2258  		And(func(app *Application) {
  2259  			_, err := RunCli("app", "create", app.Name, "--upsert", "--validate=false", "--repo", RepoURL(RepoURLTypeFile),
  2260  				"--path", "baddir2", "--project", app.Spec.Project, "--dest-server", KubernetesInternalAPIServerAddr, "--dest-namespace", DeploymentNamespace())
  2261  			assert.NoError(t, err)
  2262  		}).
  2263  		When().
  2264  		AppSet("--path", "baddir3", "--validate=false")
  2265  
  2266  }
  2267  
  2268  func TestCreateFromPartialFile(t *testing.T) {
  2269  	partialApp :=
  2270  		`metadata:
  2271    labels:
  2272      labels.local/from-file: file
  2273      labels.local/from-args: file
  2274    annotations:
  2275      annotations.local/from-file: file
  2276    finalizers:
  2277    - resources-finalizer.argocd.argoproj.io
  2278  spec:
  2279    syncPolicy:
  2280      automated:
  2281        prune: true
  2282  `
  2283  
  2284  	path := "helm-values"
  2285  	Given(t).
  2286  		When().
  2287  		// app should be auto-synced once created
  2288  		CreateFromPartialFile(partialApp, "--path", path, "-l", "labels.local/from-args=args", "--helm-set", "foo=foo").
  2289  		Then().
  2290  		Expect(Success("")).
  2291  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2292  		Expect(NoConditions()).
  2293  		And(func(app *Application) {
  2294  			assert.Equal(t, map[string]string{"labels.local/from-file": "file", "labels.local/from-args": "args"}, app.ObjectMeta.Labels)
  2295  			assert.Equal(t, map[string]string{"annotations.local/from-file": "file"}, app.ObjectMeta.Annotations)
  2296  			assert.Equal(t, []string{"resources-finalizer.argocd.argoproj.io"}, app.ObjectMeta.Finalizers)
  2297  			assert.Equal(t, path, app.Spec.GetSource().Path)
  2298  			assert.Equal(t, []HelmParameter{{Name: "foo", Value: "foo"}}, app.Spec.GetSource().Helm.Parameters)
  2299  		})
  2300  }
  2301  
  2302  // Ensure actions work when using a resource action that modifies status and/or spec
  2303  func TestCRDStatusSubresourceAction(t *testing.T) {
  2304  	actions := `
  2305  discovery.lua: |
  2306    actions = {}
  2307    actions["update-spec"] = {["disabled"] = false}
  2308    actions["update-status"] = {["disabled"] = false}
  2309    actions["update-both"] = {["disabled"] = false}
  2310    return actions
  2311  definitions:
  2312  - name: update-both
  2313    action.lua: |
  2314      obj.spec = {}
  2315      obj.spec.foo = "update-both"
  2316      obj.status = {}
  2317      obj.status.bar = "update-both"
  2318      return obj
  2319  - name: update-spec
  2320    action.lua: |
  2321      obj.spec = {}
  2322      obj.spec.foo = "update-spec"
  2323      return obj
  2324  - name: update-status
  2325    action.lua: |
  2326      obj.status = {}
  2327      obj.status.bar = "update-status"
  2328      return obj
  2329  `
  2330  	Given(t).
  2331  		Path("crd-subresource").
  2332  		And(func() {
  2333  			SetResourceOverrides(map[string]ResourceOverride{
  2334  				"argoproj.io/StatusSubResource": {
  2335  					Actions: actions,
  2336  				},
  2337  				"argoproj.io/NonStatusSubResource": {
  2338  					Actions: actions,
  2339  				},
  2340  			})
  2341  		}).
  2342  		When().CreateApp().Sync().Then().
  2343  		Expect(OperationPhaseIs(OperationSucceeded)).Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2344  		When().
  2345  		Refresh(RefreshTypeNormal).
  2346  		Then().
  2347  		// tests resource actions on a CRD using status subresource
  2348  		And(func(app *Application) {
  2349  			_, err := RunCli("app", "actions", "run", app.Name, "--kind", "StatusSubResource", "update-both")
  2350  			assert.NoError(t, err)
  2351  			text := FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "statussubresources", "status-subresource", "-o", "jsonpath={.spec.foo}")).(string)
  2352  			assert.Equal(t, "update-both", text)
  2353  			text = FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "statussubresources", "status-subresource", "-o", "jsonpath={.status.bar}")).(string)
  2354  			assert.Equal(t, "update-both", text)
  2355  
  2356  			_, err = RunCli("app", "actions", "run", app.Name, "--kind", "StatusSubResource", "update-spec")
  2357  			assert.NoError(t, err)
  2358  			text = FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "statussubresources", "status-subresource", "-o", "jsonpath={.spec.foo}")).(string)
  2359  			assert.Equal(t, "update-spec", text)
  2360  
  2361  			_, err = RunCli("app", "actions", "run", app.Name, "--kind", "StatusSubResource", "update-status")
  2362  			assert.NoError(t, err)
  2363  			text = FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "statussubresources", "status-subresource", "-o", "jsonpath={.status.bar}")).(string)
  2364  			assert.Equal(t, "update-status", text)
  2365  		}).
  2366  		// tests resource actions on a CRD *not* using status subresource
  2367  		And(func(app *Application) {
  2368  			_, err := RunCli("app", "actions", "run", app.Name, "--kind", "NonStatusSubResource", "update-both")
  2369  			assert.NoError(t, err)
  2370  			text := FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "nonstatussubresources", "non-status-subresource", "-o", "jsonpath={.spec.foo}")).(string)
  2371  			assert.Equal(t, "update-both", text)
  2372  			text = FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "nonstatussubresources", "non-status-subresource", "-o", "jsonpath={.status.bar}")).(string)
  2373  			assert.Equal(t, "update-both", text)
  2374  
  2375  			_, err = RunCli("app", "actions", "run", app.Name, "--kind", "NonStatusSubResource", "update-spec")
  2376  			assert.NoError(t, err)
  2377  			text = FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "nonstatussubresources", "non-status-subresource", "-o", "jsonpath={.spec.foo}")).(string)
  2378  			assert.Equal(t, "update-spec", text)
  2379  
  2380  			_, err = RunCli("app", "actions", "run", app.Name, "--kind", "NonStatusSubResource", "update-status")
  2381  			assert.NoError(t, err)
  2382  			text = FailOnErr(Run(".", "kubectl", "-n", app.Spec.Destination.Namespace, "get", "nonstatussubresources", "non-status-subresource", "-o", "jsonpath={.status.bar}")).(string)
  2383  			assert.Equal(t, "update-status", text)
  2384  		})
  2385  }
  2386  
  2387  func TestAppLogs(t *testing.T) {
  2388  	SkipOnEnv(t, "OPENSHIFT")
  2389  	Given(t).
  2390  		Path("guestbook-logs").
  2391  		When().
  2392  		CreateApp().
  2393  		Sync().
  2394  		Then().
  2395  		Expect(HealthIs(health.HealthStatusHealthy)).
  2396  		And(func(app *Application) {
  2397  			out, err := RunCliWithRetry(appLogsRetryCount, "app", "logs", app.Name, "--kind", "Deployment", "--group", "", "--name", "guestbook-ui")
  2398  			assert.NoError(t, err)
  2399  			assert.Contains(t, out, "Hi")
  2400  		}).
  2401  		And(func(app *Application) {
  2402  			out, err := RunCliWithRetry(appLogsRetryCount, "app", "logs", app.Name, "--kind", "Pod")
  2403  			assert.NoError(t, err)
  2404  			assert.Contains(t, out, "Hi")
  2405  		}).
  2406  		And(func(app *Application) {
  2407  			out, err := RunCliWithRetry(appLogsRetryCount, "app", "logs", app.Name, "--kind", "Service")
  2408  			assert.NoError(t, err)
  2409  			assert.NotContains(t, out, "Hi")
  2410  		})
  2411  }
  2412  
  2413  func TestAppWaitOperationInProgress(t *testing.T) {
  2414  	ctx := Given(t)
  2415  	ctx.
  2416  		And(func() {
  2417  			SetResourceOverrides(map[string]ResourceOverride{
  2418  				"batch/Job": {
  2419  					HealthLua: `return { status = 'Running' }`,
  2420  				},
  2421  				"apps/Deployment": {
  2422  					HealthLua: `return { status = 'Suspended' }`,
  2423  				},
  2424  			})
  2425  		}).
  2426  		Async(true).
  2427  		Path("hook-and-deployment").
  2428  		When().
  2429  		CreateApp().
  2430  		Sync().
  2431  		Then().
  2432  		// stuck in running state
  2433  		Expect(OperationPhaseIs(OperationRunning)).
  2434  		When().
  2435  		And(func() {
  2436  			_, err := RunCli("app", "wait", ctx.AppName(), "--suspended")
  2437  			errors.CheckError(err)
  2438  		})
  2439  }
  2440  
  2441  func TestSyncOptionReplace(t *testing.T) {
  2442  	Given(t).
  2443  		Path("config-map").
  2444  		When().
  2445  		PatchFile("config-map.yaml", `[{"op": "add", "path": "/metadata/annotations", "value": {"argocd.argoproj.io/sync-options": "Replace=true"}}]`).
  2446  		CreateApp().
  2447  		Sync().
  2448  		Then().
  2449  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2450  		And(func(app *Application) {
  2451  			assert.Equal(t, app.Status.OperationState.SyncResult.Resources[0].Message, "configmap/my-map created")
  2452  		}).
  2453  		When().
  2454  		Sync().
  2455  		Then().
  2456  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2457  		And(func(app *Application) {
  2458  			assert.Equal(t, app.Status.OperationState.SyncResult.Resources[0].Message, "configmap/my-map replaced")
  2459  		})
  2460  }
  2461  
  2462  func TestSyncOptionReplaceFromCLI(t *testing.T) {
  2463  	Given(t).
  2464  		Path("config-map").
  2465  		Replace().
  2466  		When().
  2467  		CreateApp().
  2468  		Sync().
  2469  		Then().
  2470  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2471  		And(func(app *Application) {
  2472  			assert.Equal(t, app.Status.OperationState.SyncResult.Resources[0].Message, "configmap/my-map created")
  2473  		}).
  2474  		When().
  2475  		Sync().
  2476  		Then().
  2477  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2478  		And(func(app *Application) {
  2479  			assert.Equal(t, app.Status.OperationState.SyncResult.Resources[0].Message, "configmap/my-map replaced")
  2480  		})
  2481  }
  2482  
  2483  func TestDiscoverNewCommit(t *testing.T) {
  2484  	var sha string
  2485  	Given(t).
  2486  		Path("config-map").
  2487  		When().
  2488  		CreateApp().
  2489  		Sync().
  2490  		Then().
  2491  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2492  		And(func(app *Application) {
  2493  			sha = app.Status.Sync.Revision
  2494  			assert.NotEmpty(t, sha)
  2495  		}).
  2496  		When().
  2497  		PatchFile("config-map.yaml", `[{"op": "replace", "path": "/data/foo", "value": "hello"}]`).
  2498  		Then().
  2499  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2500  		// make sure new commit is not discovered immediately after push
  2501  		And(func(app *Application) {
  2502  			assert.Equal(t, sha, app.Status.Sync.Revision)
  2503  		}).
  2504  		When().
  2505  		// make sure new commit is not discovered after refresh is requested
  2506  		Refresh(RefreshTypeNormal).
  2507  		Then().
  2508  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
  2509  		And(func(app *Application) {
  2510  			assert.NotEqual(t, sha, app.Status.Sync.Revision)
  2511  		})
  2512  }
  2513  
  2514  func TestDisableManifestGeneration(t *testing.T) {
  2515  	Given(t).
  2516  		Path("guestbook").
  2517  		When().
  2518  		CreateApp().
  2519  		Refresh(RefreshTypeHard).
  2520  		Then().
  2521  		And(func(app *Application) {
  2522  			assert.Equal(t, app.Status.SourceType, ApplicationSourceTypeKustomize)
  2523  		}).
  2524  		When().
  2525  		And(func() {
  2526  			SetEnableManifestGeneration(map[ApplicationSourceType]bool{
  2527  				ApplicationSourceTypeKustomize: false,
  2528  			})
  2529  		}).
  2530  		Refresh(RefreshTypeHard).
  2531  		Then().
  2532  		And(func(app *Application) {
  2533  			time.Sleep(1 * time.Second)
  2534  		}).
  2535  		And(func(app *Application) {
  2536  			assert.Equal(t, app.Status.SourceType, ApplicationSourceTypeDirectory)
  2537  		})
  2538  }
  2539  
  2540  func TestSwitchTrackingMethod(t *testing.T) {
  2541  	ctx := Given(t)
  2542  
  2543  	ctx.
  2544  		SetTrackingMethod(string(argo.TrackingMethodAnnotation)).
  2545  		Path("deployment").
  2546  		When().
  2547  		CreateApp().
  2548  		Sync().
  2549  		Refresh(RefreshTypeNormal).
  2550  		Then().
  2551  		Expect(OperationPhaseIs(OperationSucceeded)).
  2552  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2553  		Expect(HealthIs(health.HealthStatusHealthy)).
  2554  		When().
  2555  		And(func() {
  2556  			// Add resource with tracking annotation. This should put the
  2557  			// application OutOfSync.
  2558  			FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
  2559  				ObjectMeta: metav1.ObjectMeta{
  2560  					Name: "other-configmap",
  2561  					Annotations: map[string]string{
  2562  						common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/other-configmap", Name(), DeploymentNamespace()),
  2563  					},
  2564  				},
  2565  			}, metav1.CreateOptions{}))
  2566  		}).
  2567  		Then().
  2568  		Expect(OperationPhaseIs(OperationSucceeded)).
  2569  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
  2570  		Expect(HealthIs(health.HealthStatusHealthy)).
  2571  		When().
  2572  		And(func() {
  2573  			// Delete resource to bring application back in sync
  2574  			FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "other-configmap", metav1.DeleteOptions{}))
  2575  		}).
  2576  		Then().
  2577  		Expect(OperationPhaseIs(OperationSucceeded)).
  2578  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2579  		Expect(HealthIs(health.HealthStatusHealthy)).
  2580  		When().
  2581  		SetTrackingMethod(string(argo.TrackingMethodLabel)).
  2582  		Sync().
  2583  		Then().
  2584  		Expect(OperationPhaseIs(OperationSucceeded)).
  2585  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2586  		Expect(HealthIs(health.HealthStatusHealthy)).
  2587  		When().
  2588  		And(func() {
  2589  			// Add a resource with a tracking annotation. This should not
  2590  			// affect the application, because we now use the tracking method
  2591  			// "label".
  2592  			FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
  2593  				ObjectMeta: metav1.ObjectMeta{
  2594  					Name: "other-configmap",
  2595  					Annotations: map[string]string{
  2596  						common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/other-configmap", Name(), DeploymentNamespace()),
  2597  					},
  2598  				},
  2599  			}, metav1.CreateOptions{}))
  2600  		}).
  2601  		Then().
  2602  		Expect(OperationPhaseIs(OperationSucceeded)).
  2603  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2604  		Expect(HealthIs(health.HealthStatusHealthy)).
  2605  		When().
  2606  		And(func() {
  2607  			// Add a resource with the tracking label. The app should become
  2608  			// OutOfSync.
  2609  			FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
  2610  				ObjectMeta: metav1.ObjectMeta{
  2611  					Name: "extra-configmap",
  2612  					Labels: map[string]string{
  2613  						common.LabelKeyAppInstance: Name(),
  2614  					},
  2615  				},
  2616  			}, metav1.CreateOptions{}))
  2617  		}).
  2618  		Then().
  2619  		Expect(OperationPhaseIs(OperationSucceeded)).
  2620  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
  2621  		Expect(HealthIs(health.HealthStatusHealthy)).
  2622  		When().
  2623  		And(func() {
  2624  			// Delete resource to bring application back in sync
  2625  			FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "extra-configmap", metav1.DeleteOptions{}))
  2626  		}).
  2627  		Then().
  2628  		Expect(OperationPhaseIs(OperationSucceeded)).
  2629  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2630  		Expect(HealthIs(health.HealthStatusHealthy))
  2631  }
  2632  
  2633  func TestSwitchTrackingLabel(t *testing.T) {
  2634  	ctx := Given(t)
  2635  
  2636  	ctx.
  2637  		Path("deployment").
  2638  		When().
  2639  		CreateApp().
  2640  		Sync().
  2641  		Refresh(RefreshTypeNormal).
  2642  		Then().
  2643  		Expect(OperationPhaseIs(OperationSucceeded)).
  2644  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2645  		Expect(HealthIs(health.HealthStatusHealthy)).
  2646  		When().
  2647  		And(func() {
  2648  			// Add extra resource that carries the default tracking label
  2649  			// We expect the app to go out of sync.
  2650  			FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
  2651  				ObjectMeta: metav1.ObjectMeta{
  2652  					Name: "other-configmap",
  2653  					Labels: map[string]string{
  2654  						common.LabelKeyAppInstance: Name(),
  2655  					},
  2656  				},
  2657  			}, metav1.CreateOptions{}))
  2658  		}).
  2659  		Then().
  2660  		Expect(OperationPhaseIs(OperationSucceeded)).
  2661  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
  2662  		Expect(HealthIs(health.HealthStatusHealthy)).
  2663  		When().
  2664  		And(func() {
  2665  			// Delete resource to bring application back in sync
  2666  			FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "other-configmap", metav1.DeleteOptions{}))
  2667  		}).
  2668  		Then().
  2669  		Expect(OperationPhaseIs(OperationSucceeded)).
  2670  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2671  		Expect(HealthIs(health.HealthStatusHealthy)).
  2672  		When().
  2673  		// Change tracking label
  2674  		SetTrackingLabel("argocd.tracking").
  2675  		Sync().
  2676  		Then().
  2677  		Expect(OperationPhaseIs(OperationSucceeded)).
  2678  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2679  		Expect(HealthIs(health.HealthStatusHealthy)).
  2680  		When().
  2681  		And(func() {
  2682  			// Create resource with the new tracking label, the application
  2683  			// is expected to go out of sync
  2684  			FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
  2685  				ObjectMeta: metav1.ObjectMeta{
  2686  					Name: "other-configmap",
  2687  					Labels: map[string]string{
  2688  						"argocd.tracking": Name(),
  2689  					},
  2690  				},
  2691  			}, metav1.CreateOptions{}))
  2692  		}).
  2693  		Then().
  2694  		Expect(OperationPhaseIs(OperationSucceeded)).
  2695  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
  2696  		Expect(HealthIs(health.HealthStatusHealthy)).
  2697  		When().
  2698  		And(func() {
  2699  			// Delete resource to bring application back in sync
  2700  			FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "other-configmap", metav1.DeleteOptions{}))
  2701  		}).
  2702  		Then().
  2703  		Expect(OperationPhaseIs(OperationSucceeded)).
  2704  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2705  		Expect(HealthIs(health.HealthStatusHealthy)).
  2706  		When().
  2707  		And(func() {
  2708  			// Add extra resource that carries the default tracking label
  2709  			// We expect the app to stay in sync, because the configured
  2710  			// label is different.
  2711  			FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
  2712  				ObjectMeta: metav1.ObjectMeta{
  2713  					Name: "other-configmap",
  2714  					Labels: map[string]string{
  2715  						common.LabelKeyAppInstance: Name(),
  2716  					},
  2717  				},
  2718  			}, metav1.CreateOptions{}))
  2719  		}).
  2720  		Then().
  2721  		Expect(OperationPhaseIs(OperationSucceeded)).
  2722  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2723  		Expect(HealthIs(health.HealthStatusHealthy))
  2724  }
  2725  
  2726  func TestAnnotationTrackingExtraResources(t *testing.T) {
  2727  	ctx := Given(t)
  2728  
  2729  	SetTrackingMethod(string(argo.TrackingMethodAnnotation))
  2730  	ctx.
  2731  		Path("deployment").
  2732  		When().
  2733  		CreateApp().
  2734  		Sync().
  2735  		Refresh(RefreshTypeNormal).
  2736  		Then().
  2737  		Expect(OperationPhaseIs(OperationSucceeded)).
  2738  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2739  		Expect(HealthIs(health.HealthStatusHealthy)).
  2740  		When().
  2741  		And(func() {
  2742  			// Add a resource with an annotation that is not referencing the
  2743  			// resource.
  2744  			FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
  2745  				ObjectMeta: metav1.ObjectMeta{
  2746  					Name: "extra-configmap",
  2747  					Annotations: map[string]string{
  2748  						common.AnnotationKeyAppInstance: fmt.Sprintf("%s:apps/Deployment:%s/guestbook-cm", Name(), DeploymentNamespace()),
  2749  					},
  2750  				},
  2751  			}, metav1.CreateOptions{}))
  2752  		}).
  2753  		Refresh(RefreshTypeNormal).
  2754  		Then().
  2755  		Expect(OperationPhaseIs(OperationSucceeded)).
  2756  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2757  		Expect(HealthIs(health.HealthStatusHealthy)).
  2758  		When().
  2759  		Sync("--prune").
  2760  		And(func() {
  2761  			// The extra configmap must not be pruned, because it's not tracked
  2762  			cm, err := KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Get(context.Background(), "extra-configmap", metav1.GetOptions{})
  2763  			require.NoError(t, err)
  2764  			require.Equal(t, "extra-configmap", cm.Name)
  2765  		}).
  2766  		And(func() {
  2767  			// Add a resource with an annotation that is self-referencing the
  2768  			// resource.
  2769  			FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
  2770  				ObjectMeta: metav1.ObjectMeta{
  2771  					Name: "other-configmap",
  2772  					Annotations: map[string]string{
  2773  						common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/other-configmap", Name(), DeploymentNamespace()),
  2774  					},
  2775  				},
  2776  			}, metav1.CreateOptions{}))
  2777  		}).
  2778  		Refresh(RefreshTypeNormal).
  2779  		Then().
  2780  		Expect(OperationPhaseIs(OperationSucceeded)).
  2781  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
  2782  		Expect(HealthIs(health.HealthStatusHealthy)).
  2783  		When().
  2784  		Sync("--prune").
  2785  		And(func() {
  2786  			// The extra configmap must be pruned now, because it's tracked
  2787  			cm, err := KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Get(context.Background(), "other-configmap", metav1.GetOptions{})
  2788  			require.Error(t, err)
  2789  			require.Equal(t, "", cm.Name)
  2790  		}).
  2791  		Then().
  2792  		Expect(OperationPhaseIs(OperationSucceeded)).
  2793  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2794  		Expect(HealthIs(health.HealthStatusHealthy)).
  2795  		When().
  2796  		And(func() {
  2797  			// Add a cluster-scoped resource that is not referencing itself
  2798  			FailOnErr(KubeClientset.RbacV1().ClusterRoles().Create(context.Background(), &rbacv1.ClusterRole{
  2799  				ObjectMeta: metav1.ObjectMeta{
  2800  					Name: "e2e-test-clusterrole",
  2801  					Annotations: map[string]string{
  2802  						common.AnnotationKeyAppInstance: fmt.Sprintf("%s:rbac.authorization.k8s.io/ClusterRole:%s/e2e-other-clusterrole", Name(), DeploymentNamespace()),
  2803  					},
  2804  					Labels: map[string]string{
  2805  						fixture.TestingLabel: "true",
  2806  					},
  2807  				},
  2808  			}, metav1.CreateOptions{}))
  2809  		}).
  2810  		Refresh(RefreshTypeNormal).
  2811  		Then().
  2812  		Expect(OperationPhaseIs(OperationSucceeded)).
  2813  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2814  		Expect(HealthIs(health.HealthStatusHealthy)).
  2815  		When().
  2816  		And(func() {
  2817  			// Add a cluster-scoped resource that is referencing itself
  2818  			FailOnErr(KubeClientset.RbacV1().ClusterRoles().Create(context.Background(), &rbacv1.ClusterRole{
  2819  				ObjectMeta: metav1.ObjectMeta{
  2820  					Name: "e2e-other-clusterrole",
  2821  					Annotations: map[string]string{
  2822  						common.AnnotationKeyAppInstance: fmt.Sprintf("%s:rbac.authorization.k8s.io/ClusterRole:%s/e2e-other-clusterrole", Name(), DeploymentNamespace()),
  2823  					},
  2824  					Labels: map[string]string{
  2825  						fixture.TestingLabel: "true",
  2826  					},
  2827  				},
  2828  			}, metav1.CreateOptions{}))
  2829  		}).
  2830  		Refresh(RefreshTypeNormal).
  2831  		Then().
  2832  		Expect(OperationPhaseIs(OperationSucceeded)).
  2833  		Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
  2834  		Expect(HealthIs(health.HealthStatusHealthy)).
  2835  		When().
  2836  		Sync("--prune").
  2837  		And(func() {
  2838  			// The extra configmap must be pruned now, because it's tracked and does not exist in git
  2839  			cr, err := KubeClientset.RbacV1().ClusterRoles().Get(context.Background(), "e2e-other-clusterrole", metav1.GetOptions{})
  2840  			require.Error(t, err)
  2841  			require.Equal(t, "", cr.Name)
  2842  		}).
  2843  		Then().
  2844  		Expect(OperationPhaseIs(OperationSucceeded)).
  2845  		Expect(SyncStatusIs(SyncStatusCodeSynced)).
  2846  		Expect(HealthIs(health.HealthStatusHealthy))
  2847  
  2848  }