diff --git a/.github/actions/deploy/entrypoint.sh b/.github/actions/deploy/entrypoint.sh index f71b77bc0e..7eadd81eb1 100644 --- a/.github/actions/deploy/entrypoint.sh +++ b/.github/actions/deploy/entrypoint.sh @@ -15,7 +15,7 @@ cd - && kustomize build config/dev | kubectl apply -f - # Ensuring the Atlas credentials Secret kubectl delete secrets my-atlas-key --ignore-not-found -n "${ns}" -kubectl create secret generic my-atlas-key --from-literal="orgId=${INPUT_ATLAS_ORG_ID}" --from-literal="publicApiKey=${INPUT_ATLAS_PUBLIC_KEY}" --from-literal="privateApiKey=${INPUT_ATLAS_PRIVATE_KEY}" -n mongodb-atlas-kubernetes-system +kubectl create secret generic my-atlas-key --from-literal="orgId=${INPUT_ATLAS_ORG_ID}" --from-literal="publicApiKey=${INPUT_ATLAS_PUBLIC_KEY}" --from-literal="privateApiKey=${INPUT_ATLAS_PRIVATE_KEY}" -n "${ns}" # Wait for the Operator to start cmd="while ! kubectl -n ${ns} get pods -l control-plane=controller-manager -o jsonpath={.items[0].status.phase} 2>/dev/null | grep -q Running ; do printf .; sleep 1; done" diff --git a/.github/actions/gen-install-scripts/action.yml b/.github/actions/gen-install-scripts/action.yml index b494b1ecaa..87b3e1d372 100644 --- a/.github/actions/gen-install-scripts/action.yml +++ b/.github/actions/gen-install-scripts/action.yml @@ -4,6 +4,9 @@ inputs: IMAGE_URL: description: "Operator image" required: true + ENV: + description: "Kustomize patch name (enviroment configuration patch)" + required: true runs: using: 'docker' image: 'Dockerfile' diff --git a/.github/actions/gen-install-scripts/entrypoint.sh b/.github/actions/gen-install-scripts/entrypoint.sh index dbe8ad583a..6b7895af6a 100644 --- a/.github/actions/gen-install-scripts/entrypoint.sh +++ b/.github/actions/gen-install-scripts/entrypoint.sh @@ -1,11 +1,11 @@ #!/bin/sh target_dir="deploy" -mkdir "${target_dir}" +mkdir -p "${target_dir}" # Generate configuration and save it to `all-in-one` controller-gen crd:crdVersions=v1 rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases cd config/manager && kustomize edit set image controller="${INPUT_IMAGE_URL}" -cd - && kustomize build config/default > "${target_dir}"/all-in-one.yaml +cd - && kustomize build "config/${INPUT_ENV}" > "${target_dir}/all-in-one.yaml" -cat "${target_dir}"/all-in-one.yaml +cat "${target_dir}/all-in-one.yaml" diff --git a/.github/workflows/push-image.yml b/.github/workflows/push-image.yml index 42b57f423b..1e526d23e4 100644 --- a/.github/workflows/push-image.yml +++ b/.github/workflows/push-image.yml @@ -1,12 +1,9 @@ # Triggered by Pull Request or Manually (from GitHub UI) events +# TODO remove after removing scripts/e2e_local.sh name: Publish image to Registry on: - pull_request: - push: - branches: - - main workflow_dispatch: jobs: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3737093cc4..ea1de424f0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,6 +25,7 @@ jobs: - run: go version - name: Run testing run: CGO_ENABLED=0 go test -v $(go list ./pkg/...) + int-test: name: Integration tests runs-on: ubuntu-latest @@ -43,3 +44,98 @@ jobs: ATLAS_PUBLIC_KEY: ${{ secrets.ATLAS_PUBLIC_KEY }} ATLAS_PRIVATE_KEY: ${{ secrets.ATLAS_PRIVATE_KEY }} + prepare-e2e: + name: Prepare E2E configuration and image + needs: [unit-test] + runs-on: ubuntu-latest + steps: + + - name: Check out code + uses: actions/checkout@v2.3.1 + + - name: Prepare tag + id: prepare + uses: ./.github/actions/set-tag + + - name: Push Atlas Operator to Registry + uses: docker/build-push-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + repository: ${{ secrets.DOCKER_REPO }} + registry: ${{ secrets.DOCKER_REGISTRY }} + tags: ${{ steps.prepare.outputs.tag }} + + e2e: + name: E2E tests + needs: prepare-e2e + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # k8s: ["1.17-kind", "1.19-kind", "1.17-opeshift"] # - + k8s: ["v1.18.15-kind"] # - + test: ["all-in-one"] # TODO refactor + steps: + + - name: Check out code + uses: actions/checkout@v2.3.1 + + - name: Prepare tag + id: prepare + uses: ./.github/actions/set-tag + + - name: Generate configuration for the tests + uses: ./.github/actions/gen-install-scripts + with: + IMAGE_URL: ${{ secrets.DOCKER_REPO }}:${{ steps.prepare.outputs.tag }} + ENV: dev + + - name: Set properties + id: properties + run: | + version=$(echo ${{ matrix.k8s }} | awk -F "-" '{print $1}') + platform=$(echo ${{ matrix.k8s }} | awk -F "-" '{print $2}') + echo "::set-output name=k8s_version::$version" + echo "::set-output name=k8s_platform::$platform" + + # run if platform = kind #TODO + - name: Create k8s Kind Cluster + if: ${{ steps.properties.outputs.k8s_platform == 'kind' && !env.ACT }} + uses: helm/kind-action@v1.1.0 + with: + node_image: kindest/node:${{ steps.properties.outputs.k8s_version }} + cluster_name: ${{ matrix.k8s }} + + - name: Setup Go + if: ${{ steps.properties.outputs.k8s_platform == 'kind' && !env.ACT }} + uses: actions/setup-go@v2 + with: + go-version: '1.15.6' + + - name: Install MongoCLI + run: | + sudo apt-get update + sudo apt-get install -y mongocli + mongocli --version + + - name: Run e2e test + if: ${{ steps.properties.outputs.k8s_platform == 'kind' && !env.ACT }} + env: + MCLI_PUBLIC_API_KEY: ${{ secrets.ATLAS_PUBLIC_KEY }} + MCLI_PRIVATE_API_KEY: ${{ secrets.ATLAS_PRIVATE_KEY }} + MCLI_ORG_ID: ${{ secrets.ATLAS_ORG_ID}} + MCLI_OPS_MANAGER_URL: "https://cloud-qa.mongodb.com/" + K8S_PLATFORM: "${{ steps.properties.outputs.k8s_platform }}" + K8S_VERSION: "${{ steps.properties.outputs.k8s_version }}" + TEST_NAME: "${{ matrix.test }}" + run: | + kubectl version + + go version + go get github.com/onsi/ginkgo/ginkgo && \ + go get github.com/onsi/gomega/... + ginkgo ./test/e2e -x -focus "${TEST_NAME}" + + +# TODO if int test failed - stop e2e, add job for cleanup Atlas projects/clusters \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index 9c38861bae..933761ee7e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -86,5 +86,9 @@ issues: linters: - gochecknoglobals - wrapcheck + - path: test/e2e + linters: + - errcheck + - stylecheck max-issues-per-linter: 0 max-same-issues: 0 diff --git a/Makefile b/Makefile index fed075aaaf..76a7754e36 100644 --- a/Makefile +++ b/Makefile @@ -40,6 +40,10 @@ int-test: generate manifests ## Run integration tests test -f $(ENVTEST_ASSETS_DIR)/setup-envtest.sh || curl -sSLo $(ENVTEST_ASSETS_DIR)/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.8.0/hack/setup-envtest.sh source $(ENVTEST_ASSETS_DIR)/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); ginkgo -v -p -nodes=4 ./test/int -coverprofile cover.out +.PHONY: e2e +e2e: run-kind + ./scripts/e2e_local.sh + .PHONY: manager manager: generate fmt vet ## Build manager binary go build -o bin/manager main.go diff --git a/config/dev/kustomization.yaml b/config/dev/kustomization.yaml index 7d5bf888d8..75ea775a62 100644 --- a/config/dev/kustomization.yaml +++ b/config/dev/kustomization.yaml @@ -1,4 +1,4 @@ - +namespace: mongodb-atlas-kubernetes-system resources: - ../default diff --git a/go.mod b/go.mod index e2a125f873..11afac2887 100644 --- a/go.mod +++ b/go.mod @@ -9,11 +9,13 @@ require ( github.com/google/go-cmp v0.5.4 github.com/mongodb-forks/digest v1.0.2 github.com/onsi/ginkgo v1.14.2 + github.com/pborman/uuid v1.2.0 github.com/onsi/gomega v1.10.5 github.com/stretchr/testify v1.7.0 go.mongodb.org/atlas v0.7.1 go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.16.0 + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c k8s.io/api v0.18.6 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.18.6 diff --git a/go.sum b/go.sum index 010f4e1a13..f95ee141ed 100644 --- a/go.sum +++ b/go.sum @@ -265,6 +265,7 @@ github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ= github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48= github.com/openlyinc/pointy v1.1.2 h1:LywVV2BWC5Sp5v7FoP4bUD+2Yn5k0VNeRbU5vq9jUMY= github.com/openlyinc/pointy v1.1.2/go.mod h1:w2Sytx+0FVuMKn37xpXIAyBNhFNBIJGR/v2m7ik1WtM= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= diff --git a/scripts/e2e_local.sh b/scripts/e2e_local.sh new file mode 100644 index 0000000000..0484699635 --- /dev/null +++ b/scripts/e2e_local.sh @@ -0,0 +1,18 @@ +#!/bin/sh +act -j build-push + +public_key=$(grep "ATLAS_PUBLIC_KEY" .actrc | cut -d "=" -f 2) +private_key=$(grep "ATLAS_PRIVATE_KEY" .actrc | cut -d "=" -f 2) +org_id=$(grep "ATLAS_ORG_ID" .actrc | cut -d "=" -f 2) +image=$(grep "DOCKER_REPO" .actrc | cut -d "=" -f 2):$(git rev-parse --abbrev-ref HEAD)-$(git rev-parse --short HEAD) + +export MCLI_OPS_MANAGER_URL="https://cloud-qa.mongodb.com/" +export MCLI_PUBLIC_API_KEY="${public_key}" +export MCLI_PRIVATE_API_KEY="${private_key}" +export MCLI_ORG_ID="${org_id}" +export INPUT_IMAGE_URL="${image}" +export INPUT_ENV=dev + +./.github/actions/gen-install-scripts/entrypoint.sh + +ginkgo -v -x test/e2e \ No newline at end of file diff --git a/test/e2e/cli/cli.go b/test/e2e/cli/cli.go new file mode 100644 index 0000000000..8cd67cd9bc --- /dev/null +++ b/test/e2e/cli/cli.go @@ -0,0 +1,15 @@ +package cli + +import ( + "os/exec" + + . "github.com/onsi/ginkgo" + "github.com/onsi/gomega/gexec" +) + +func Execute(command string, args ...string) *gexec.Session { + // GinkgoWriter.Write([]byte("\n " + command + " " + strings.Join(args, " "))) // TODO for the local run only + cmd := exec.Command(command, args...) + session, _ := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + return session +} diff --git a/test/e2e/cli/kube/kubectl.go b/test/e2e/cli/kube/kubectl.go new file mode 100644 index 0000000000..bdd53c23e0 --- /dev/null +++ b/test/e2e/cli/kube/kubectl.go @@ -0,0 +1,73 @@ +package kube + +import ( + "fmt" + "strings" + + "encoding/json" + + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" + + v1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1" + cli "github.com/mongodb/mongodb-atlas-kubernetes/test/e2e/cli" +) + +// GenKubeVersion +func GenKubeVersion(fullVersion string) string { + version := strings.Split(fullVersion, ".") + return fmt.Sprintf("Major:\"%s\", Minor:\"%s\"", version[0], version[1]) +} + +// GetPodStatus status.phase +func GetPodStatus(ns string) func() string { + return func() string { + session := cli.Execute("kubectl", "get", "pods", "-l", "control-plane=controller-manager", "-o", "jsonpath={.items[0].status.phase}", "-n", ns) + return string(session.Wait("1m").Out.Contents()) + } +} + +// GetGeneration .status.observedGeneration +func GetGeneration(ns string) func() string { + return func() string { + session := cli.Execute("kubectl", "get", "atlascluster.atlas.mongodb.com/atlascluster-sample", "-n", ns, "-o", "jsonpath={.status.observedGeneration}") + return string(session.Wait("1m").Out.Contents()) + } +} + +// GetStatusCondition .status.conditions.type=Ready.status +func GetStatusCondition(ns string, atlasname string) func() string { + return func() string { + session := cli.Execute("kubectl", "get", atlasname, "-n", ns, "-o", "jsonpath={.status.conditions[?(@.type=='Ready')].status}") + return string(session.Wait("1m").Out.Contents()) + } +} + +// GetProjectResource +func GetProjectResource(namespace, rName string) v1.AtlasProject { + session := cli.Execute("kubectl", "get", rName, "-n", namespace, "-o", "json") + output := session.Wait("1m").Out.Contents() + var project v1.AtlasProject + ExpectWithOffset(1, json.Unmarshal(output, &project)).ShouldNot(HaveOccurred()) + return project +} + +// GetClusterResource +func GetClusterResource(namespace, rName string) v1.AtlasCluster { + session := cli.Execute("kubectl", "get", rName, "-n", namespace, "-o", "json") + output := session.Wait("1m").Out.Contents() + var cluster v1.AtlasCluster + ExpectWithOffset(1, json.Unmarshal(output, &cluster)).ShouldNot(HaveOccurred()) + return cluster +} + +func GetK8sClusterStateName(ns, rName string) func() string { + return func() string { + return GetClusterResource(ns, rName).Status.StateName + } +} + +func DeleteNamespace(ns string) *Buffer { + session := cli.Execute("kubectl", "delete", "namespace", ns) + return session.Wait().Out +} diff --git a/test/e2e/cli/mongocli/mongocli.go b/test/e2e/cli/mongocli/mongocli.go new file mode 100644 index 0000000000..c1c52adde2 --- /dev/null +++ b/test/e2e/cli/mongocli/mongocli.go @@ -0,0 +1,92 @@ +package mongocli + +import ( + "encoding/json" + "fmt" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gbytes" + "github.com/onsi/gomega/gexec" + "go.mongodb.org/atlas/mongodbatlas" + + cli "github.com/mongodb/mongodb-atlas-kubernetes/test/e2e/cli" +) + +func GetClusters(projectID string) []mongodbatlas.Cluster { + session := cli.Execute("mongocli", "atlas", "clusters", "list", "--projectId", projectID, "-o", "json") + output := session.Wait("1m").Out.Contents() + var clusters []mongodbatlas.Cluster + ExpectWithOffset(1, json.Unmarshal(output, &clusters)).ShouldNot(HaveOccurred()) + return clusters +} + +func GetClusterByName(projectID string, name string) mongodbatlas.Cluster { + clusters := GetClusters(projectID) + for _, c := range clusters { + if c.Name == name { + return c + } + } + panic(fmt.Sprintf("no Cluster with name %s in project %s", name, projectID)) +} + +func GetProjects() mongodbatlas.Projects { + session := cli.Execute("mongocli", "iam", "projects", "list", "-o", "json") + output := session.Wait("1m").Out.Contents() + var projects mongodbatlas.Projects + json.Unmarshal(output, &projects) + return projects +} + +func GetProjectID(name string) string { + projects := GetProjects() + for _, p := range projects.Results { + GinkgoWriter.Write([]byte(p.Name + p.ID + name)) + if p.Name == name { + return p.ID + } + } + return "" +} + +func GetClustersInfo(projectID string, name string) mongodbatlas.Cluster { + session := cli.Execute("mongocli", "atlas", "clusters", "describe", name, "--projectId", projectID, "-o", "json") + EventuallyWithOffset(1, session).Should(gexec.Exit(0)) + output := session.Out.Contents() + var cluster mongodbatlas.Cluster + ExpectWithOffset(1, json.Unmarshal(output, &cluster)).ShouldNot(HaveOccurred()) + return cluster +} + +func DeleteCluster(projectID, clusterName string) *Buffer { + session := cli.Execute("mongocli", "atlas", "cluster", "delete", clusterName, "--projectId", projectID, "--force") + return session.Wait().Out +} + +func IsProjectExist(name string) bool { + projects := GetProjects().Results + for _, p := range projects { + if p.Name == name { + return true + } + } + return false +} + +func IsClusterExist(projectID string, name string) bool { + clusters := GetClusters(projectID) + // if clusters + for _, c := range clusters { + GinkgoWriter.Write([]byte(c.Name + name + "\n")) + if c.Name == name { + return true + } + } + return false +} + +func GetClusterStateName(projectID string, clusterName string) string { + result := GetClustersInfo(projectID, clusterName) + return result.StateName +} diff --git a/test/e2e/configuration_test.go b/test/e2e/configuration_test.go new file mode 100644 index 0000000000..b4d5e26a7c --- /dev/null +++ b/test/e2e/configuration_test.go @@ -0,0 +1,158 @@ +package e2e_test + +import ( + "os" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/pborman/uuid" + + . "github.com/onsi/gomega/gbytes" + "github.com/onsi/gomega/gexec" + + cli "github.com/mongodb/mongodb-atlas-kubernetes/test/e2e/cli" + kube "github.com/mongodb/mongodb-atlas-kubernetes/test/e2e/cli/kube" + mongocli "github.com/mongodb/mongodb-atlas-kubernetes/test/e2e/cli/mongocli" + utils "github.com/mongodb/mongodb-atlas-kubernetes/test/e2e/utils" +) + +var _ = Describe("Deploy simple cluster", func() { + + var ID string + + var _ = AfterEach(func() { + GinkgoWriter.Write([]byte(ID)) + Eventually(kube.DeleteNamespace("ns-" + ID)).Should(Say("deleted")) + // mongocli.DeleteCluster(ID, "cluster45") // TODO struct + }) + + It("Release sample all-in-one.yaml should work", func() { + By("Prepare namespaces and project configuration") // TODO clusters/keys will be a bit later + ID = uuid.NewRandom().String() + // TODO move it + namespaceUserResources := "ns-" + ID + namespaceOperator := "mongodb-atlas-kubernetes-system" + keyName := "my-atlas-key" + pName := ID + k8sProjectName := "k-" + ID + ProjectSampleFile := "data/" + pName + ".yaml" + ClusterSampleFile := "data/atlascluster_basic.yaml" // TODO put it to dataprovider + GinkgoWriter.Write([]byte(namespaceUserResources)) + session := cli.Execute("kubectl", "create", "namespace", namespaceUserResources) + Expect(session.Wait()).Should(Say("created")) + + project := utils.NewProject().ProjectName(pName).SecretRef(keyName).CompleteK8sConfig(k8sProjectName) + utils.SaveToFile(ProjectSampleFile, project) + + userProjectConfig := utils.LoadUserProjectConfig(ProjectSampleFile) + userClusterConfig := utils.LoadUserClusterConfig(ClusterSampleFile) + userClusterConfig.Spec.Project.Name = k8sProjectName + userClusterConfig.Spec.ProviderSettings.InstanceSizeName = "M10" + clusterData, _ := utils.JSONToYAMLConvert(userClusterConfig) + utils.SaveToFile(ClusterSampleFile, clusterData) + + By("Check Kubernetes/MongoCLI version\n") + session = cli.Execute("kubectl", "version") + Eventually(session).Should(Say(K8sVersion)) + session = cli.Execute("mongocli", "--version") + Eventually(session).Should(gexec.Exit(0)) + + By("Apply All-in-one configuration\n") + session = cli.Execute("kubectl", "apply", "-f", ConfigAll) + Eventually(session.Wait()).Should(Say("customresourcedefinition.apiextensions.k8s.io/atlasclusters.atlas.mongodb.com")) + Eventually( + kube.GetPodStatus(namespaceOperator), + "5m", "3s", + ).Should(Equal("Running")) + + By("Create secret") + session = cli.Execute("kubectl", "create", "secret", "generic", "my-atlas-key", + "--from-literal=orgId="+os.Getenv("MCLI_ORG_ID"), + "--from-literal=publicApiKey="+os.Getenv("MCLI_PUBLIC_API_KEY"), + "--from-literal=privateApiKey="+os.Getenv("MCLI_PRIVATE_API_KEY"), + "-n", namespaceUserResources, + ) + Eventually(session.Wait()).Should(Say("my-atlas-key created")) + + By("Create Sample Project\n") + session = cli.Execute("kubectl", "apply", "-f", ProjectSampleFile, "-n", namespaceUserResources) + Eventually(session.Wait()).Should(Say("atlasproject.atlas.mongodb.com/" + k8sProjectName + " created")) + + By("Sample Cluster\n") + session = cli.Execute("kubectl", "apply", "-f", ClusterSample, "-n", namespaceUserResources) + Eventually(session.Wait()).Should(Say("created")) + + By("Wait project creation") + Eventually(kube.GetStatusCondition(namespaceUserResources, "atlasproject.atlas.mongodb.com/"+k8sProjectName)).Should(Equal("True")) + Eventually(kube.GetGeneration(namespaceUserResources)).Should(Equal("1")) + Expect( + mongocli.IsProjectExist(userProjectConfig.Spec.Name), + ).Should(BeTrue()) + + projectID := kube.GetProjectResource(namespaceUserResources, "atlasproject.atlas.mongodb.com/"+k8sProjectName).Status.ID + By("Wait cluster creation") + GinkgoWriter.Write([]byte("projectID = " + projectID)) + Eventually(kube.GetK8sClusterStateName( + namespaceUserResources, "atlascluster.atlas.mongodb.com/"+userClusterConfig.ObjectMeta.Name), + "35m", "1m", + ).Should(Equal("IDLE")) + + Expect( + mongocli.GetClusterStateName(projectID, userClusterConfig.Spec.Name), + ).Should(Equal("IDLE")) + + By("check cluster Attribute") // TODO ... + cluster := mongocli.GetClustersInfo(projectID, userClusterConfig.Spec.Name) + Expect( + cluster.ProviderSettings.InstanceSizeName, + ).Should(Equal(userClusterConfig.Spec.ProviderSettings.InstanceSizeName)) + Expect( + cluster.ProviderSettings.ProviderName, + ).Should(Equal(userClusterConfig.Spec.ProviderSettings.ProviderName)) + Expect( + cluster.ProviderSettings.RegionName, + ).Should(Equal(userClusterConfig.Spec.ProviderSettings.RegionName)) + + By("Update cluster\n") + userClusterConfig.Spec.ProviderSettings.InstanceSizeName = "M20" + clusterData, _ = utils.JSONToYAMLConvert(userClusterConfig) + utils.SaveToFile(ClusterSampleFile, clusterData) + + session = cli.Execute("kubectl", "apply", "-f", ClusterSampleFile, "-n", namespaceUserResources) // TODO param + Eventually(session.Wait()).Should(Say("atlascluster-sample configured")) + + By("Wait creation") + Eventually(kube.GetStatusCondition(namespaceUserResources, "atlasproject.atlas.mongodb.com/"+k8sProjectName)).Should(Equal("True")) + Eventually(kube.GetGeneration(namespaceUserResources)).Should(Equal("2")) + Eventually(kube.GetK8sClusterStateName( + namespaceUserResources, "atlascluster.atlas.mongodb.com/"+userClusterConfig.ObjectMeta.Name), + "45m", "1m", + ).Should(Equal("IDLE")) + Expect( + mongocli.GetClusterStateName(projectID, userClusterConfig.Spec.Name), + ).Should(Equal("IDLE")) + + uCluster := mongocli.GetClustersInfo(projectID, userClusterConfig.Spec.Name) + Expect( + uCluster.ProviderSettings.InstanceSizeName, + ).Should(Equal( + userClusterConfig.Spec.ProviderSettings.InstanceSizeName, + )) + + By("Delete cluster") + session = cli.Execute("kubectl", "delete", "-f", "data/updated_atlascluster_basic.yaml", "-n", namespaceUserResources) + Eventually(session.Wait("7m")).Should(gexec.Exit(0)) + Eventually( + func() bool { return mongocli.IsClusterExist(projectID, userClusterConfig.Spec.Name) }, + "10m", "1m", + ).Should(BeFalse()) + + // By("Delete project") // TODO + // session = cli.Execute("kubectl", "delete", "-f", "data/atlasproject.yaml", "-n", namespaceUserResources) + // Eventually( + // cli.IsProjectExist(userProjectConfig.Spec.Name), + // "5m", "20s", + // ).Should(BeFalse()) + }) +}) diff --git a/test/e2e/data/atlascluster_basic.yaml b/test/e2e/data/atlascluster_basic.yaml new file mode 100644 index 0000000000..346b783af7 --- /dev/null +++ b/test/e2e/data/atlascluster_basic.yaml @@ -0,0 +1,12 @@ +apiVersion: atlas.mongodb.com/v1 +kind: AtlasCluster +metadata: + name: atlascluster-sample +spec: + name: cluster45 + projectRef: + name: my-project + providerSettings: + instanceSizeName: M10 + providerName: AWS + regionName: US_EAST_1 diff --git a/test/e2e/data/atlasproject.yaml b/test/e2e/data/atlasproject.yaml new file mode 100644 index 0000000000..5e40d0c547 --- /dev/null +++ b/test/e2e/data/atlasproject.yaml @@ -0,0 +1,8 @@ +apiVersion: atlas.mongodb.com/v1 +kind: AtlasProject +metadata: + name: my-project +spec: + name: Test Atlas Operator Project + connectionSecretRef: + name: my-atlas-key \ No newline at end of file diff --git a/test/e2e/data/updated_atlascluster_basic.yaml b/test/e2e/data/updated_atlascluster_basic.yaml new file mode 100644 index 0000000000..e218dccaa9 --- /dev/null +++ b/test/e2e/data/updated_atlascluster_basic.yaml @@ -0,0 +1,12 @@ +apiVersion: atlas.mongodb.com/v1 +kind: AtlasCluster +metadata: + name: atlascluster-sample +spec: + name: cluster45 + projectRef: + name: my-project + providerSettings: + instanceSizeName: M20 + providerName: AWS + regionName: US_EAST_1 diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go new file mode 100644 index 0000000000..cdc299b340 --- /dev/null +++ b/test/e2e/e2e_suite_test.go @@ -0,0 +1,49 @@ +package e2e_test + +import ( + "os" + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +const ( + EventuallyTimeout = 60 * time.Second + ConsistentlyTimeout = 1 * time.Second + // TODO data provider? + ConfigAll = "../../deploy/" // Released generated files + ProjectSample = "data/atlasproject.yaml" + ClusterSample = "data/atlascluster_basic.yaml" +) + +var ( + // default + Platform = "kind" + K8sVersion = "v1.17.17" +) + +func TestE2e(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "E2e Suite") +} + +var _ = BeforeSuite(func() { + GinkgoWriter.Write([]byte("==============================Before==============================\n")) + SetDefaultEventuallyTimeout(EventuallyTimeout) + SetDefaultConsistentlyDuration(ConsistentlyTimeout) + checkUpMongoCLI() + GinkgoWriter.Write([]byte("========================End of Before==============================\n")) +}) + +// setUpMongoCLI initial setup +func checkUpMongoCLI() { + Platform = os.Getenv("K8S_PLATFORM") + K8sVersion = os.Getenv("K8S_VERSION") + // additional checks + Expect(os.Getenv("MCLI_ORG_ID")).ShouldNot(BeEmpty()) + Expect(os.Getenv("MCLI_PUBLIC_API_KEY")).ShouldNot(BeEmpty()) + Expect(os.Getenv("MCLI_PRIVATE_API_KEY")).ShouldNot(BeEmpty()) + Expect(os.Getenv("MCLI_OPS_MANAGER_URL")).ShouldNot(BeEmpty()) +} diff --git a/test/e2e/utils/cluster.go b/test/e2e/utils/cluster.go new file mode 100644 index 0000000000..ad32fc56fe --- /dev/null +++ b/test/e2e/utils/cluster.go @@ -0,0 +1,15 @@ +package utils + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + v1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1" +) + +type AC struct { + metav1.TypeMeta `json:",inline"` + ObjectMeta *metav1.ObjectMeta `json:"metadata,omitempty"` + Spec ClusterSpec `json:"spec,omitempty"` +} + +type ClusterSpec v1.AtlasClusterSpec diff --git a/test/e2e/utils/project.go b/test/e2e/utils/project.go new file mode 100644 index 0000000000..10080aa788 --- /dev/null +++ b/test/e2e/utils/project.go @@ -0,0 +1,50 @@ +package utils + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + v1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1" +) + +type ISpec interface { + ProjectName(string) ISpec + SecretRef(string) ISpec + // TODO WhiteIP + CompleteK8sConfig(string) []byte +} + +type ProjectSpec v1.AtlasProjectSpec + +type ap struct { + metav1.TypeMeta `json:",inline"` + ObjectMeta *metav1.ObjectMeta `json:"metadata,omitempty"` + Spec ProjectSpec `json:"spec,omitempty"` +} + +func NewProject() ISpec { + return &ProjectSpec{} +} + +func (s *ProjectSpec) ProjectName(name string) ISpec { + s.Name = name + return s +} + +func (s *ProjectSpec) SecretRef(name string) ISpec { + s.ConnectionSecret = &v1.ResourceRef{Name: name} + return s +} + +func (s ProjectSpec) CompleteK8sConfig(k8sname string) []byte { + var t ap + t.TypeMeta = metav1.TypeMeta{ + APIVersion: "atlas.mongodb.com/v1", + Kind: "AtlasProject", + } + t.ObjectMeta = &metav1.ObjectMeta{ + Name: k8sname, + } + t.Spec = s + yamlConf, _ := JSONToYAMLConvert(t) + return yamlConf +} diff --git a/test/e2e/utils/utils.go b/test/e2e/utils/utils.go new file mode 100644 index 0000000000..514cbbb198 --- /dev/null +++ b/test/e2e/utils/utils.go @@ -0,0 +1,89 @@ +package utils + +import ( + "encoding/json" + + "io/ioutil" + "log" + "path/filepath" + + yaml "gopkg.in/yaml.v3" + + v1 "github.com/mongodb/mongodb-atlas-kubernetes/pkg/api/v1" +) + +// LoadUserProjectConfig load configuration into object +func LoadUserProjectConfig(path string) *v1.AtlasProject { + var config v1.AtlasProject + ReadInYAMLFileAndConvert(path, &config) + return &config +} + +// LoadUserClusterConfig load configuration into object +func LoadUserClusterConfig(path string) AC { + var config AC + ReadInYAMLFileAndConvert(path, &config) + return config +} + +func SaveToFile(path string, data []byte) { + ioutil.WriteFile(path, data, 0777) //nolint:gosec // kubectl apply (?) +} + +func JSONToYAMLConvert(cnfg interface{}) ([]byte, error) { + var jsonI interface{} + j, _ := json.Marshal(cnfg) + err := yaml.Unmarshal(j, &jsonI) + if err != nil { + return nil, err + } + return yaml.Marshal(jsonI) +} + +// ReadInYAMLFileAndConvert reads in the yaml file given by the path given +func ReadInYAMLFileAndConvert(pathToYamlFile string, cnfg interface{}) interface{} { + // Read in the yaml file at the path given + yamlFile, err := ioutil.ReadFile(filepath.Clean(pathToYamlFile)) + if err != nil { + log.Printf("Error while parsing YAML file %v, error: %s", filepath.Clean(pathToYamlFile), err) + } + + // Map yamlFile to interface + var body interface{} + if err := yaml.Unmarshal(yamlFile, &body); err != nil { + panic(err) + } + + // Convert yaml to its json counterpart + body = ConvertYAMLtoJSONHelper(body) + + // Generate json string from data structure + jsonFormat, err := json.Marshal(body) + if err != nil { + panic(err) + } + + if err := json.Unmarshal(jsonFormat, &cnfg); err != nil { + panic(err) + } + + return cnfg +} + +// ConvertYAMLtoJSONHelper converts the yaml to json recursively +func ConvertYAMLtoJSONHelper(i interface{}) interface{} { + switch item := i.(type) { + case map[interface{}]interface{}: + document := map[string]interface{}{} + for k, v := range item { + document[k.(string)] = ConvertYAMLtoJSONHelper(v) + } + return document + case []interface{}: + for i, arr := range item { + item[i] = ConvertYAMLtoJSONHelper(arr) + } + } + + return i +}