From 41fd4b12eadc63f0c399db3cd646857c81ca268f Mon Sep 17 00:00:00 2001 From: "jose.vazquez" Date: Mon, 11 Nov 2024 15:59:55 +0100 Subject: [PATCH] Improve contract test helper Signed-off-by: jose.vazquez --- Makefile | 18 ++++- test/contract/audit/audit_test.go | 35 ++++----- test/helper/contract/ako.go | 49 ------------ test/helper/contract/contract.go | 105 ++++++++++++++++---------- test/helper/contract/contract_test.go | 20 +++-- 5 files changed, 107 insertions(+), 120 deletions(-) diff --git a/Makefile b/Makefile index 55bb0593f0..8a4fc85400 100644 --- a/Makefile +++ b/Makefile @@ -132,6 +132,10 @@ CONTAINER_SPEC=.spec.template.spec.containers[0] SILK_ASSET_GROUP="atlas-kubernetes-operator" +HELM_REPO_URL = "https://mongodb.github.io/helm-charts" +HELM_AKO_INSTALL_NAME = local-ako-install +HELM_AKO_NAMESPACE = test-ako + .DEFAULT_GOAL := help .PHONY: help help: ## Show this help screen @@ -567,7 +571,17 @@ download-from-silk: ## Download the latest augmented SBOM for a given architectu store-silk-sboms: download-from-silk ## Download & Store the latest augmented SBOM for a given version & architecture SILK_ASSET_GROUP=$(SILK_ASSET_GROUP) ./scripts/store-sbom-in-s3.sh $(VERSION) $(TARGET_ARCH) +.PHONY: install-ako-helm +install-ako-helm: + helm repo add mongodb $(HELM_REPO_URL) + helm upgrade $(HELM_AKO_INSTALL_NAME) mongodb/mongodb-atlas-operator --atomic --install \ + --set-string atlasURI=$(MCLI_OPS_MANAGER_URL) \ + --set objectDeletionProtection=false \ + --set subobjectDeletionProtection=false \ + --namespace=$(HELM_AKO_NAMESPACE) --create-namespace + kubectl get crds + .PHONY: contract-tests -contract-tests: run-kind ## Run contract tests +contract-tests: run-kind install-ako-helm ## Run contract tests with support by a k8s cluster and AKO go clean -testcache - AKO_CONTRACT_TEST=1 go test -v -race -cover ./test/contract/... + AKO_CONTRACT_TEST=1 HELM_AKO_NAMESPACE=$(HELM_AKO_NAMESPACE) go test -v -race -cover ./test/contract/... diff --git a/test/contract/audit/audit_test.go b/test/contract/audit/audit_test.go index 86440cd52c..da411302a3 100644 --- a/test/contract/audit/audit_test.go +++ b/test/contract/audit/audit_test.go @@ -8,8 +8,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/audit" akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1" @@ -17,19 +15,22 @@ import ( ) func TestDefaultAuditingGet(t *testing.T) { - contract.RunContractTest(t, "get default auditing", func(ct *contract.ContractTest) { + ctx := context.Background() + contract.RunGoContractTest(ctx, t, "get default auditing", func(ch contract.ContractHelper) { projectName := "default-auditing-project" - ct.AddResources(time.Minute, contract.DefaultAtlasProject(projectName)) - testProjectID := mustReadProjectID(t, ct.Ctx, ct.K8sClient, ct.Namespace(), projectName) - as := audit.NewAuditLog(ct.AtlasClient.AuditingApi) + require.NoError(t, ch.AddResources(ctx, time.Minute, contract.DefaultAtlasProject(projectName))) + testProjectID, err := ch.ProjectID(ctx, projectName) + require.NoError(t, err) + as := audit.NewAuditLog(ch.AtlasClient().AuditingApi) - result, err := as.Get(ct.Ctx, testProjectID) + result, err := as.Get(ctx, testProjectID) require.NoError(t, err) assert.Equal(t, audit.NewAuditConfig(nil), result) }) } func TestSyncs(t *testing.T) { + ctx := context.Background() testCases := []struct { title string auditing *audit.AuditConfig @@ -86,12 +87,13 @@ func TestSyncs(t *testing.T) { ), }, } - contract.RunContractTest(t, "test syncs", func(ct *contract.ContractTest) { + contract.RunGoContractTest(ctx, t, "test syncs", func(ch contract.ContractHelper) { projectName := "audit-syncs-project" - ct.AddResources(time.Minute, contract.DefaultAtlasProject(projectName)) - testProjectID := mustReadProjectID(t, ct.Ctx, ct.K8sClient, ct.Namespace(), projectName) + require.NoError(t, ch.AddResources(ctx, time.Minute, contract.DefaultAtlasProject(projectName))) + testProjectID, err := ch.ProjectID(ctx, projectName) + require.NoError(t, err) ctx := context.Background() - as := audit.NewAuditLog(ct.AtlasClient.AuditingApi) + as := audit.NewAuditLog(ch.AtlasClient().AuditingApi) for _, tc := range testCases { t.Run(tc.title, func(t *testing.T) { @@ -105,14 +107,3 @@ func TestSyncs(t *testing.T) { } }) } - -func mustReadProjectID(t *testing.T, ctx context.Context, k8sClient client.Client, ns, projectName string) string { - t.Helper() - project := akov2.AtlasProject{} - key := types.NamespacedName{ - Namespace: ns, - Name: projectName, - } - require.NoError(t, k8sClient.Get(ctx, key, &project)) - return project.Status.ID -} diff --git a/test/helper/contract/ako.go b/test/helper/contract/ako.go index bcc614e38c..0225fd76d6 100644 --- a/test/helper/contract/ako.go +++ b/test/helper/contract/ako.go @@ -3,10 +3,7 @@ package contract import ( "context" "fmt" - "log" "os" - "os/exec" - "strings" "go.mongodb.org/atlas-sdk/v20231115008/admin" corev1 "k8s.io/api/core/v1" @@ -17,10 +14,6 @@ import ( "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/atlas" ) -const ( - mongodbRepoURL = "https://mongodb.github.io/helm-charts" -) - func DefaultAtlasProject(name string) client.Object { return &akov2.AtlasProject{ ObjectMeta: metav1.ObjectMeta{Name: name}, @@ -51,41 +44,6 @@ func mustCreateVersionedAtlasClient(ctx context.Context) *admin.APIClient { return client } -func ensureTestAtlasOperator(namespace string) error { - if !isTestAtlasOperatorInstalled(namespace) { - return installTestAtlasOperator(namespace) - } - return nil -} - -func isTestAtlasOperatorInstalled(namespace string) bool { - output, err := run("helm", "ls", "-n", namespace) - if err != nil { - return false - } - return strings.Contains(string(output), operatorInstallName) -} - -func installTestAtlasOperator(namespace string) error { - if _, err := run("helm", "repo", "add", "mongodb", mongodbRepoURL); err != nil { - return fmt.Errorf("failed to set mongodb repo to URL %q: %w", mongodbRepoURL, err) - } - domain := os.Getenv("MCLI_OPS_MANAGER_URL") - args := []string{ - "install", - operatorInstallName, - "mongodb/mongodb-atlas-operator", - "--atomic", - "--set-string", fmt.Sprintf("atlasURI=%s", domain), - "--set", "objectDeletionProtection=false", - "--set", "subobjectDeletionProtection=false", - "--namespace=" + namespace, - "--create-namespace", - } - _, err := run("helm", args...) - return err -} - func globalSecret(namespace string) client.Object { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -102,10 +60,3 @@ func globalSecret(namespace string) client.Object { }, } } - -func run(cmd string, args ...string) ([]byte, error) { - log.Printf("Running:\n%s %s", cmd, strings.Join(args, " ")) - output, err := exec.Command(cmd, args...).CombinedOutput() - log.Printf("%s", string(output)) - return output, err -} diff --git a/test/helper/contract/contract.go b/test/helper/contract/contract.go index 44422928aa..febc16486f 100644 --- a/test/helper/contract/contract.go +++ b/test/helper/contract/contract.go @@ -2,84 +2,111 @@ package contract import ( "context" + "fmt" + "os" "testing" "time" "go.mongodb.org/atlas-sdk/v20231115008/admin" + "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/stretchr/testify/require" + akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1" "github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/control" "github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e/utils" ) -const ( - operatorInstallName = "test-atlas-operator" - akoTestNamespace = "ako-test" -) - -type ContractTest struct { - t *testing.T - akoInstalled bool - namespace string - resources []client.Object +type ContractHelper interface { + AtlasClient() *admin.APIClient + AddResources(ctx context.Context, timeout time.Duration, resources ...client.Object) error + ProjectID(ctx context.Context, projectName string) (string, error) +} - Ctx context.Context - AtlasClient *admin.APIClient - K8sClient client.Client +type contractTest struct { + credentials bool + namespace string + resources []client.Object + k8sClient client.Client + atlasClient *admin.APIClient } -func (ct *ContractTest) cleanup() { +func (ct *contractTest) cleanup(ctx context.Context) error { for i := len(ct.resources) - 1; i >= 0; i-- { resource := ct.resources[i] - require.NoError(ct.t, ct.K8sClient.Delete(ct.Ctx, resource)) + if err := ct.k8sClient.Delete(ctx, resource); err != nil { + return fmt.Errorf("failed to delete contract test pre-set resource: %w", err) + } } ct.resources = []client.Object{} if ct.namespace != "" { - require.NoError(ct.t, ct.K8sClient.Delete(ct.Ctx, defaultNamespace(ct.namespace))) + if err := ct.k8sClient.Delete(ctx, defaultNamespace(ct.namespace)); err != nil { + return fmt.Errorf("failed to delete namespace %q: %w", ct.namespace, err) + } } + return nil } -func RunContractTest(t *testing.T, name string, ctFn func(ct *ContractTest)) { - t.Helper() +func RunGoContractTest(ctx context.Context, t *testing.T, name string, contractTest func(ch ContractHelper)) { if !control.Enabled("AKO_CONTRACT_TEST") { t.Skip("Skipping contract test as AKO_CONTRACT_TEST is unset") return } - ctx := context.Background() - ct := &ContractTest{ - t: t, - Ctx: ctx, - K8sClient: mustCreateK8sClient(), - akoInstalled: false, - resources: []client.Object{}, - AtlasClient: mustCreateVersionedAtlasClient(ctx), - } - defer ct.cleanup() + ct := newContractTest(ctx) + defer func() { + require.NoError(t, ct.cleanup(ctx)) + }() t.Run(name, func(t *testing.T) { - ctFn(ct) + contractTest(ct) }) } -func (ct *ContractTest) AddResources(timeout time.Duration, resources ...client.Object) { - if !ct.akoInstalled { - require.NoError(ct.t, ensureTestAtlasOperator(akoTestNamespace)) - require.NoError(ct.t, k8sRecreate(ct.Ctx, ct.K8sClient, globalSecret(akoTestNamespace))) - ct.akoInstalled = true +func newContractTest(ctx context.Context) *contractTest { + return &contractTest{ + k8sClient: mustCreateK8sClient(), + credentials: false, + resources: []client.Object{}, + atlasClient: mustCreateVersionedAtlasClient(ctx), + } +} + +func (ct *contractTest) AtlasClient() *admin.APIClient { + return ct.atlasClient +} + +func (ct *contractTest) AddResources(ctx context.Context, timeout time.Duration, resources ...client.Object) error { + if !ct.credentials { + akoTestNamespace := os.Getenv("HELM_AKO_NAMESPACE") + if err := k8sRecreate(ctx, ct.k8sClient, globalSecret(akoTestNamespace)); err != nil { + return fmt.Errorf("failed to set AKO namespace: %w", err) + } + ct.credentials = true } if ct.namespace == "" { ct.namespace = utils.RandomName("test-ns") - require.NoError(ct.t, ct.K8sClient.Create(ct.Ctx, defaultNamespace(ct.namespace))) + if err := ct.k8sClient.Create(ctx, defaultNamespace(ct.namespace)); err != nil { + return fmt.Errorf("failed to create test namespace: %w", err) + } } for _, resource := range resources { resource.SetNamespace(ct.namespace) - require.NoError(ct.t, ct.K8sClient.Create(ct.Ctx, resource)) + if err := ct.k8sClient.Create(ctx, resource); err != nil { + return fmt.Errorf("failed to create resource: %w", err) + } } ct.resources = append(ct.resources, resources...) - require.NoError(ct.t, waitForReadyStatus(ct.K8sClient, resources, timeout)) + if err := waitForReadyStatus(ct.k8sClient, resources, timeout); err != nil { + return fmt.Errorf("failed to reach READY status: %w", err) + } + return nil } -func (ct *ContractTest) Namespace() string { - return ct.namespace +func (ct *contractTest) ProjectID(ctx context.Context, projectName string) (string, error) { + project := akov2.AtlasProject{} + key := types.NamespacedName{Namespace: ct.namespace, Name: projectName} + if err := ct.k8sClient.Get(ctx, key, &project); err != nil { + return "", fmt.Errorf("failed to get project ID: %w", err) + } + return project.Status.ID, nil } diff --git a/test/helper/contract/contract_test.go b/test/helper/contract/contract_test.go index 6d263dcc63..d74efd1c4a 100644 --- a/test/helper/contract/contract_test.go +++ b/test/helper/contract/contract_test.go @@ -1,6 +1,7 @@ package contract_test import ( + "context" "fmt" "os" "strings" @@ -19,17 +20,19 @@ const ( ) func TestContractTestSkip(t *testing.T) { + ctx := context.Background() testWithEnv(func() { - contract.RunContractTest(t, "Skip contract test", func(_ *contract.ContractTest) { + contract.RunGoContractTest(ctx, t, "Skip contract test", func(_ contract.ContractHelper) { panic("should not have got here!") }) }, "-AKO_CONTRACT_TEST") } func TestContractTestClientSetFails(t *testing.T) { + ctx := context.Background() testWithEnv(func() { assert.Panics(t, func() { - contract.RunContractTest(t, "bad client settings panics", func(_ *contract.ContractTest) {}) + contract.RunGoContractTest(ctx, t, "bad client settings panics", func(_ contract.ContractHelper) {}) }) }, "AKO_CONTRACT_TEST=1", @@ -39,14 +42,15 @@ func TestContractTestClientSetFails(t *testing.T) { } func TestContractsWithResources(t *testing.T) { - contract.RunContractTest(t, "run contract test list projects", func(ct *contract.ContractTest) { - ct.AddResources(time.Minute, contract.DefaultAtlasProject("contract-tests-list-projects")) - _, _, err := ct.AtlasClient.ProjectsApi.ListProjects(ct.Ctx).Execute() + ctx := context.Background() + contract.RunGoContractTest(ctx, t, "run contract test list projects", func(ch contract.ContractHelper) { + ch.AddResources(ctx, time.Minute, contract.DefaultAtlasProject("contract-tests-list-projects")) + _, _, err := ch.AtlasClient().ProjectsApi.ListProjects(ctx).Execute() assert.NoError(t, err) }) - contract.RunContractTest(t, "run contract test list orgs", func(ct *contract.ContractTest) { - ct.AddResources(time.Minute, contract.DefaultAtlasProject("contract-tests-list-orgs")) - _, _, err := ct.AtlasClient.OrganizationsApi.ListOrganizations(ct.Ctx).Execute() + contract.RunGoContractTest(ctx, t, "run contract test list orgs", func(ch contract.ContractHelper) { + ch.AddResources(ctx, time.Minute, contract.DefaultAtlasProject("contract-tests-list-orgs")) + _, _, err := ch.AtlasClient().OrganizationsApi.ListOrganizations(ctx).Execute() assert.NoError(t, err) }) }