diff --git a/hack/tests/e2e-helm.sh b/hack/tests/e2e-helm.sh index 71c9d83a4d..faea970172 100755 --- a/hack/tests/e2e-helm.sh +++ b/hack/tests/e2e-helm.sh @@ -1,157 +1,23 @@ #!/usr/bin/env bash -set -eux +set -o errexit +set -o nounset +set -o pipefail -source hack/lib/test_lib.sh -source hack/lib/image_lib.sh +source ./hack/lib/test_lib.sh +source ./hack/lib/image_lib.sh -DEST_IMAGE="quay.io/example/nginx-operator:v0.0.2" -TMPDIR="$(mktemp -d)" -pushd $TMPDIR -trap_add 'rm -rf $TMPDIR' EXIT +# install SDK binaries +make install -setup_envs $tmp_sdk_root - -# kind has an issue with certain image registries (ex. redhat's), so use a -# different test pod image. -METRICS_TEST_IMAGE="curlimages/curl:latest" - -test_namespace="nginx-cr-system" -operator_namespace="nginx-operator-system" - -deploy_operator() { - make install - make deploy IMG="$DEST_IMAGE" - kubectl create clusterrolebinding nginx-operator-system-metrics-reader --clusterrole=nginx-operator-metrics-reader --serviceaccount=${operator_namespace}:default - kubectl create namespace ${test_namespace} -} - -remove_operator() { - kubectl delete --ignore-not-found=true --namespace=${test_namespace} -f "$OPERATORDIR/config/samples/helm.example_v1alpha1_nginx.yaml" - kubectl delete --ignore-not-found=true namespace ${test_namespace} - kubectl delete --ignore-not-found=true clusterrolebinding nginx-operator-system-metrics-reader - make undeploy -} - -operator_logs() { - kubectl get all --namespace=${operator_namespace} - kubectl get events --namespace=${operator_namespace} - kubectl logs deployment/nginx-operator-controller-manager -c manager --namespace=${operator_namespace} -} - -test_operator() { - # kind has an issue with certain image registries (ex. redhat's), so use a - # different test pod image. - local metrics_test_image="$METRICS_TEST_IMAGE" - - # wait for operator pod to run - if ! timeout 1m kubectl rollout status deployment/nginx-operator-controller-manager -n ${operator_namespace} ; - then - error_text "Failed to rollout status" - operator_logs - exit 1 - fi - - metrics_service="nginx-operator-controller-manager-metrics-service" - - # verify that metrics service was created - if ! timeout 60s bash -c -- "until kubectl get service/${metrics_service} --namespace=${operator_namespace} > /dev/null 2>&1; do sleep 1; done"; - then - error_text "Failed to get metrics service" - operator_logs - exit 1 - fi - - - serviceaccount_secret=$(kubectl get serviceaccounts default -n ${operator_namespace} -o jsonpath='{.secrets[0].name}') - token=$(kubectl get secret ${serviceaccount_secret} -n ${operator_namespace} -o jsonpath={.data.token} | base64 -d) - - # verify that the metrics endpoint exists - if ! timeout 60s bash -c -- "until kubectl run --attach --rm --restart=Never --namespace=${operator_namespace} test-metrics --image=${metrics_test_image} -- -sfkH \"Authorization: Bearer ${token}\" https://${metrics_service}:8443/metrics; do sleep 1; done"; - then - error_text "Failed to verify that metrics endpoint exists" - operator_logs - exit 1 - fi - - # create CR - kubectl create --namespace=${test_namespace} -f config/samples/helm.example_v1alpha1_nginx.yaml - trap_add "kubectl delete --namespace=${test_namespace} --ignore-not-found -f ${OPERATORDIR}/config/samples/helm.example_v1alpha1_nginx.yaml" EXIT - if ! timeout 1m bash -c -- "until kubectl get --namespace=${test_namespace} nginxes.helm.example.com nginx-sample -o jsonpath='{..status.deployedRelease.name}' | grep 'nginx-sample'; do sleep 1; done"; - then - error_text "Failed to create CR" - operator_logs - exit 1 - fi +# create test directories +test_dir=./test +tests=$test_dir/e2e-helm +export TRACE=1 +export GO111MODULE=on +# set default envvars +setup_envs $tmp_sdk_root - release_name=$(kubectl get --namespace=${test_namespace} nginxes.helm.example.com nginx-sample -o jsonpath="{..status.deployedRelease.name}") - nginx_deployment=$(kubectl get --namespace=${test_namespace} deployment -l "app.kubernetes.io/instance=${release_name}" -o jsonpath="{..metadata.name}") - - if ! timeout 1m kubectl rollout --namespace=${test_namespace} status deployment/${nginx_deployment}; - then - error_text "FAIL: kubectl rollout status CR deployment" - kubectl describe --namespace=${test_namespace} pods -l "app.kubernetes.io/instance=${release_name}" - kubectl describe --namespace=${test_namespace} deployments ${nginx_deployment} - operator_logs - exit 1 - fi - - nginx_service=$(kubectl get --namespace=${test_namespace} service -l "app.kubernetes.io/instance=${release_name}" -o jsonpath="{..metadata.name}") - kubectl get --namespace=${test_namespace} service ${nginx_service} - - # scale deployment replicas to 2 and verify the - # deployment automatically scales back down to 1. - kubectl scale --namespace=${test_namespace} deployment/${nginx_deployment} --replicas=2 - if ! timeout 1m bash -c -- "until test \$(kubectl get --namespace=${test_namespace} deployment/${nginx_deployment} -o jsonpath='{..spec.replicas}') -eq 1; do sleep 1; done"; - then - kubectl describe --namespace=${test_namespace} pods -l "app.kubernetes.io/instance=${release_name}" - kubectl describe --namespace=${test_namespace} deployments ${nginx_deployment} - operator_logs - exit 1 - fi - - # update CR to replicaCount=2 and verify the deployment - # automatically scales up to 2 replicas. - kubectl patch --namespace=${test_namespace} nginxes.helm.example.com nginx-sample -p '[{"op":"replace","path":"/spec/replicaCount","value":2}]' --type=json - if ! timeout 1m bash -c -- "until test \$(kubectl get --namespace=${test_namespace} deployment/${nginx_deployment} -o jsonpath='{..spec.replicas}') -eq 2; do sleep 1; done"; - then - kubectl describe --namespace=${test_namespace} pods -l "app.kubernetes.io/instance=${release_name}" - kubectl describe --namespace=${test_namespace} deployments ${nginx_deployment} - operator_logs - exit 1 - fi - - kubectl delete --namespace=${test_namespace} -f config/samples/helm.example_v1alpha1_nginx.yaml --wait=true - kubectl logs deployment/nginx-operator-controller-manager -c manager --namespace=${operator_namespace} | grep "Uninstalled release" | grep "${release_name}" -} - -# create and build the operator -mkdir nginx-operator -pushd nginx-operator -log=$(operator-sdk init --plugins=helm.operator-sdk.io/v1 \ - --domain=com --group=helm.example --version=v1alpha1 --kind=Nginx \ - 2>&1) -echo $log -if echo $log | grep -q "failed to generate RBAC rules"; then - echo FAIL expected successful generation of RBAC rules - exit 1 -fi - - -sed -i".bak" -E -e 's/(FROM quay.io\/operator-framework\/helm-operator)(:.*)?/\1:dev/g' Dockerfile; rm -f Dockerfile.bak -make docker-build IMG="$DEST_IMAGE" - -# If using a kind cluster, load the image into all nodes. -load_image_if_kind "$DEST_IMAGE" - -docker pull "$METRICS_TEST_IMAGE" -# If using a kind cluster, load the metrics test image into all nodes. -load_image_if_kind "$METRICS_TEST_IMAGE" - -OPERATORDIR="$(pwd)" - -deploy_operator -trap_add 'remove_operator' EXIT -test_operator \ No newline at end of file +go test $tests -v -ginkgo.v diff --git a/internal/plugins/helm/v1/scaffolds/templates/makefile.go b/internal/plugins/helm/v1/scaffolds/templates/makefile.go index bad1b8f837..66656c139c 100644 --- a/internal/plugins/helm/v1/scaffolds/templates/makefile.go +++ b/internal/plugins/helm/v1/scaffolds/templates/makefile.go @@ -115,7 +115,7 @@ ifeq (, $(shell which kustomize 2>/dev/null)) mkdir -p bin ;\ curl -sSLo - https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/{{ .KustomizeVersion }}/kustomize_{{ .KustomizeVersion }}_$(OS)_$(ARCH).tar.gz | tar xzf - -C bin/ ;\ } -KUSTOMIZE=./bin/kustomize +KUSTOMIZE=$(realpath ./bin/kustomize) else KUSTOMIZE=$(shell which kustomize) endif @@ -129,7 +129,7 @@ ifeq (, $(shell which helm-operator 2>/dev/null)) mv helm-operator-{{ .HelmOperatorVersion }}-$(ARCHOPER)-$(OSOPER) ./bin/helm-operator ;\ chmod +x ./bin/helm-operator ;\ } -HELM_OPERATOR=./bin/helm-operator +HELM_OPERATOR=$(realpath ./bin/helm-operator) else HELM_OPERATOR=$(shell which helm-operator) endif diff --git a/test/e2e-helm/e2e_helm_cluster_test.go b/test/e2e-helm/e2e_helm_cluster_test.go new file mode 100644 index 0000000000..7de87a3669 --- /dev/null +++ b/test/e2e-helm/e2e_helm_cluster_test.go @@ -0,0 +1,291 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e_helm_test + +import ( + "encoding/base64" + "fmt" + "path/filepath" + "strings" + "time" + + . "github.com/onsi/ginkgo" //nolint:golint + . "github.com/onsi/gomega" //nolint:golint + kbtestutils "sigs.k8s.io/kubebuilder/test/e2e/utils" + + testutils "github.com/operator-framework/operator-sdk/test/internal" +) + +var _ = Describe("Running Helm projects", func() { + var controllerPodName string + + Context("built with operator-sdk", func() { + BeforeEach(func() { + By("enabling Prometheus via the kustomization.yaml") + Expect(kbtestutils.UncommentCode( + filepath.Join(tc.Dir, "config", "default", "kustomization.yaml"), + "#- ../prometheus", "#")).To(Succeed()) + + By("deploying project on the cluster") + err := tc.Make("deploy", "IMG="+tc.ImageName) + Expect(err).Should(Succeed()) + }) + AfterEach(func() { + By("deleting Curl Pod created") + _, _ = tc.Kubectl.Delete(true, "pod", "curl") + + By("deleting CR instances created") + sampleFile := filepath.Join("config", "samples", + fmt.Sprintf("%s_%s_%s.yaml", tc.Group, tc.Version, strings.ToLower(tc.Kind))) + _, _ = tc.Kubectl.Delete(false, "-f", sampleFile) + + By("cleaning up permissions") + _, _ = tc.Kubectl.Command("delete", "clusterrolebinding", + fmt.Sprintf("metrics-%s", tc.TestSuffix)) + + By("undeploy project") + _ = tc.Make("undeploy") + + By("ensuring that the namespace was deleted") + verifyNamespaceDeleted := func() error { + _, err := tc.Kubectl.Command("get", "namespace", tc.Kubectl.Namespace) + if strings.Contains(err.Error(), "(NotFound): namespaces") { + return err + } + return nil + } + Eventually(verifyNamespaceDeleted, 2*time.Minute, time.Second).ShouldNot(Succeed()) + }) + + It("should run correctly in a cluster", func() { + By("checking if the Operator project Pod is running") + verifyControllerUp := func() error { + By("getting the controller-manager pod name") + podOutput, err := tc.Kubectl.Get( + true, + "pods", "-l", "control-plane=controller-manager", + "-o", "go-template={{ range .items }}{{ if not .metadata.deletionTimestamp }}{{ .metadata.name }}"+ + "{{ \"\\n\" }}{{ end }}{{ end }}") + Expect(err).NotTo(HaveOccurred()) + + By("ensuring the created controller-manager Pod") + podNames := kbtestutils.GetNonEmptyLines(podOutput) + if len(podNames) != 1 { + return fmt.Errorf("expect 1 controller pods running, but got %d", len(podNames)) + } + controllerPodName = podNames[0] + Expect(controllerPodName).Should(ContainSubstring("controller-manager")) + + By("checking the controller-manager Pod is running") + status, err := tc.Kubectl.Get( + true, + "pods", controllerPodName, "-o", "jsonpath={.status.phase}") + Expect(err).NotTo(HaveOccurred()) + if status != "Running" { + return fmt.Errorf("controller pod in %s status", status) + } + return nil + } + Eventually(verifyControllerUp, 2*time.Minute, time.Second).Should(Succeed()) + + By("ensuring the created ServiceMonitor for the manager") + _, err := tc.Kubectl.Get( + true, + "ServiceMonitor", + fmt.Sprintf("e2e-%s-controller-manager-metrics-monitor", tc.TestSuffix)) + Expect(err).NotTo(HaveOccurred()) + + By("ensuring the created metrics Service for the manager") + _, err = tc.Kubectl.Get( + true, + "Service", + fmt.Sprintf("e2e-%s-controller-manager-metrics-service", tc.TestSuffix)) + Expect(err).NotTo(HaveOccurred()) + + By("creating an instance of release(CR)") + sampleFile := filepath.Join("config", "samples", + fmt.Sprintf("%s_%s_%s.yaml", tc.Group, tc.Version, strings.ToLower(tc.Kind))) + _, err = tc.Kubectl.Apply(false, "-f", sampleFile) + Expect(err).NotTo(HaveOccurred()) + + By("ensuring the CR gets reconciled and the release was Installed") + managerContainerLogs := func() string { + logOutput, err := tc.Kubectl.Logs(controllerPodName, "-c", "manager") + Expect(err).NotTo(HaveOccurred()) + return logOutput + } + Eventually(managerContainerLogs, time.Minute, time.Second).Should(ContainSubstring("Installed release")) + + By("getting the release name") + releaseName, err := tc.Kubectl.Get( + false, + tc.Kind, "-o", "jsonpath={..status.deployedRelease.name}") + Expect(err).NotTo(HaveOccurred()) + Expect(len(releaseName)).NotTo(BeIdenticalTo(0)) + + By("checking the release(CR) deployment status") + verifyReleaseUp := func() string { + output, err := tc.Kubectl.Command( + "rollout", "status", "deployment", releaseName) + Expect(err).NotTo(HaveOccurred()) + return output + } + Eventually(verifyReleaseUp, time.Minute, time.Second).Should(ContainSubstring("successfully rolled out")) + + By("ensuring the created Service for the release(CR)") + crServiceName, err := tc.Kubectl.Get( + false, + "Service", "-l", fmt.Sprintf("app.kubernetes.io/instance=%s", releaseName), + "-o", "jsonpath={..metadata.name}") + Expect(err).NotTo(HaveOccurred()) + Expect(len(crServiceName)).NotTo(BeIdenticalTo(0)) + + By("scaling deployment replicas to 2") + _, err = tc.Kubectl.Command( + "scale", "deployment", releaseName, "--replicas", "2") + Expect(err).NotTo(HaveOccurred()) + + By("verifying the deployment automatically scales back down to 1") + verifyRelease := func() error { + replicas, err := tc.Kubectl.Get( + false, + "deployment", releaseName, "-o", "jsonpath={..spec.replicas}") + Expect(err).NotTo(HaveOccurred()) + if replicas != "1" { + return fmt.Errorf("release(CR) deployment with %s replicas", replicas) + } + return nil + } + Eventually(verifyRelease, time.Minute, time.Second).Should(Succeed()) + + By("updating replicaCount to 2 in the CR manifest") + testutils.ReplaceInFile(filepath.Join(tc.Dir, sampleFile), "replicaCount: 1", "replicaCount: 2") + + By("applying CR manifest with replicaCount: 2") + _, err = tc.Kubectl.Apply(false, "-f", sampleFile) + Expect(err).NotTo(HaveOccurred()) + + By("ensuring the CR gets reconciled and the release was Upgraded") + managerContainerLogsAfterUpdateCR := func() string { + logOutput, err := tc.Kubectl.Logs(controllerPodName, "-c", "manager") + Expect(err).NotTo(HaveOccurred()) + return logOutput + } + Eventually(managerContainerLogsAfterUpdateCR, time.Minute, time.Second).Should( + ContainSubstring("Upgraded release")) + + By("checking Deployment replicas spec is equals 2") + verifyReleaseUpgrade := func() error { + replicas, err := tc.Kubectl.Get( + false, + "deployment", releaseName, "-o", "jsonpath={..spec.replicas}") + Expect(err).NotTo(HaveOccurred()) + if replicas != "2" { + return fmt.Errorf("release(CR) deployment with %s replicas", replicas) + } + return nil + } + Eventually(verifyReleaseUpgrade, time.Minute, time.Second).Should(Succeed()) + + By("granting permissions to access the metrics and read the token") + _, err = tc.Kubectl.Command( + "create", + "clusterrolebinding", + fmt.Sprintf("metrics-%s", tc.TestSuffix), + fmt.Sprintf("--clusterrole=e2e-%s-metrics-reader", tc.TestSuffix), + fmt.Sprintf("--serviceaccount=%s:default", tc.Kubectl.Namespace)) + Expect(err).NotTo(HaveOccurred()) + + By("getting the token") + b64Token, err := tc.Kubectl.Get( + true, + "secrets", + "-o=jsonpath={.items[0].data.token}") + Expect(err).NotTo(HaveOccurred()) + token, err := base64.StdEncoding.DecodeString(strings.TrimSpace(b64Token)) + Expect(err).NotTo(HaveOccurred()) + Expect(token).NotTo(HaveLen(0)) + + By("creating a pod with curl image") + // todo: the flag --generator=run-pod/v1 is deprecated, however, shows that besides + // it should not make any difference and work locally successfully when the flag is removed + // travis has been failing and the curl pod is not found when the flag is not used + cmdOpts := []string{ + "run", "--generator=run-pod/v1", "curl", "--image=curlimages/curl:7.68.0", "--restart=OnFailure", "--", + "curl", "-v", "-k", "-H", fmt.Sprintf(`Authorization: Bearer %s`, token), + fmt.Sprintf("https://e2e-%v-controller-manager-metrics-service.e2e-%v-system.svc:8443/metrics", + tc.TestSuffix, tc.TestSuffix), + } + _, err = tc.Kubectl.CommandInNamespace(cmdOpts...) + Expect(err).NotTo(HaveOccurred()) + + By("validating the curl pod running as expected") + verifyCurlUp := func() error { + // Validate pod status + status, err := tc.Kubectl.Get( + true, + "pods", "curl", "-o", "jsonpath={.status.phase}") + Expect(err).NotTo(HaveOccurred()) + if status != "Completed" && status != "Succeeded" { + return fmt.Errorf("curl pod in %s status", status) + } + return nil + } + Eventually(verifyCurlUp, 4*time.Minute, time.Second).Should(Succeed()) + + By("checking metrics endpoint serving as expected") + getCurlLogs := func() string { + logOutput, err := tc.Kubectl.Logs("curl") + Expect(err).NotTo(HaveOccurred()) + return logOutput + } + Eventually(getCurlLogs, time.Minute, time.Second).Should(ContainSubstring("< HTTP/2 200")) + + By("getting the CR namespace token") + crNamespace, err := tc.Kubectl.Get( + false, + tc.Kind, + fmt.Sprintf("%s-sample", strings.ToLower(tc.Kind)), + "-o=jsonpath={..metadata.namespace}") + Expect(err).NotTo(HaveOccurred()) + Expect(crNamespace).NotTo(HaveLen(0)) + + By("ensuring the operator metrics contains a `resource_created_at` metric for the CR") + metricExportedCR := fmt.Sprintf("resource_created_at_seconds{group=\"%s\","+ + "kind=\"%s\","+ + "name=\"%s-sample\","+ + "namespace=\"%s\","+ + "version=\"%s\"}", + fmt.Sprintf("%s.%s", tc.Group, tc.Domain), + tc.Kind, + strings.ToLower(tc.Kind), + crNamespace, + tc.Version) + Eventually(getCurlLogs, time.Minute, time.Second).Should(ContainSubstring(metricExportedCR)) + + By("deleting CR manifest") + _, err = tc.Kubectl.Delete(false, "-f", sampleFile) + Expect(err).NotTo(HaveOccurred()) + + By("ensuring the CR gets reconciled and the release was Uninstalled") + managerContainerLogsAfterDeleteCR := func() string { + logOutput, err := tc.Kubectl.Logs(controllerPodName, "-c", "manager") + Expect(err).NotTo(HaveOccurred()) + return logOutput + } + Eventually(managerContainerLogsAfterDeleteCR, time.Minute, time.Second).Should(ContainSubstring("Uninstalled release")) + }) + }) +}) diff --git a/test/e2e-helm/e2e_helm_local_test.go b/test/e2e-helm/e2e_helm_local_test.go new file mode 100644 index 0000000000..2a89f2c00a --- /dev/null +++ b/test/e2e-helm/e2e_helm_local_test.go @@ -0,0 +1,50 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e_helm_test + +import ( + "os/exec" + + . "github.com/onsi/ginkgo" //nolint:golint + . "github.com/onsi/gomega" //nolint:golint +) + +var _ = Describe("Running Helm projects", func() { + Context("built with operator-sdk", func() { + + BeforeEach(func() { + By("installing CRD's") + err := tc.Make("install") + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + By("uninstalling CRD's") + err := tc.Make("uninstall") + Expect(err).NotTo(HaveOccurred()) + }) + + It("should run correctly locally", func() { + By("running the project") + cmd := exec.Command("make", "run") + err := cmd.Start() + Expect(err).NotTo(HaveOccurred()) + + By("killing the project") + err = cmd.Process.Kill() + Expect(err).NotTo(HaveOccurred()) + }) + }) +}) diff --git a/test/e2e-helm/e2e_helm_olm_test.go b/test/e2e-helm/e2e_helm_olm_test.go new file mode 100644 index 0000000000..735950bcdf --- /dev/null +++ b/test/e2e-helm/e2e_helm_olm_test.go @@ -0,0 +1,39 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e_helm_test + +import ( + "path/filepath" + + . "github.com/onsi/ginkgo" //nolint:golint + + testutils "github.com/operator-framework/operator-sdk/test/internal" +) + +var _ = PDescribe("Integrating Helm Projects with OLM", func() { + Context("with operator-sdk", func() { + BeforeEach(func() { + By("Turning off interactive prompts for all generation tasks.") + replace := "operator-sdk generate kustomize manifests" + testutils.ReplaceInFile(filepath.Join(tc.Dir, "Makefile"), replace, replace+" --interactive=false") + }) + + AfterEach(func() { + }) + + It("Should allow generate the OLM bundle and run it", func() { + }) + }) +}) diff --git a/test/e2e-helm/e2e_helm_suite_test.go b/test/e2e-helm/e2e_helm_suite_test.go new file mode 100644 index 0000000000..1a8bff0a6b --- /dev/null +++ b/test/e2e-helm/e2e_helm_suite_test.go @@ -0,0 +1,144 @@ +// Copyright 2020 The Operator-SDK Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package e2e_helm_test + +import ( + "path/filepath" + "strings" + "testing" + "time" + + . "github.com/onsi/ginkgo" //nolint:golint + . "github.com/onsi/gomega" //nolint:golint + + testutils "github.com/operator-framework/operator-sdk/test/internal" + "github.com/operator-framework/operator-sdk/version" +) + +// TestE2EHelm ensures the Helm projects built with the SDK tool by using its binary. +func TestE2EHelm(t *testing.T) { + if testing.Short() { + t.Skip("skipping Operator SDK E2E Helm Suite testing in short mode") + } + RegisterFailHandler(Fail) + RunSpecs(t, "E2EHelm Suite") +} + +var ( + tc testutils.TestContext + // isPromethuesManagedBySuite is true when the suite tests is installing/uninstalling the Prometheus + isPromethuesManagedBySuite = true + // isOLMManagedBySuite is true when the suite tests is installing/uninstalling the OLM + isOLMManagedBySuite = true + // kubectx stores the k8s context from where the tests are running + kubectx string +) + +// BeforeSuite run before any specs are run to perform the required actions for all e2e Helm tests. +var _ = BeforeSuite(func(done Done) { + var err error + + By("creating a new test context") + tc, err = testutils.NewTestContext("GO111MODULE=on") + Expect(err).NotTo(HaveOccurred()) + Expect(tc.Prepare()).To(Succeed()) + + By("checking the cluster type") + kubectx, err = tc.Kubectl.Command("config", "current-context") + Expect(err).Should(Succeed()) + + By("checking API resources applied on Cluster") + output, err := tc.Kubectl.Command("api-resources") + Expect(err).NotTo(HaveOccurred()) + if strings.Contains(output, "servicemonitors") { + isPromethuesManagedBySuite = false + } + if strings.Contains(output, "clusterserviceversions") { + isOLMManagedBySuite = false + } + + if isPromethuesManagedBySuite { + By("installing Prometheus") + Expect(tc.InstallPrometheusOperManager()).To(Succeed()) + + By("ensuring provisioned Prometheus Manager Service") + Eventually(func() error { + _, err := tc.Kubectl.Get( + false, + "Service", "prometheus-operator") + return err + }, 3*time.Minute, time.Second).Should(Succeed()) + } + + if isOLMManagedBySuite { + By("installing OLM") + Expect(tc.InstallOLM()).To(Succeed()) + } + + By("initializing a Helm project") + err = tc.Init( + "--plugins", "helm.operator-sdk.io/v1", + "--project-version", "3-alpha", + "--domain", tc.Domain) + Expect(err).Should(Succeed()) + + By("creating an API definition") + err = tc.CreateAPI( + "--group", tc.Group, + "--version", tc.Version, + "--kind", tc.Kind) + Expect(err).Should(Succeed()) + + By("replacing project Dockerfile to use Helm base image with the dev tag") + version := strings.TrimSuffix(version.Version, "+git") + testutils.ReplaceInFile(filepath.Join(tc.Dir, "Dockerfile"), version, "dev") + + By("checking the kustomize setup") + err = tc.Make("kustomize") + Expect(err).Should(Succeed()) + + By("building the project image") + err = tc.Make("docker-build", "IMG="+tc.ImageName) + Expect(err).Should(Succeed()) + + if isRunningOnKind() { + By("loading the project image into Kind cluster") + err = tc.LoadImageToKindCluster() + Expect(err).Should(Succeed()) + } + + close(done) +}, 360) + +// AfterSuite run after all the specs have run, regardless of whether any tests have failed to ensures that +// all be cleaned up +var _ = AfterSuite(func() { + if isPromethuesManagedBySuite { + By("uninstalling Prometheus") + tc.UninstallPrometheusOperManager() + } + if isOLMManagedBySuite { + By("uninstalling OLM") + tc.UninstallOLM() + } + + By("destroying container image and work dir") + tc.Destroy() +}) + +// isRunningOnKind returns true when the tests are executed in a Kind Cluster +func isRunningOnKind() bool { + return strings.Contains(kubectx, "kind") +} diff --git a/test/e2e/e2e_suite.go b/test/e2e/e2e_suite.go index 36b36819c5..5ae3d7cb76 100644 --- a/test/e2e/e2e_suite.go +++ b/test/e2e/e2e_suite.go @@ -19,8 +19,6 @@ package e2e import ( "bytes" "fmt" - "io/ioutil" - "os" "os/exec" "path" "path/filepath" @@ -167,7 +165,7 @@ var _ = Describe("operator-sdk", func() { By("generating the operator bundle") // Turn off interactive prompts for all generation tasks. replace := "operator-sdk generate kustomize manifests" - replaceInFile(filepath.Join(tc.Dir, "Makefile"), replace, replace+" --interactive=false") + testutils.ReplaceInFile(filepath.Join(tc.Dir, "Makefile"), replace, replace+" --interactive=false") err = tc.Make("bundle") Expect(err).NotTo(HaveOccurred()) @@ -214,14 +212,3 @@ var _ = Describe("operator-sdk", func() { }) }) }) - -// replaceInFile replaces all instances of old with new in the file at path. -func replaceInFile(path, old, new string) { - info, err := os.Stat(path) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - b, err := ioutil.ReadFile(path) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - s := strings.Replace(string(b), old, new, -1) - err = ioutil.WriteFile(path, []byte(s), info.Mode()) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) -} diff --git a/test/internal/utils.go b/test/internal/utils.go index b63c544e3e..3cd9f0cb98 100644 --- a/test/internal/utils.go +++ b/test/internal/utils.go @@ -16,9 +16,13 @@ package internal import ( "fmt" + "io/ioutil" + "os" "os/exec" + "strings" . "github.com/onsi/ginkgo" //nolint:golint + . "github.com/onsi/gomega" //nolint:golint kbtestutils "sigs.k8s.io/kubebuilder/test/e2e/utils" ) @@ -53,3 +57,14 @@ func (tc TestContext) UninstallOLM() { func (tc TestContext) KustomizeBuild(dir string) ([]byte, error) { return tc.Run(exec.Command("kustomize", "build", dir)) } + +// ReplaceInFile replaces all instances of old with new in the file at path. +func ReplaceInFile(path, old, new string) { + info, err := os.Stat(path) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + b, err := ioutil.ReadFile(path) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + s := strings.Replace(string(b), old, new, -1) + err = ioutil.WriteFile(path, []byte(s), info.Mode()) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) +}