diff --git a/.travis.yml b/.travis.yml index 2c7e6c3a9d9..22fff5dc11f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -132,9 +132,9 @@ jobs: name: Subcommands on Kubernetes script: make test-subcommand - # Build and test go + # Build and test go for legacy project layouts - <<: *test - name: Go on Kubernetes + name: Go for legacy project layouts on Kubernetes before_script: - (cd / && go get github.com/mattn/goveralls) script: @@ -143,6 +143,23 @@ jobs: - make test-e2e-go - make test-integration + # Build and test go for new project layouts + - name: Go e2e tests for new project layouts + before_install: + # hack/ci/check-doc-only-update.sh needs to be sourced so + # that it can properly exit the test early with success + - source hack/ci/check-doc-only-update.sh + script: + - make test-e2e-go-new + after_success: + - echo "E2E tests passed" + after_failure: + - echo "E2E tests failed" + - kubectl get all --all-namespaces + - kubectl get events --all-namespaces --field-selector=type=Warning + services: + - docker + # Build and test helm - <<: *test name: Helm on Kubernetes diff --git a/Makefile b/Makefile index a3ccd08d34c..318132ed39a 100644 --- a/Makefile +++ b/Makefile @@ -254,14 +254,17 @@ test-subcommand-scorecard: test-subcommand-olm-install: ./hack/tests/subcommand-olm-install.sh -# E2E and integration tests. -.PHONY: test-e2e test-e2e-go test-e2e-ansible test-e2e-ansible-molecule test-e2e-helm test-integration +# E2E tests. +.PHONY: test-e2e test-e2e-go test-e2e-go-new test-e2e-ansible test-e2e-ansible-molecule test-e2e-helm -test-e2e: test-e2e-go test-e2e-ansible test-e2e-ansible-molecule test-e2e-helm ## Run the e2e tests +test-e2e: test-e2e-go test-e2e-go-new test-e2e-ansible test-e2e-ansible-molecule test-e2e-helm ## Run the e2e tests test-e2e-go: ./hack/tests/e2e-go.sh $(ARGS) +test-e2e-go-new: + K8S_VERSION=$(K8S_VERSION) ./hack/tests/e2e-go-new.sh + test-e2e-ansible: image-build-ansible ./hack/tests/e2e-ansible.sh @@ -271,5 +274,8 @@ test-e2e-ansible-molecule: image-build-ansible test-e2e-helm: image-build-helm ./hack/tests/e2e-helm.sh -test-integration: +# Integration tests. +.PHONY: test-integration + +test-integration: ## Run integration tests ./hack/tests/integration.sh diff --git a/hack/lib/common.sh b/hack/lib/common.sh index 72d570bf701..83dc6315f6a 100644 --- a/hack/lib/common.sh +++ b/hack/lib/common.sh @@ -1,9 +1,19 @@ #!/usr/bin/env bash -function log() { printf '%s\n' "$*"; } -function error() { error_text "ERROR:" $* >&2; } -function fatal() { error "$@"; exit 1; } - +# Skip fetching and untaring the tools by setting the SKIP_FETCH_TOOLS variable +# in your environment to any value: +# +# $ SKIP_FETCH_TOOLS=1 ./test.sh +# +# If you skip fetching tools, this script will use the tools already on your +# machine, but rebuild the operator-sdk binary. +SKIP_FETCH_TOOLS=${SKIP_FETCH_TOOLS:-""} +# Current version of the 'kind' binary. Update this when a new breaking release +# is made for a docker.io/kindest/node:${K8S_VERSION} image. +KIND_VERSION="v0.8.1" +# ENVTEST_TOOLS_VERSION is the version of k8s server tarballs used for envtest. +# TODO: use K8S_VERSION once we start building our own server binary tarballs. +ENVTEST_TOOLS_VERSION="1.16.4" # Turn colors in this script off by setting the NO_COLOR variable in your # environment to any value: NO_COLOR=${NO_COLOR:-""} @@ -17,6 +27,14 @@ else reset_color='' fi +# Roots used by tests. +tmp_root=/tmp +tmp_sdk_root=$tmp_root/operator-sdk + +function log() { printf '%s\n' "$*"; } +function error() { error_text "ERROR:" $* >&2; } +function fatal() { error "$@"; exit 1; } + function header_text { echo "$header_color$*$reset_color" } @@ -37,3 +55,79 @@ function is_installed { function install_service_monitor_crd { kubectl apply -f https://raw.githubusercontent.com/coreos/prometheus-operator/release-0.35/example/prometheus-operator-crd/monitoring.coreos.com_servicemonitors.yaml } + +# prepare the e2e test staging dir, containing test tools (SKIP_FETCH_TOOLS aware). +function prepare_staging_dir { + + header_text "preparing staging dir $1" + + if [[ -z "$SKIP_FETCH_TOOLS" ]]; then + rm -rf "$1" + else + rm -f "$1/bin/operator-sdk" + fi + + mkdir -p "$1" +} + +# Fetch k8s API gen tools and make it available under $1/bin. +function fetch_tools { + if [[ -z "$SKIP_FETCH_TOOLS" ]]; then + fetch_envtest_tools $@ + install_kind $@ + fi +} + +# Fetch tools required for envtest. +function fetch_envtest_tools { + + # TODO: make our own tarball containing envtest binaries: etcd, kubectl, kube-apiserver + # + # To get k8s server binaries: + # server_tar="kubernetes-server-$(go env GOOS)-$(go env GOARCH).tar.gz" + # url=https://dl.k8s.io/$K8S_VERSION/$server_tar + # curl -fL --retry 3 --keepalive-time 2 "${url}" -o "${tmp_sdk_root}/${server_tar}" + # tar -zxvf "${tmp_sdk_root}/${server_tar}" + + local tools_archive_name="kubebuilder-tools-${ENVTEST_TOOLS_VERSION}-$(go env GOOS)-$(go env GOARCH).tar.gz" + local tools_download_url="https://storage.googleapis.com/kubebuilder-tools/$tools_archive_name" + + local tools_archive_path="$1/$tools_archive_name" + if [[ ! -f $tools_archive_path ]]; then + header_text "fetching envtest tools" + curl -sSLo "$tools_archive_path" $tools_download_url + else + header_text "using existing envtest tools in $tools_archive_path" + fi + tar -zvxf "$tools_archive_path" -C "$1/" --strip-components=1 +} + +# Set up test and envtest vars +function setup_envs { + header_text "setting up env vars" + + export PATH="$1"/bin:$PATH + export TEST_ASSET_KUBECTL="$1"/bin/kubectl + export TEST_ASSET_KUBE_APISERVER="$1"/bin/kube-apiserver + export TEST_ASSET_ETCD="$1"/bin/etcd +} + +# Build the operator-sdk binary. +function build_sdk { + header_text "building operator-sdk" + + GO111MODULE=on make build/operator-sdk + mv ./build/operator-sdk "$1"/bin/operator-sdk +} + +# Install the 'kind' binary at version $KIND_VERSION. +function install_kind { + + local kind_path="${1}/bin/kind" + + header_text "installing kind $KIND_VERSION" + local kind_binary="kind-$(go env GOOS)-$(go env GOARCH)" + local kind_url="https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VERSION}/$kind_binary" + curl -sSLo "$kind_path" $kind_url + chmod +x "$kind_path" +} diff --git a/hack/lib/image_lib.sh b/hack/lib/image_lib.sh index 1c0cbeab3db..4438b55372d 100755 --- a/hack/lib/image_lib.sh +++ b/hack/lib/image_lib.sh @@ -120,7 +120,7 @@ function is_latest_tag() { # function load_image_if_kind() { if [[ "$(kubectl config current-context)" == "kind-kind" ]]; then - if which kind 2>/dev/null; then + if is_installed kind; then kind load docker-image "$1" fi fi diff --git a/hack/tests/e2e-go-new.sh b/hack/tests/e2e-go-new.sh new file mode 100755 index 00000000000..bb84e653b60 --- /dev/null +++ b/hack/tests/e2e-go-new.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# remove running containers on exit +function cleanup() { + kind delete cluster +} + +set -o errexit +set -o nounset +set -o pipefail + +source ./hack/lib/common.sh +source ./hack/lib/test_lib.sh + +test_dir=./test +tests=$test_dir/e2e-new + +export TRACE=1 +export GO111MODULE=on + +: ${K8S_VERSION:?"must be set"} + +prepare_staging_dir $tmp_sdk_root +fetch_tools $tmp_sdk_root +# These envtest environment variables are required for the default unit tests +# scaffolded in the test operator project. No e2e tests currently use envtest. +setup_envs $tmp_sdk_root +build_sdk $tmp_sdk_root + +# Create a cluster of version $K8S_VERSION. +kind create cluster -v 4 --retain --wait=1m \ + --config $test_dir/kind-config.yaml \ + --image=kindest/node:$K8S_VERSION + +kind export kubeconfig + +kubectl cluster-info + +docker pull gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 +kind load docker-image gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 + +trap_add cleanup EXIT +go test -v $tests diff --git a/test/e2e-new/e2e_suite.go b/test/e2e-new/e2e_suite.go new file mode 100644 index 00000000000..bf70e5b3321 --- /dev/null +++ b/test/e2e-new/e2e_suite.go @@ -0,0 +1,139 @@ +// 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. + +// Modified from https://github.com/kubernetes-sigs/kubebuilder/tree/39224f0/test/e2e/v3 + +package e2e + +import ( + "fmt" + "path/filepath" + "strings" + "time" + + . "github.com/onsi/ginkgo" //nolint:golint + . "github.com/onsi/gomega" //nolint:golint + + "github.com/operator-framework/operator-sdk/test/e2e-new/utils" +) + +var _ = Describe("operator-sdk", func() { + Context("with the new project layout", func() { + var tc *utils.TestContext + BeforeEach(func() { + + By("creating a new test context") + var err error + tc, err = utils.NewTestContext("operator-sdk", "GO111MODULE=on") + Expect(err).NotTo(HaveOccurred()) + Expect(tc.Prepare()).To(Succeed()) + }) + + AfterEach(func() { + By("cleaning up created API objects during test process") + tc.CleanupManifests(filepath.Join("config", "default")) + + By("removing container image and work dir") + tc.Destroy() + }) + + It("should generate a runnable project", func() { + var controllerPodName string + By("initializing a project") + err := tc.Init( + "--project-version", "3-alpha", + "--domain", tc.Domain, + "--fetch-deps=false") + Expect(err).Should(Succeed()) + + By("creating an API definition") + err = tc.CreateAPI( + "--group", tc.Group, + "--version", tc.Version, + "--kind", tc.Kind, + "--namespaced", + "--resource", + "--controller", + "--make=false") + Expect(err).Should(Succeed()) + + By("implementing the API") + Expect(utils.InsertCode( + filepath.Join(tc.Dir, "api", tc.Version, fmt.Sprintf("%s_types.go", strings.ToLower(tc.Kind))), + fmt.Sprintf(`type %sSpec struct { +`, tc.Kind), + ` // +optional + Count int `+"`"+`json:"count,omitempty"`+"`"+` +`)).Should(Succeed()) + + By("building the operator image") + err = tc.Make("docker-build", "IMG="+tc.ImageName) + Expect(err).Should(Succeed()) + + By("loading the operator image into the test cluster") + err = tc.LoadImageToKindCluster() + Expect(err).Should(Succeed()) + + By("deploying the controller manager") + err = tc.Make("deploy", "IMG="+tc.ImageName) + Expect(err).Should(Succeed()) + + By("ensuring the controller-manager pod is running as expected") + verifyControllerUp := func() error { + // Get 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()) + podNames := utils.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")) + + // Validate pod status + 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, time.Minute, time.Second).Should(Succeed()) + + By("creating an instance of CR") + // currently controller-runtime doesn't provide a readiness probe, we retry a few times + // we can change it to probe the readiness endpoint after CR supports it. + sampleFile := filepath.Join("config", "samples", + fmt.Sprintf("%s_%s_%s.yaml", tc.Group, tc.Version, strings.ToLower(tc.Kind))) + Eventually(func() error { + _, err = tc.Kubectl.Apply(true, "-f", sampleFile) + return err + }, time.Minute, time.Second).Should(Succeed()) + + By("ensuring the created resource object gets reconciled in controller") + 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("Successfully Reconciled")) + }) + }) +}) diff --git a/test/e2e-new/e2e_test.go b/test/e2e-new/e2e_test.go new file mode 100644 index 00000000000..31e78db348e --- /dev/null +++ b/test/e2e-new/e2e_test.go @@ -0,0 +1,29 @@ +// 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. + +// Modified from https://github.com/kubernetes-sigs/kubebuilder/tree/39224f0/test/e2e/v3 + +package e2e + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestE2E(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Operator SDK e2e suite") +} diff --git a/test/e2e-new/utils/kubectl.go b/test/e2e-new/utils/kubectl.go new file mode 100644 index 00000000000..527ef3df924 --- /dev/null +++ b/test/e2e-new/utils/kubectl.go @@ -0,0 +1,94 @@ +// 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. + +// Modified from https://github.com/kubernetes-sigs/kubebuilder/tree/39224f0/test/e2e/v3 + +package utils + +import ( + "errors" + "os/exec" + "strings" +) + +// TODO: remove this file once kubernetes-sigs/kubebuilder#1520 is merged + +// Kubectl contains context to run kubectl commands +type Kubectl struct { + *CmdContext + Namespace string +} + +// Command is a general func to run kubectl commands +func (k *Kubectl) Command(cmdOptions ...string) (string, error) { + cmd := exec.Command("kubectl", cmdOptions...) + output, err := k.Run(cmd) + return string(output), err +} + +// WithInput is a general func to run kubectl commands with input +func (k *Kubectl) WithInput(stdinInput string) *Kubectl { + k.Stdin = strings.NewReader(stdinInput) + return k +} + +// CommandInNamespace is a general func to run kubectl commands in the namespace +func (k *Kubectl) CommandInNamespace(cmdOptions ...string) (string, error) { + if len(k.Namespace) == 0 { + return "", errors.New("namespace should not be empty") + } + return k.Command(append([]string{"-n", k.Namespace}, cmdOptions...)...) +} + +// Apply is a general func to run kubectl apply commands +func (k *Kubectl) Apply(inNamespace bool, cmdOptions ...string) (string, error) { + ops := append([]string{"apply"}, cmdOptions...) + if inNamespace { + return k.CommandInNamespace(ops...) + } + return k.Command(ops...) +} + +// Get is a func to run kubectl get commands +func (k *Kubectl) Get(inNamespace bool, cmdOptions ...string) (string, error) { + ops := append([]string{"get"}, cmdOptions...) + if inNamespace { + return k.CommandInNamespace(ops...) + } + return k.Command(ops...) +} + +// Delete is a func to run kubectl delete commands +func (k *Kubectl) Delete(inNamespace bool, cmdOptions ...string) (string, error) { + ops := append([]string{"delete"}, cmdOptions...) + if inNamespace { + return k.CommandInNamespace(ops...) + } + return k.Command(ops...) +} + +// Logs is a func to run kubectl logs commands +func (k *Kubectl) Logs(cmdOptions ...string) (string, error) { + ops := append([]string{"logs"}, cmdOptions...) + return k.CommandInNamespace(ops...) +} + +// Wait is a func to run kubectl wait commands +func (k *Kubectl) Wait(inNamespace bool, cmdOptions ...string) (string, error) { + ops := append([]string{"wait"}, cmdOptions...) + if inNamespace { + return k.CommandInNamespace(ops...) + } + return k.Command(ops...) +} diff --git a/test/e2e-new/utils/test_context.go b/test/e2e-new/utils/test_context.go new file mode 100644 index 00000000000..aa84439749d --- /dev/null +++ b/test/e2e-new/utils/test_context.go @@ -0,0 +1,172 @@ +// 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. + +// Modified from https://github.com/kubernetes-sigs/kubebuilder/tree/39224f0/test/e2e/v3 + +package utils + +import ( + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + + . "github.com/onsi/ginkgo" //nolint:golint +) + +// TODO: remove this file once kubernetes-sigs/kubebuilder#1520 is merged + +// TestContext specified to run e2e tests +type TestContext struct { + *CmdContext + TestSuffix string + Domain string + Group string + Version string + Kind string + Resources string + ImageName string + BinName string + Kubectl *Kubectl +} + +// NewTestContext init with a random suffix for test TestContext stuff, +// to avoid conflict when running tests synchronously. +func NewTestContext(binName string, env ...string) (*TestContext, error) { + testSuffix, err := randomSuffix() + if err != nil { + return nil, err + } + + testGroup := "bar" + testSuffix + path, err := filepath.Abs("e2e-" + testSuffix) + if err != nil { + return nil, err + } + + cc := &CmdContext{ + Env: env, + Dir: path, + } + + return &TestContext{ + TestSuffix: testSuffix, + Domain: "example.com" + testSuffix, + Group: testGroup, + Version: "v1alpha1", + Kind: "Foo" + testSuffix, + Resources: "foo" + testSuffix + "s", + ImageName: "e2e-test/controller-manager:" + testSuffix, + BinName: binName, + CmdContext: cc, + Kubectl: &Kubectl{ + Namespace: fmt.Sprintf("e2e-%s-system", testSuffix), + CmdContext: cc, + }, + }, nil +} + +// Prepare prepare a work directory for testing +func (tc *TestContext) Prepare() error { + fmt.Fprintf(GinkgoWriter, "preparing testing directory: %s\n", tc.Dir) + return os.MkdirAll(tc.Dir, 0755) +} + +// CleanupManifests is a helper func to run kustomize build and pipe the output to kubectl delete -f - +func (tc *TestContext) CleanupManifests(dir string) { + cmd := exec.Command("kustomize", "build", dir) + output, err := tc.Run(cmd) + if err != nil { + fmt.Fprintf(GinkgoWriter, "warning: error when running kustomize build: %v\n", err) + } + if _, err := tc.Kubectl.WithInput(string(output)).Command("delete", "-f", "-"); err != nil { + fmt.Fprintf(GinkgoWriter, "warning: error when running kubectl delete -f -: %v\n", err) + } +} + +// Init is for running ` init` +func (tc *TestContext) Init(initOptions ...string) error { + initOptions = append([]string{"init"}, initOptions...) + cmd := exec.Command(tc.BinName, initOptions...) + _, err := tc.Run(cmd) + return err +} + +// CreateAPI is for running ` create api` +func (tc *TestContext) CreateAPI(resourceOptions ...string) error { + resourceOptions = append([]string{"create", "api"}, resourceOptions...) + cmd := exec.Command(tc.BinName, resourceOptions...) + _, err := tc.Run(cmd) + return err +} + +// CreateWebhook is for running ` create webhook` +func (tc *TestContext) CreateWebhook(resourceOptions ...string) error { + resourceOptions = append([]string{"create", "webhook"}, resourceOptions...) + cmd := exec.Command(tc.BinName, resourceOptions...) + _, err := tc.Run(cmd) + return err +} + +// Make is for running `make` with various targets +func (tc *TestContext) Make(makeOptions ...string) error { + cmd := exec.Command("make", makeOptions...) + _, err := tc.Run(cmd) + return err +} + +// Destroy is for cleaning up the docker images for testing +func (tc *TestContext) Destroy() { + //nolint:gosec + cmd := exec.Command("docker", "rmi", "-f", tc.ImageName) + if _, err := tc.Run(cmd); err != nil { + fmt.Fprintf(GinkgoWriter, "warning: error when removing the local image: %v\n", err) + } + if err := os.RemoveAll(tc.Dir); err != nil { + fmt.Fprintf(GinkgoWriter, "warning: error when removing the word dir: %v\n", err) + } +} + +// LoadImageToKindCluster loads a local docker image to the kind cluster +func (tc *TestContext) LoadImageToKindCluster() error { + kindOptions := []string{"load", "docker-image", tc.ImageName} + cmd := exec.Command("kind", kindOptions...) + _, err := tc.Run(cmd) + return err +} + +// CmdContext provides context for command execution +type CmdContext struct { + // environment variables in k=v format. + Env []string + Dir string + Stdin io.Reader +} + +// Run executes the provided command within this context +func (cc *CmdContext) Run(cmd *exec.Cmd) ([]byte, error) { + cmd.Dir = cc.Dir + cmd.Env = append(os.Environ(), cc.Env...) + cmd.Stdin = cc.Stdin + command := strings.Join(cmd.Args, " ") + fmt.Fprintf(GinkgoWriter, "running: %s\n", command) + output, err := cmd.CombinedOutput() + if err != nil { + return output, fmt.Errorf("%s failed with error: %s", command, string(output)) + } + + return output, nil +} diff --git a/test/e2e-new/utils/util.go b/test/e2e-new/utils/util.go new file mode 100644 index 00000000000..7cdfd88c2b2 --- /dev/null +++ b/test/e2e-new/utils/util.go @@ -0,0 +1,103 @@ +// 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. + +// Modified from https://github.com/kubernetes-sigs/kubebuilder/tree/39224f0/test/e2e/v3 + +package utils + +import ( + "bytes" + "crypto/rand" + "io/ioutil" + "math/big" + "strings" +) + +// TODO: remove this file once kubernetes-sigs/kubebuilder#1520 is merged + +// randomSuffix returns a 4-letter string. +func randomSuffix() (string, error) { + source := []rune("abcdefghijklmnopqrstuvwxyz") + res := make([]rune, 4) + for i := range res { + bi := new(big.Int) + r, err := rand.Int(rand.Reader, bi.SetInt64(int64(len(source)))) + if err != nil { + return "", err + } + res[i] = source[r.Int64()] + } + return string(res), nil +} + +// GetNonEmptyLines converts given command output string into individual objects +// according to line breakers, and ignores the empty elements in it. +func GetNonEmptyLines(output string) []string { + var res []string + elements := strings.Split(output, "\n") + for _, element := range elements { + if element != "" { + res = append(res, element) + } + } + + return res +} + +// InsertCode searches target content in the file and insert `toInsert` after the target. +func InsertCode(filename, target, code string) error { + contents, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + idx := strings.Index(string(contents), target) + out := string(contents[:idx+len(target)]) + code + string(contents[idx+len(target):]) + return ioutil.WriteFile(filename, []byte(out), 0644) +} + +// UncommentCode searches for target in the file and remove the comment prefix +// of the target content. The target content may span multiple lines. +func UncommentCode(filename, target, prefix string) error { + content, err := ioutil.ReadFile(filename) + if err != nil { + return err + } + strContent := string(content) + + idx := strings.Index(strContent, target) + if idx < 0 { + return nil + } + + out := new(bytes.Buffer) + _, err = out.Write(content[:idx]) + if err != nil { + return err + } + + strs := strings.Split(target, "\n") + for _, str := range strs { + _, err := out.WriteString(strings.TrimPrefix(str, prefix) + "\n") + if err != nil { + return err + } + } + + _, err = out.Write(content[idx+len(target):]) + if err != nil { + return err + } + + return ioutil.WriteFile(filename, out.Bytes(), 0644) +} diff --git a/test/kind-config.yaml b/test/kind-config.yaml new file mode 100644 index 00000000000..f4bf78cb4a4 --- /dev/null +++ b/test/kind-config.yaml @@ -0,0 +1,7 @@ +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +nodes: + - role: control-plane + - role: worker + - role: worker + - role: worker