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

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