diff --git a/test/e2e/admin_test.go b/test/e2e/admin_test.go new file mode 100644 index 0000000000000..a896888b1b55f --- /dev/null +++ b/test/e2e/admin_test.go @@ -0,0 +1,79 @@ +package e2e + +import ( + "context" + "testing" + + "github.com/argoproj/gitops-engine/pkg/utils/kube" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + . "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/argoproj/argo-cd/v2/test/e2e/fixture" + . "github.com/argoproj/argo-cd/v2/test/e2e/fixture/admin" + . "github.com/argoproj/argo-cd/v2/test/e2e/fixture/admin/utils" + appfixture "github.com/argoproj/argo-cd/v2/test/e2e/fixture/app" +) + +func TestBackupExportImport(t *testing.T) { + var exportRawOutput string + ctx := Given(t) + // Create application in argocd namespace + appctx := appfixture.GivenWithSameState(t) + + // Create application in test namespace + appctx. + Path(guestbookPath). + Name("exported-app1"). + When(). + CreateApp(). + Then(). + And(func(app *Application) { + assert.Equal(t, "exported-app1", app.Name) + assert.Equal(t, fixture.TestNamespace(), app.Namespace) + }) + + // Create app in other namespace + appctx. + Path(guestbookPath). + Name("exported-app-other-namespace"). + SetAppNamespace(fixture.AppNamespace()). + When(). + CreateApp(). + Then(). + And(func(app *Application) { + assert.Equal(t, "exported-app-other-namespace", app.Name) + assert.Equal(t, fixture.AppNamespace(), app.Namespace) + }) + + ctx. + When(). + RunExport(). + Then(). + AndCLIOutput(func(output string, err error) { + require.NoError(t, err, "export finished with error") + exportRawOutput = output + }). + AndExportedResources(func(exportResources *ExportedResources, err error) { + require.NoError(t, err, "export format not valid") + assert.True(t, exportResources.HasResource(kube.NewResourceKey("", "ConfigMap", "", "argocd-cm")), "argocd-cm not found in export") + assert.True(t, exportResources.HasResource(kube.NewResourceKey(ApplicationSchemaGroupVersionKind.Group, ApplicationSchemaGroupVersionKind.Kind, "", "exported-app1")), "test namespace application not in export") + assert.True(t, exportResources.HasResource(kube.NewResourceKey(ApplicationSchemaGroupVersionKind.Group, ApplicationSchemaGroupVersionKind.Kind, fixture.AppNamespace(), "exported-app-other-namespace")), "app namespace application not in export") + }) + + // Test import - clean state + ctx = Given(t) + + ctx. + When(). + RunImport(exportRawOutput). + Then(). + AndCLIOutput(func(output string, err error) { + require.NoError(t, err, "import finished with error") + _, err = fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.TestNamespace()).Get(context.Background(), "exported-app1", v1.GetOptions{}) + require.NoError(t, err, "failed getting test namespace application after import") + _, err = fixture.AppClientset.ArgoprojV1alpha1().Applications(fixture.AppNamespace()).Get(context.Background(), "exported-app-other-namespace", v1.GetOptions{}) + require.NoError(t, err, "failed getting app namespace application after import") + }) +} diff --git a/test/e2e/fixture/admin/actions.go b/test/e2e/fixture/admin/actions.go new file mode 100644 index 0000000000000..4519d228f9c1a --- /dev/null +++ b/test/e2e/fixture/admin/actions.go @@ -0,0 +1,67 @@ +package admin + +import ( + "github.com/argoproj/argo-cd/v2/test/e2e/fixture" +) + +// this implements the "when" part of given/when/then +// +// none of the func implement error checks, and that is complete intended, you should check for errors +// using the Then() +type Actions struct { + context *Context + ignoreErrors bool + lastOutput string + lastError error +} + +func (a *Actions) prepareExportCommand() []string { + a.context.t.Helper() + args := []string{"export", "--application-namespaces", fixture.AppNamespace()} + + return args +} + +func (a *Actions) prepareImportCommand() []string { + a.context.t.Helper() + args := []string{"import", "--application-namespaces", fixture.AppNamespace(), "-"} + + return args +} + +func (a *Actions) RunExport() *Actions { + a.context.t.Helper() + a.runCli(a.prepareExportCommand()...) + return a +} + +func (a *Actions) RunImport(stdin string) *Actions { + a.context.t.Helper() + a.runCliWithStdin(stdin, a.prepareImportCommand()...) + return a +} + +func (a *Actions) IgnoreErrors() *Actions { + a.ignoreErrors = true + return a +} + +func (a *Actions) DoNotIgnoreErrors() *Actions { + a.ignoreErrors = false + return a +} + +func (a *Actions) runCli(args ...string) { + a.context.t.Helper() + a.lastOutput, a.lastError = RunCli(args...) +} + +func (a *Actions) runCliWithStdin(stdin string, args ...string) { + a.context.t.Helper() + a.lastOutput, a.lastError = RunCliWithStdin(stdin, args...) +} + +func (a *Actions) Then() *Consequences { + a.context.t.Helper() + return &Consequences{a.context, a} +} diff --git a/test/e2e/fixture/admin/consequences.go b/test/e2e/fixture/admin/consequences.go new file mode 100644 index 0000000000000..bc65f3a532794 --- /dev/null +++ b/test/e2e/fixture/admin/consequences.go @@ -0,0 +1,37 @@ +package admin + +import ( + . "github.com/argoproj/argo-cd/v2/test/e2e/fixture/admin/utils" +) + +// this implements the "then" part of given/when/then +type Consequences struct { + context *Context + actions *Actions +} + +func (c *Consequences) And(block func()) *Consequences { + c.context.t.Helper() + block() + return c +} + +func (c *Consequences) AndCLIOutput(block func(output string, err error)) *Consequences { + c.context.t.Helper() + block(c.actions.lastOutput, c.actions.lastError) + return c +} + +// For use after running export with the exported resources desirialized +func (c *Consequences) AndExportedResources(block func(resources *ExportedResources, err error)) { + result, err := GetExportedResourcesFromOutput(c.actions.lastOutput) + block(&result, err) +} + +func (c *Consequences) Given() *Context { + return c.context +} + +func (c *Consequences) When() *Actions { + return c.actions +} diff --git a/test/e2e/fixture/admin/context.go b/test/e2e/fixture/admin/context.go new file mode 100644 index 0000000000000..aed58cb1a7b79 --- /dev/null +++ b/test/e2e/fixture/admin/context.go @@ -0,0 +1,41 @@ +package admin + +import ( + "testing" + + "github.com/argoproj/argo-cd/v2/test/e2e/fixture" + "github.com/argoproj/argo-cd/v2/util/env" +) + +// this implements the "given" part of given/when/then +type Context struct { + t *testing.T + // seconds + timeout int + name string +} + +func Given(t *testing.T) *Context { + fixture.EnsureCleanState(t) + return GivenWithSameState(t) +} + +func GivenWithSameState(t *testing.T) *Context { + // ARGOCE_E2E_DEFAULT_TIMEOUT can be used to override the default timeout + // for any context. + timeout := env.ParseNumFromEnv("ARGOCD_E2E_DEFAULT_TIMEOUT", 20, 0, 180) + return &Context{ + t: t, + name: fixture.Name(), + timeout: timeout, + } +} + +func (c *Context) And(block func()) *Context { + block() + return c +} + +func (c *Context) When() *Actions { + return &Actions{context: c} +} diff --git a/test/e2e/fixture/admin/fixture.go b/test/e2e/fixture/admin/fixture.go new file mode 100644 index 0000000000000..92216c58d42fe --- /dev/null +++ b/test/e2e/fixture/admin/fixture.go @@ -0,0 +1,15 @@ +package admin + +import ( + "github.com/argoproj/argo-cd/v2/test/e2e/fixture" +) + +// For admin CLI with kubernetes context +func RunCli(args ...string) (string, error) { + return RunCliWithStdin("", args...) +} + +func RunCliWithStdin(stdin string, args ...string) (string, error) { + args = append([]string{"admin", "--namespace", fixture.TestNamespace()}, args...) + return fixture.RunCliWithStdin(stdin, true, args...) +} diff --git a/test/e2e/fixture/admin/utils/backup.go b/test/e2e/fixture/admin/utils/backup.go new file mode 100644 index 0000000000000..79bd890518603 --- /dev/null +++ b/test/e2e/fixture/admin/utils/backup.go @@ -0,0 +1,48 @@ +package utils + +import ( + "fmt" + "strings" + + kube "github.com/argoproj/gitops-engine/pkg/utils/kube" + yaml "gopkg.in/yaml.v3" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type ExportedResources []unstructured.Unstructured + +func GetExportedResourcesFromOutput(output string) (ExportedResources, error) { + var resources []unstructured.Unstructured + docs := strings.Split(output, "---") + + for _, doc := range docs { + doc = strings.TrimSpace(doc) + if len(doc) == 0 { + continue + } + + var resourceData map[string]interface{} + + if err := yaml.Unmarshal([]byte(doc), &resourceData); err != nil { + return nil, fmt.Errorf("error unmarshaling YAML: %w", err) + } + + resource := unstructured.Unstructured{Object: resourceData} + resources = append(resources, resource) + } + + return resources, nil +} + +func (e ExportedResources) HasResource(resource kube.ResourceKey) bool { + for _, res := range e { + if res.GetObjectKind().GroupVersionKind().Group == resource.Group && + res.GetKind() == resource.Kind && + res.GetName() == resource.Name && + res.GetNamespace() == resource.Namespace { + return true + } + } + + return false +} diff --git a/test/e2e/fixture/fixture.go b/test/e2e/fixture/fixture.go index e9e2c351c3df9..24d0e4ce74d71 100644 --- a/test/e2e/fixture/fixture.go +++ b/test/e2e/fixture/fixture.go @@ -741,15 +741,20 @@ func RunCliWithRetry(maxRetries int, args ...string) (string, error) { } func RunCli(args ...string) (string, error) { - return RunCliWithStdin("", args...) + return RunCliWithStdin("", false, args...) } -func RunCliWithStdin(stdin string, args ...string) (string, error) { +func RunCliWithStdin(stdin string, isKubeConextOnlyCli bool, args ...string) (string, error) { if plainText { args = append(args, "--plaintext") } - args = append(args, "--server", apiServerAddress, "--auth-token", token, "--insecure") + // For commands executed with Kubernetes context server argument causes a conflict (for those commands server argument is for KubeAPI server), also authentication is not required + if !isKubeConextOnlyCli { + args = append(args, "--server", apiServerAddress, "--auth-token", token) + } + + args = append(args, "--insecure") return RunWithStdin(stdin, "", "../../dist/argocd", args...) } @@ -1010,3 +1015,11 @@ func RecordTestRun(t *testing.T) { t.Fatalf("could not write to %s: %v", rf, err) } } + +func GetApiServerAddress() string { + return apiServerAddress +} + +func GetToken() string { + return token +} diff --git a/test/e2e/project_management_test.go b/test/e2e/project_management_test.go index 3983cedcd75c8..f00c4e3eeba62 100644 --- a/test/e2e/project_management_test.go +++ b/test/e2e/project_management_test.go @@ -80,12 +80,12 @@ func TestProjectCreation(t *testing.T) { require.NoError(t, err) // fail without upsert flag - _, err = fixture.RunCliWithStdin(stdinString, "proj", "create", + _, err = fixture.RunCliWithStdin(stdinString, false, "proj", "create", "-f", "-") require.Error(t, err) // succeed with the upsert flag - _, err = fixture.RunCliWithStdin(stdinString, "proj", "create", + _, err = fixture.RunCliWithStdin(stdinString, false, "proj", "create", "-f", "-", "--upsert") require.NoError(t, err) proj, err = fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.TestNamespace()).Get(context.Background(), projectName, metav1.GetOptions{})