Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

commands/.../test/local,pkg/test: add no-setup flag #770

Merged
merged 11 commits into from
Nov 29, 2018
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 27 additions & 3 deletions commands/operator-sdk/cmd/test/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type testLocalConfig struct {
namespacedManPath string
goTestFlags string
namespace string
noSetup bool
image string
}

Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
hasbro17 marked this conversation as resolved.
Show resolved Hide resolved
testArgs = append(testArgs, "-"+test.SingleNamespaceFlag, "-parallel=1")
}
dc := exec.Command("go", testArgs...)
Expand Down
1 change: 1 addition & 0 deletions doc/sdk-cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 11 additions & 0 deletions doc/test-framework/writing-e2e-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 33 additions & 0 deletions hack/lib/test_lib.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env bash

function listPkgs() {
go list ./commands/... ./pkg/... ./test/... | grep -v generated
}
Expand All @@ -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
}
31 changes: 1 addition & 30 deletions hack/tests/e2e-ansible.sh
Original file line number Diff line number Diff line change
@@ -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"

Expand Down
18 changes: 17 additions & 1 deletion hack/tests/test-subcommand.sh
Original file line number Diff line number Diff line change
@@ -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
21 changes: 13 additions & 8 deletions pkg/test/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ type Framework struct {
}

func setup(kubeconfigPath, namespacedManPath *string) error {
namespace := ""
if *singleNamespace {
namespace = os.Getenv(TestNamespaceEnv)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If len(namespace) == 0 exit with error TestNamespaceEnv must be set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
var err error
var kubeconfig *rest.Config
if *kubeconfigPath == "incluster" {
Expand All @@ -73,8 +77,16 @@ func setup(kubeconfigPath, namespacedManPath *string) error {
}
kubeconfig, err = rest.InClusterConfig()
*singleNamespace = true
namespace = os.Getenv(TestNamespaceEnv)
AlexNPavel marked this conversation as resolved.
Show resolved Hide resolved
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)
Expand All @@ -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,
Expand Down