diff --git a/CHANGELOG.md b/CHANGELOG.md index edcd7262d5c..1bb05fcd1a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Added - A new command [`operator-sdk print-deps`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#print-deps) which prints Golang packages and versions expected by the current Operator SDK version. Supplying `--as-file` prints packages and versions in Gopkg.toml format. ([#772](https://github.com/operator-framework/operator-sdk/pull/772)) +- Add [`no-setup`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#flags-9) flag to `test local` subcommand ([#770](https://github.com/operator-framework/operator-sdk/pull/770)) - Add [`image`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#flags-9) flag to `test local` subcommand ([#768](https://github.com/operator-framework/operator-sdk/pull/768)) ### Bug fixes diff --git a/commands/operator-sdk/cmd/test/local.go b/commands/operator-sdk/cmd/test/local.go index d1568b8f55a..1ed24c06e87 100644 --- a/commands/operator-sdk/cmd/test/local.go +++ b/commands/operator-sdk/cmd/test/local.go @@ -45,6 +45,7 @@ type testLocalConfig struct { namespacedManPath string goTestFlags string namespace string + noSetup bool image string } @@ -61,6 +62,7 @@ func NewTestLocalCmd() *cobra.Command { testCmd.Flags().StringVar(&tlConfig.namespacedManPath, "namespaced-manifest", "", "Path to manifest for per-test, namespaced resources (e.g. RBAC and Operator manifest)") testCmd.Flags().StringVar(&tlConfig.goTestFlags, "go-test-flags", "", "Additional flags to pass to go test") testCmd.Flags().StringVar(&tlConfig.namespace, "namespace", "", "If non-empty, single namespace to run tests in") + testCmd.Flags().BoolVar(&tlConfig.noSetup, "no-setup", false, "Disable test resource creation") testCmd.Flags().StringVar(&tlConfig.image, "image", "", "Use a different operator image from the one specified in the namespaced manifest") return testCmd @@ -70,11 +72,14 @@ func testLocalFunc(cmd *cobra.Command, args []string) { if len(args) != 1 { log.Fatal("operator-sdk test local requires exactly 1 argument") } + if (tlConfig.noSetup && tlConfig.globalManPath != "") || (tlConfig.noSetup && tlConfig.namespacedManPath != "") { + log.Fatal("the global-manifest and namespaced-manifest flags cannot be enabled at the same time as the no-setup flag") + } log.Info("Testing operator locally.") // if no namespaced manifest path is given, combine deploy/service_account.yaml, deploy/role.yaml, deploy/role_binding.yaml and deploy/operator.yaml - if tlConfig.namespacedManPath == "" { + if tlConfig.namespacedManPath == "" && !tlConfig.noSetup { err := os.MkdirAll(deployTestDir, os.FileMode(fileutil.DefaultDirFileMode)) if err != nil { log.Fatalf("could not create %s: (%v)", deployTestDir, err) @@ -113,7 +118,7 @@ func testLocalFunc(cmd *cobra.Command, args []string) { } }() } - if tlConfig.globalManPath == "" { + if tlConfig.globalManPath == "" && !tlConfig.noSetup { err := os.MkdirAll(deployTestDir, os.FileMode(fileutil.DefaultDirFileMode)) if err != nil { log.Fatalf("could not create %s: (%v)", deployTestDir, err) @@ -149,6 +154,25 @@ func testLocalFunc(cmd *cobra.Command, args []string) { } }() } + if tlConfig.noSetup { + err := os.MkdirAll(deployTestDir, os.FileMode(fileutil.DefaultDirFileMode)) + if err != nil { + log.Fatalf("could not create %s: (%v)", deployTestDir, err) + } + tlConfig.namespacedManPath = filepath.Join(deployTestDir, "empty.yaml") + tlConfig.globalManPath = filepath.Join(deployTestDir, "empty.yaml") + emptyBytes := []byte{} + err = ioutil.WriteFile(tlConfig.globalManPath, emptyBytes, os.FileMode(fileutil.DefaultFileMode)) + if err != nil { + log.Fatalf("could not create empty manifest file: (%v)", err) + } + defer func() { + err := os.Remove(tlConfig.globalManPath) + if err != nil { + log.Fatalf("could not delete empty manifest file: (%v)", err) + } + }() + } if tlConfig.image != "" { if err := replaceImage(tlConfig.namespacedManPath, tlConfig.image); err != nil { log.Fatalf("failed to overwrite operator image in the namespaced manifest: %v", err) @@ -164,7 +188,7 @@ func testLocalFunc(cmd *cobra.Command, args []string) { if tlConfig.goTestFlags != "" { testArgs = append(testArgs, strings.Split(tlConfig.goTestFlags, " ")...) } - if tlConfig.namespace != "" { + if tlConfig.namespace != "" || tlConfig.noSetup { testArgs = append(testArgs, "-"+test.SingleNamespaceFlag, "-parallel=1") } dc := exec.Command("go", testArgs...) diff --git a/doc/sdk-cli-reference.md b/doc/sdk-cli-reference.md index bdcd87d76c1..e53dcb3ac1e 100644 --- a/doc/sdk-cli-reference.md +++ b/doc/sdk-cli-reference.md @@ -257,6 +257,7 @@ Runs the tests locally * `--namespaced-manifest` string - path to manifest for per-test, namespaced resources (default: combines deploy/service_account.yaml, deploy/rbac.yaml, and deploy/operator.yaml) * `--namespace` string - if non-empty, single namespace to run tests in (e.g. "operator-test") (default: "") * `--go-test-flags` string - extra arguments to pass to `go test` (e.g. -f "-v -parallel=2") +* `--no-setup` bool - disable test resource creation * `--image` string - use a different operator image from the one specified in the namespaced manifest * `-h, --help` - help for local diff --git a/doc/test-framework/writing-e2e-tests.md b/doc/test-framework/writing-e2e-tests.md index b54b92eab42..da32d6c5f73 100644 --- a/doc/test-framework/writing-e2e-tests.md +++ b/doc/test-framework/writing-e2e-tests.md @@ -250,6 +250,17 @@ $ kubectl create namespace operator-test $ operator-sdk test local ./test/e2e --namespace operator-test ``` +If you would prefer to create the resources yourself and skip resource creation, you can use the `--no-setup` flag: +```shell +$ kubectl create namespace operator-test +$ kubectl create -f deploy/crds/cache_v1alpha1_memcached_crd.yaml +$ kubectl create -f deploy/service_account.yaml --namespace operator-test +$ kubectl create -f deploy/role.yaml --namespace operator-test +$ kubectl create -f deploy/role_binding.yaml --namespace operator-test +$ kubectl create -f deploy/operator.yaml --namespace operator-test +$ operator-sdk test local ./test/e2e --namespace operator-test --no-setup +``` + For more documentation on the `operator-sdk test local` command, see the [SDK CLI Reference][sdk-cli-ref] doc. For advanced use cases, it is possible to run the tests via `go test` directly. As long as all flags defined diff --git a/hack/lib/test_lib.sh b/hack/lib/test_lib.sh index 6dae39ccb80..b989f75bfec 100644 --- a/hack/lib/test_lib.sh +++ b/hack/lib/test_lib.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + function listPkgs() { go list ./commands/... ./pkg/... ./test/... | grep -v generated } @@ -13,3 +15,34 @@ function listFiles() { # pipeline is much faster than for loop listPkgs | xargs -I {} find "${GOPATH}/src/{}" -name '*.go' | grep -v generated } + +#=================================================================== +# FUNCTION trap_add () +# +# Purpose: prepends a command to a trap +# +# - 1st arg: code to add +# - remaining args: names of traps to modify +# +# Example: trap_add 'echo "in trap DEBUG"' DEBUG +# +# See: http://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal +#=================================================================== +function trap_add() { + trap_add_cmd=$1; shift || fatal "${FUNCNAME} usage error" + new_cmd= + for trap_add_name in "$@"; do + # Grab the currently defined trap commands for this trap + existing_cmd=`trap -p "${trap_add_name}" | awk -F"'" '{print $2}'` + + # Define default command + [ -z "${existing_cmd}" ] && existing_cmd="echo exiting @ `date`" + + # Generate the new command + new_cmd="${trap_add_cmd};${existing_cmd}" + + # Assign the test + trap "${new_cmd}" "${trap_add_name}" || \ + fatal "unable to add to trap ${trap_add_name}" + done +} \ No newline at end of file diff --git a/hack/tests/e2e-ansible.sh b/hack/tests/e2e-ansible.sh index 31ddda0c6e7..90fa1261ceb 100755 --- a/hack/tests/e2e-ansible.sh +++ b/hack/tests/e2e-ansible.sh @@ -1,35 +1,6 @@ #!/usr/bin/env bash -#=================================================================== -# FUNCTION trap_add () -# -# Purpose: prepends a command to a trap -# -# - 1st arg: code to add -# - remaining args: names of traps to modify -# -# Example: trap_add 'echo "in trap DEBUG"' DEBUG -# -# See: http://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal -#=================================================================== -trap_add() { - trap_add_cmd=$1; shift || fatal "${FUNCNAME} usage error" - new_cmd= - for trap_add_name in "$@"; do - # Grab the currently defined trap commands for this trap - existing_cmd=`trap -p "${trap_add_name}" | awk -F"'" '{print $2}'` - - # Define default command - [ -z "${existing_cmd}" ] && existing_cmd="echo exiting @ `date`" - - # Generate the new command - new_cmd="${trap_add_cmd};${existing_cmd}" - - # Assign the test - trap "${new_cmd}" "${trap_add_name}" || \ - fatal "unable to add to trap ${trap_add_name}" - done -} +source hack/lib/test_lib.sh DEST_IMAGE="quay.io/example/memcached-operator:v0.0.2" diff --git a/hack/tests/test-subcommand.sh b/hack/tests/test-subcommand.sh index 6910c299866..83f65d71ba3 100755 --- a/hack/tests/test-subcommand.sh +++ b/hack/tests/test-subcommand.sh @@ -1,12 +1,28 @@ #!/usr/bin/env bash +source hack/lib/test_lib.sh + set -ex -cd test/test-framework +pushd test/test-framework # test framework with defaults operator-sdk test local . # test operator-sdk test flags operator-sdk test local . --global-manifest deploy/crds/cache_v1alpha1_memcached_crd.yaml --namespaced-manifest deploy/namespace-init.yaml --go-test-flags "-parallel 1" --kubeconfig $HOME/.kube/config --image=quay.io/coreos/operator-sdk-dev:test-framework-operator-runtime # test operator-sdk test local single namespace mode kubectl create namespace test-memcached +# we use the test-memcached namespace for all future tests, so we only need to set this trap once +trap_add 'kubectl delete namespace test-memcached || true' EXIT operator-sdk test local . --namespace=test-memcached kubectl delete namespace test-memcached +# test operator in no-setup mode +kubectl create namespace test-memcached +kubectl create -f deploy/crds/cache_v1alpha1_memcached_crd.yaml +# this runs after the popd at the end, so it needs the path from the project root +trap_add 'kubectl delete -f test/test-framework/deploy/crds/cache_v1alpha1_memcached_crd.yaml' EXIT +kubectl create -f deploy/service_account.yaml --namespace test-memcached +kubectl create -f deploy/role.yaml --namespace test-memcached +kubectl create -f deploy/role_binding.yaml --namespace test-memcached +kubectl create -f deploy/operator.yaml --namespace test-memcached +operator-sdk test local . --namespace=test-memcached --no-setup +kubectl delete namespace test-memcached +popd diff --git a/pkg/test/framework.go b/pkg/test/framework.go index e5c4473d50f..061ccba2399 100644 --- a/pkg/test/framework.go +++ b/pkg/test/framework.go @@ -57,6 +57,10 @@ type Framework struct { } func setup(kubeconfigPath, namespacedManPath *string) error { + namespace := "" + if *singleNamespace { + namespace = os.Getenv(TestNamespaceEnv) + } var err error var kubeconfig *rest.Config if *kubeconfigPath == "incluster" { @@ -73,8 +77,16 @@ func setup(kubeconfigPath, namespacedManPath *string) error { } kubeconfig, err = rest.InClusterConfig() *singleNamespace = true + namespace = os.Getenv(TestNamespaceEnv) + if len(namespace) == 0 { + return fmt.Errorf("test namespace env not set") + } } else { - kubeconfig, _, err = k8sInternal.GetKubeconfigAndNamespace(*kubeconfigPath) + var kcNamespace string + kubeconfig, kcNamespace, err = k8sInternal.GetKubeconfigAndNamespace(*kubeconfigPath) + if *singleNamespace && namespace == "" { + namespace = kcNamespace + } } if err != nil { return fmt.Errorf("failed to build the kubeconfig: %v", err) @@ -91,13 +103,6 @@ func setup(kubeconfigPath, namespacedManPath *string) error { return fmt.Errorf("failed to build the dynamic client: %v", err) } dynamicDecoder = serializer.NewCodecFactory(scheme).UniversalDeserializer() - namespace := "" - if *singleNamespace { - namespace = os.Getenv(TestNamespaceEnv) - if len(namespace) == 0 { - return fmt.Errorf("namespace set in %s cannot be empty", TestNamespaceEnv) - } - } Global = &Framework{ Client: &frameworkClient{Client: dynClient}, KubeConfig: kubeconfig,