From b25c0d176f27afa6389819012085afce90698493 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Fri, 31 Aug 2018 14:44:32 -0700 Subject: [PATCH 01/35] pkg/test: add in-cluster/in-image testing support Issue #435 --- Gopkg.lock | 3 - commands/operator-sdk/cmd/build.go | 139 ++++++++++++++++++++++++++++- pkg/generator/generator.go | 31 ++++++- pkg/generator/templates.go | 80 ++++++++++++++++- pkg/test/framework.go | 32 ++++++- pkg/test/main_entry.go | 16 ++-- pkg/test/resource_creator.go | 5 ++ 7 files changed, 288 insertions(+), 18 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index b745be52b7..4f1410f5dc 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -485,10 +485,7 @@ "github.com/sirupsen/logrus", "github.com/spf13/cobra", "gopkg.in/yaml.v2", - "k8s.io/api/apps/v1", "k8s.io/api/core/v1", - "k8s.io/api/rbac/v1beta1", - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1", "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset", "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme", "k8s.io/apimachinery/pkg/api/errors", diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 8747b93a4d..33748a3cd5 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -15,17 +15,31 @@ package cmd import ( + "bytes" "fmt" + "io/ioutil" + "log" "os" "os/exec" + "github.com/operator-framework/operator-sdk/commands/operator-sdk/cmd/cmdutil" cmdError "github.com/operator-framework/operator-sdk/commands/operator-sdk/error" + "github.com/operator-framework/operator-sdk/pkg/generator" + "github.com/ghodss/yaml" "github.com/spf13/cobra" ) +var ( + namespacedManBuild string + globalManBuild string + rbacManBuild string + testLocationBuild string + enableTests bool +) + func NewBuildCmd() *cobra.Command { - return &cobra.Command{ + buildCmd := &cobra.Command{ Use: "build ", Short: "Compiles code and builds artifacts", Long: `The operator-sdk build command compiles the code, builds the executables, @@ -42,6 +56,74 @@ For example: `, Run: buildFunc, } + buildCmd.Flags().BoolVarP(&enableTests, "enable-tests", "e", false, "Enable in-cluster testing by adding test binary to the image") + buildCmd.Flags().StringVarP(&testLocationBuild, "test-location", "t", "./test/e2e", "Location of tests") + buildCmd.Flags().StringVarP(&namespacedManBuild, "namespaced", "n", "", "Path of namespaced resources for tests") + buildCmd.Flags().StringVarP(&globalManBuild, "global", "g", "deploy/crd.yaml", "Path of global resources for tests") + buildCmd.Flags().StringVarP(&rbacManBuild, "rbac", "r", "deploy/rbac.yaml", "Path of global resources for tests") + return buildCmd +} + +func parseRoles(yamlFile []byte) ([]byte, error) { + res := make([]byte, 0) + yamlSplit := bytes.Split(yamlFile, []byte("\n---\n")) + for _, yamlSpec := range yamlSplit { + yamlMap := make(map[string]interface{}) + err := yaml.Unmarshal(yamlSpec, &yamlMap) + if err != nil { + return nil, err + } + if yamlMap["kind"].(string) == "Role" { + ruleBytes, err := yaml.Marshal(yamlMap["rules"]) + if err != nil { + return nil, err + } + res = append(res, ruleBytes...) + } + } + return res, nil +} + +func verifyDeploymentImage(yamlFile []byte, imageName string) string { + warningMessages := "" + yamlSplit := bytes.Split(yamlFile, []byte("\n---\n")) + for _, yamlSpec := range yamlSplit { + yamlMap := make(map[string]interface{}) + err := yaml.Unmarshal(yamlSpec, &yamlMap) + if err != nil { + fmt.Printf("WARNING: Could not unmarshal yaml namespaced spec") + return "" + } + if yamlMap["kind"].(string) == "Deployment" { + // this is ugly and hacky; we should probably make this cleaner + nestedMap, ok := yamlMap["spec"].(map[string]interface{}) + if !ok { + continue + } + nestedMap, ok = nestedMap["template"].(map[string]interface{}) + if !ok { + continue + } + nestedMap, ok = nestedMap["spec"].(map[string]interface{}) + if !ok { + continue + } + containersArray, ok := nestedMap["containers"].([]interface{}) + if !ok { + continue + } + for _, item := range containersArray { + image, ok := item.(map[string]interface{})["image"].(string) + if !ok { + continue + } + if image != imageName { + warningMessages = fmt.Sprintf("%s\nWARNING: Namespace manifest contains a deployment with image %v, which does not match the name of the image being built: %v", warningMessages, image, imageName) + } + } + } + } + return warningMessages } const ( @@ -56,18 +138,73 @@ func buildFunc(cmd *cobra.Command, args []string) { } bcmd := exec.Command(build) + bcmd.Env = append(os.Environ(), fmt.Sprintf("TEST_LOCATION=%v", testLocationBuild)) + bcmd.Env = append(bcmd.Env, fmt.Sprintf("ENABLE_TESTS=%v", enableTests)) o, err := bcmd.CombinedOutput() if err != nil { cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to build: (%v)", string(o))) } fmt.Fprintln(os.Stdout, string(o)) + namespacedRolesBytes := make([]byte, 0) + genWarning := "" image := args[0] + if enableTests { + if namespacedManBuild == "" { + os.Mkdir("deploy/test", os.FileMode(int(0775))) + namespacedManBuild = "deploy/test/namespace-manifests.yaml" + rbac, err := ioutil.ReadFile("deploy/rbac.yaml") + if err != nil { + log.Fatalf("could not find rbac manifest: %v", err) + } + operator, err := ioutil.ReadFile("deploy/operator.yaml") + if err != nil { + log.Fatalf("could not find operator manifest: %v", err) + } + combined := append(rbac, []byte("\n---\n")...) + combined = append(combined, operator...) + err = ioutil.WriteFile(namespacedManBuild, combined, os.FileMode(int(0664))) + if err != nil { + log.Fatalf("could not create temporary namespaced manifest file: %v", err) + } + defer func() { + err := os.Remove(namespacedManBuild) + if err != nil { + log.Fatalf("could not delete temporary namespace manifest file") + } + }() + } + namespacedBytes, err := ioutil.ReadFile(namespacedManBuild) + if err != nil { + log.Fatalf("could not read rbac manifest: %v", err) + } + namespacedRolesBytes, err = parseRoles(namespacedBytes) + if err != nil { + log.Fatalf("could not parse namespaced manifest file for rbac roles: %v", err) + } + genWarning = verifyDeploymentImage(namespacedBytes, image) + global, err := ioutil.ReadFile(globalManBuild) + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to read global manifest: (%v)", err)) + } + c := cmdutil.GetConfig() + if err = generator.RenderTestYaml(c, string(global), string(namespacedRolesBytes), image); err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to generate deploy/test-gen.yaml: (%v)", err)) + } + os.Link("tmp/build/dockerfiles/Dockerfile_Tests", "tmp/build/Dockerfile") + } else { + os.Link("tmp/build/dockerfiles/Dockerfile_Standard", "tmp/build/Dockerfile") + } + defer os.Remove("tmp/build/Dockerfile") dbcmd := exec.Command(dockerBuild) dbcmd.Env = append(os.Environ(), fmt.Sprintf("IMAGE=%v", image)) + dbcmd.Env = append(dbcmd.Env, fmt.Sprintf("NAMESPACEDMAN=%v", namespacedManBuild)) o, err = dbcmd.CombinedOutput() if err != nil { cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to output build image %v: (%v)", image, string(o))) } fmt.Fprintln(os.Stdout, string(o)) + if genWarning != "" { + fmt.Printf("%s\n", genWarning) + } } diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index 9991e820ce..e8fcf27937 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -39,6 +39,7 @@ const ( tmpDir = "tmp" buildDir = tmpDir + "/build" codegenDir = tmpDir + "/codegen" + dockerDir = buildDir + "/dockerfiles" pkgDir = "pkg" apisDir = pkgDir + "/apis" stubDir = pkgDir + "/stub" @@ -52,7 +53,9 @@ const ( types = "types.go" build = "build.sh" dockerBuild = "docker_build.sh" - dockerfile = "Dockerfile" + standardDockerfile = "Dockerfile_Standard" + testingDockerfile = "Dockerfile_Testing" + goTest = "go-test.sh" boilerplate = "boilerplate.go.txt" updateGenerated = "update-generated.sh" gopkgtoml = "Gopkg.toml" @@ -76,6 +79,7 @@ const ( operatorTmplName = "deploy/operator.yaml" rbacTmplName = "deploy/rbac.yaml" crTmplName = "deploy/cr.yaml" + testYamlName = "deploy/test-gen.yaml" pluralSuffix = "s" ) @@ -246,6 +250,16 @@ func renderDeployFiles(deployDir, projectName, apiVersion, kind string) error { return renderWriteFile(filepath.Join(deployDir, "operator.yaml"), operatorTmplName, operatorYamlTmpl, opTd) } +func RenderTestYaml(c *Config, globalManifest, extraRoles, image string) error { + opTd := tmplData{ + GlobalManifest: globalManifest, + ExtraRoles: extraRoles, + ProjectName: c.ProjectName, + Image: image, + } + return renderWriteFile(filepath.Join(deployDir, "test-gen.yaml"), testYamlName, testYamlTmpl, opTd) +} + // RenderOlmCatalog generates catalog manifests "deploy/olm-catalog/*" // The current working directory must be the project repository root func RenderOlmCatalog(c *Config, image, version string) error { @@ -343,10 +357,16 @@ func renderBuildFiles(buildDir, repoPath, projectName string) error { dTd := tmplData{ ProjectName: projectName, } - if err := renderFile(buf, "tmp/build/Dockerfile", dockerFileTmpl, dTd); err != nil { + + if err := renderWriteFile(filepath.Join(buildDir, "dockerfiles", standardDockerfile), "tmp/build/dockerfiles/Dockerfile_Standard", standardDockerFileTmpl, dTd); err != nil { + return err + } + + if err := renderWriteFile(filepath.Join(buildDir, "dockerfiles", testingDockerfile), "tmp/build/dockerfiles/Dockerfile_Testing", testingDockerFileTmpl, dTd); err != nil { return err } - return renderWriteFile(filepath.Join(buildDir, dockerfile), "tmp/build/Dockerfile", dockerFileTmpl, dTd) + + return renderWriteFile(filepath.Join(buildDir, goTest), "tmp/build/go-test.sh", goTestScript, tmplData{}) } func renderDockerBuildFile(w io.Writer) error { @@ -459,6 +479,10 @@ type tmplData struct { CRDVersion string CSVName string CatalogVersion string + + // global manifest used for testing + GlobalManifest string + ExtraRoles string } // Creates all the necesary directories for the generated files @@ -471,6 +495,7 @@ func (g *Generator) generateDirStructure() error { filepath.Join(g.projectName, olmCatalogDir), filepath.Join(g.projectName, buildDir), filepath.Join(g.projectName, codegenDir), + filepath.Join(g.projectName, dockerDir), filepath.Join(g.projectName, versionDir), filepath.Join(g.projectName, apisDir, apiDirName(g.apiVersion), version(g.apiVersion)), filepath.Join(g.projectName, stubDir), diff --git a/pkg/generator/templates.go b/pkg/generator/templates.go index e803bc8a2e..d3910f30db 100644 --- a/pkg/generator/templates.go +++ b/pkg/generator/templates.go @@ -424,6 +424,54 @@ spec: version: {{.Version}} ` +const testYamlTmpl = `{{.GlobalManifest}} +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: {{.ProjectName}}-test +rules: +- apiGroups: + - "rbac.authorization.k8s.io" + resources: + - "*" + verbs: + - "*" +{{.ExtraRoles}} +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: default-account-{{.ProjectName}}-test +subjects: +- kind: ServiceAccount + name: default +roleRef: + kind: Role + name: {{.ProjectName}}-test + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: v1 +kind: Pod +metadata: + name: {{.ProjectName}}-test +spec: + restartPolicy: Never + containers: + - name: {{.ProjectName}}-test + image: {{.Image}} + imagePullPolicy: Always + command: ["/go-test.sh"] + resources: + requests: + cpu: 1 + env: + - name: TEST_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +` + const operatorYamlTmpl = `apiVersion: apps/v1 kind: Deployment metadata: @@ -541,8 +589,13 @@ mkdir -p ${BIN_DIR} PROJECT_NAME="{{.ProjectName}}" REPO_PATH="{{.RepoPath}}" BUILD_PATH="${REPO_PATH}/cmd/${PROJECT_NAME}" +TEST_PATH="${REPO_PATH}/${TEST_LOCATION}" echo "building "${PROJECT_NAME}"..." GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ${BIN_DIR}/${PROJECT_NAME} $BUILD_PATH +if $ENABLE_TESTS ; then + echo "building "${PROJECT_NAME}-test"..." + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go test -c -o ${BIN_DIR}/${PROJECT_NAME}-test $TEST_PATH +fi ` const dockerBuildTmpl = `#!/usr/bin/env bash @@ -555,15 +608,38 @@ fi : ${IMAGE:?"Need to set IMAGE, e.g. gcr.io//-operator"} echo "building container ${IMAGE}..." -docker build -t "${IMAGE}" -f tmp/build/Dockerfile . +docker build -t "${IMAGE}" -f tmp/build/Dockerfile . --build-arg NAMESPACEDMAN=$NAMESPACEDMAN ` -const dockerFileTmpl = `FROM alpine:3.6 +const goTestScript = `#!/bin/sh + +memcached-operator-test -test.parallel=1 -test.failfast -root=/ -kubeconfig=incluster -namespacedMan=namespaced.yaml -test.v +` + +const standardDockerFileTmpl = `FROM alpine:3.6 RUN adduser -D {{.ProjectName}} USER {{.ProjectName}} ADD tmp/_output/bin/{{.ProjectName}} /usr/local/bin/{{.ProjectName}} + +# just keep this to ignore warnings +ARG NAMESPACEDMAN +` + +const testingDockerFileTmpl = `FROM alpine:3.6 + +RUN adduser -D {{.ProjectName}} +USER {{.ProjectName}} + +ADD tmp/_output/bin/{{.ProjectName}} /usr/local/bin/{{.ProjectName}} + +# just keep this to ignore warnings +ARG NAMESPACEDMAN + +ADD tmp/_output/bin/{{.ProjectName}}-test /usr/local/bin/{{.ProjectName}}-test +ADD $NAMESPACEDMAN /namespaced.yaml +ADD tmp/build/go-test.sh /go-test.sh ` // apiDocTmpl is the template for apis/../doc.go diff --git a/pkg/test/framework.go b/pkg/test/framework.go index 2edb1b806f..fcc4a9e51b 100644 --- a/pkg/test/framework.go +++ b/pkg/test/framework.go @@ -17,6 +17,8 @@ package test import ( goctx "context" "fmt" + "net" + "os" "sync" "time" @@ -51,10 +53,31 @@ type Framework struct { DynamicClient dynclient.Client DynamicDecoder runtime.Decoder NamespacedManPath *string + InCluster bool } func setup(kubeconfigPath, namespacedManPath *string) error { - kubeconfig, err := clientcmd.BuildConfigFromFlags("", *kubeconfigPath) + var err error + var kubeconfig *rest.Config + inCluster := false + if *kubeconfigPath == "incluster" { + // Work around https://github.com/kubernetes/kubernetes/issues/40973 + // See https://github.com/coreos/etcd-operator/issues/731#issuecomment-283804819 + if len(os.Getenv("KUBERNETES_SERVICE_HOST")) == 0 { + addrs, err := net.LookupHost("kubernetes.default.svc") + if err != nil { + return fmt.Errorf("failed to get service host: %v", err) + } + os.Setenv("KUBERNETES_SERVICE_HOST", addrs[0]) + } + if len(os.Getenv("KUBERNETES_SERVICE_PORT")) == 0 { + os.Setenv("KUBERNETES_SERVICE_PORT", "443") + } + kubeconfig, err = rest.InClusterConfig() + inCluster = true + } else { + kubeconfig, err = clientcmd.BuildConfigFromFlags("", *kubeconfigPath) + } if err != nil { return fmt.Errorf("failed to build the kubeconfig: %v", err) } @@ -82,6 +105,7 @@ func setup(kubeconfigPath, namespacedManPath *string) error { DynamicClient: dynClient, DynamicDecoder: dynDec, NamespacedManPath: namespacedManPath, + InCluster: inCluster, } return nil } @@ -114,7 +138,11 @@ func AddToFrameworkScheme(addToScheme addToSchemeFunc, obj runtime.Object) error Global.RestMapper.Reset() Global.DynamicClient, err = dynclient.New(Global.KubeConfig, dynclient.Options{Scheme: Global.Scheme, Mapper: Global.RestMapper}) err = wait.PollImmediate(time.Second, time.Second*10, func() (done bool, err error) { - err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: "default"}, obj) + if Global.InCluster { + err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: os.Getenv("TEST_NAMESPACE")}, obj) + } else { + err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: "default"}, obj) + } if err != nil { Global.RestMapper.Reset() return false, nil diff --git a/pkg/test/main_entry.go b/pkg/test/main_entry.go index 4c7a15fd40..70a3ec23dc 100644 --- a/pkg/test/main_entry.go +++ b/pkg/test/main_entry.go @@ -53,12 +53,14 @@ func MainEntry(m *testing.M) { os.Exit(exitCode) }() // create crd - globalYAML, err := ioutil.ReadFile(*globalManPath) - if err != nil { - log.Fatalf("failed to read global resource manifest: %v", err) - } - err = ctx.createFromYAML(globalYAML, true) - if err != nil { - log.Fatalf("failed to create resource(s) in global resource manifest: %v", err) + if !Global.InCluster { + globalYAML, err := ioutil.ReadFile(*globalManPath) + if err != nil { + log.Fatalf("failed to read global resource manifest: %v", err) + } + err = ctx.createFromYAML(globalYAML, true) + if err != nil { + log.Fatalf("failed to create resource(s) in global resource manifest: %v", err) + } } } diff --git a/pkg/test/resource_creator.go b/pkg/test/resource_creator.go index 5ed0cd24b9..2f0a8ebcab 100644 --- a/pkg/test/resource_creator.go +++ b/pkg/test/resource_creator.go @@ -19,6 +19,7 @@ import ( goctx "context" "fmt" "io/ioutil" + "os" yaml "gopkg.in/yaml.v2" core "k8s.io/api/core/v1" @@ -30,6 +31,10 @@ func (ctx *TestCtx) GetNamespace() (string, error) { if ctx.Namespace != "" { return ctx.Namespace, nil } + if Global.InCluster { + ctx.Namespace = os.Getenv("TEST_NAMESPACE") + return ctx.Namespace, nil + } // create namespace ctx.Namespace = ctx.GetID() namespaceObj := &core.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ctx.Namespace}} From c0a67a78e18dee5fcb4509d3da9e6dc295415331 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Mon, 10 Sep 2018 17:13:07 -0700 Subject: [PATCH 02/35] *: change the way the dockerfiles are used --- commands/operator-sdk/cmd/build.go | 30 +++++++++++++++++------------- pkg/generator/generator.go | 10 +++++----- pkg/generator/generator_test.go | 5 +++++ pkg/generator/templates.go | 16 ++++------------ 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 33748a3cd5..6ed4dad2a9 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -127,9 +127,8 @@ func verifyDeploymentImage(yamlFile []byte, imageName string) string { } const ( - build = "./tmp/build/build.sh" - dockerBuild = "./tmp/build/docker_build.sh" - configYaml = "./config/config.yaml" + build = "./tmp/build/build.sh" + configYaml = "./config/config.yaml" ) func buildFunc(cmd *cobra.Command, args []string) { @@ -149,6 +148,7 @@ func buildFunc(cmd *cobra.Command, args []string) { namespacedRolesBytes := make([]byte, 0) genWarning := "" image := args[0] + intermediateImageName := image if enableTests { if namespacedManBuild == "" { os.Mkdir("deploy/test", os.FileMode(int(0775))) @@ -191,20 +191,24 @@ func buildFunc(cmd *cobra.Command, args []string) { if err = generator.RenderTestYaml(c, string(global), string(namespacedRolesBytes), image); err != nil { cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to generate deploy/test-gen.yaml: (%v)", err)) } - os.Link("tmp/build/dockerfiles/Dockerfile_Tests", "tmp/build/Dockerfile") - } else { - os.Link("tmp/build/dockerfiles/Dockerfile_Standard", "tmp/build/Dockerfile") + intermediateImageName += "-intermediate" } - defer os.Remove("tmp/build/Dockerfile") - dbcmd := exec.Command(dockerBuild) - dbcmd.Env = append(os.Environ(), fmt.Sprintf("IMAGE=%v", image)) - dbcmd.Env = append(dbcmd.Env, fmt.Sprintf("NAMESPACEDMAN=%v", namespacedManBuild)) + dbcmd := exec.Command("docker", "build", ".", "-f", "tmp/build/Dockerfile", "-t", intermediateImageName) o, err = dbcmd.CombinedOutput() if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to output build image %v: (%v)", image, string(o))) + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to output build image %v: (%v)", intermediateImageName, string(o))) } fmt.Fprintln(os.Stdout, string(o)) - if genWarning != "" { - fmt.Printf("%s\n", genWarning) + + if enableTests { + testDbcmd := exec.Command("docker", "build", ".", "-f", "tmp/build/test-framework/Dockerfile", "-t", image, "--build-arg", "NAMESPACEDMAN="+namespacedManBuild, "--build-arg", "BASEIMAGE="+intermediateImageName) + o, err = testDbcmd.CombinedOutput() + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to output build image %v: (%v)", image, string(o))) + } + fmt.Fprintln(os.Stdout, string(o)) + if genWarning != "" { + fmt.Printf("%s\n", genWarning) + } } } diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index e8fcf27937..e25caf0bae 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -39,7 +39,7 @@ const ( tmpDir = "tmp" buildDir = tmpDir + "/build" codegenDir = tmpDir + "/codegen" - dockerDir = buildDir + "/dockerfiles" + dockerTestDir = buildDir + "/test-framework" pkgDir = "pkg" apisDir = pkgDir + "/apis" stubDir = pkgDir + "/stub" @@ -53,7 +53,7 @@ const ( types = "types.go" build = "build.sh" dockerBuild = "docker_build.sh" - standardDockerfile = "Dockerfile_Standard" + dockerfile = "Dockerfile" testingDockerfile = "Dockerfile_Testing" goTest = "go-test.sh" boilerplate = "boilerplate.go.txt" @@ -358,11 +358,11 @@ func renderBuildFiles(buildDir, repoPath, projectName string) error { ProjectName: projectName, } - if err := renderWriteFile(filepath.Join(buildDir, "dockerfiles", standardDockerfile), "tmp/build/dockerfiles/Dockerfile_Standard", standardDockerFileTmpl, dTd); err != nil { + if err := renderWriteFile(filepath.Join(buildDir, dockerfile), "tmp/build/Dockerfile", dockerFileTmpl, dTd); err != nil { return err } - if err := renderWriteFile(filepath.Join(buildDir, "dockerfiles", testingDockerfile), "tmp/build/dockerfiles/Dockerfile_Testing", testingDockerFileTmpl, dTd); err != nil { + if err := renderWriteFile(filepath.Join(buildDir, "test-framework", testingDockerfile), "tmp/build/test-framework/Dockerfile", testingDockerFileTmpl, dTd); err != nil { return err } @@ -495,7 +495,7 @@ func (g *Generator) generateDirStructure() error { filepath.Join(g.projectName, olmCatalogDir), filepath.Join(g.projectName, buildDir), filepath.Join(g.projectName, codegenDir), - filepath.Join(g.projectName, dockerDir), + filepath.Join(g.projectName, dockerTestDir), filepath.Join(g.projectName, versionDir), filepath.Join(g.projectName, apisDir, apiDirName(g.apiVersion), version(g.apiVersion)), filepath.Join(g.projectName, stubDir), diff --git a/pkg/generator/generator_test.go b/pkg/generator/generator_test.go index 92130ff094..fef124c495 100644 --- a/pkg/generator/generator_test.go +++ b/pkg/generator/generator_test.go @@ -492,8 +492,13 @@ mkdir -p ${BIN_DIR} PROJECT_NAME="app-operator" REPO_PATH="github.com/example-inc/app-operator" BUILD_PATH="${REPO_PATH}/cmd/${PROJECT_NAME}" +TEST_PATH="${REPO_PATH}/${TEST_LOCATION}" echo "building "${PROJECT_NAME}"..." GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ${BIN_DIR}/${PROJECT_NAME} $BUILD_PATH +if $ENABLE_TESTS ; then + echo "building "${PROJECT_NAME}-test"..." + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go test -c -o ${BIN_DIR}/${PROJECT_NAME}-test $TEST_PATH +fi ` const dockerFileExp = `FROM alpine:3.6 diff --git a/pkg/generator/templates.go b/pkg/generator/templates.go index d3910f30db..770f8c16a1 100644 --- a/pkg/generator/templates.go +++ b/pkg/generator/templates.go @@ -616,28 +616,20 @@ const goTestScript = `#!/bin/sh memcached-operator-test -test.parallel=1 -test.failfast -root=/ -kubeconfig=incluster -namespacedMan=namespaced.yaml -test.v ` -const standardDockerFileTmpl = `FROM alpine:3.6 +const dockerFileTmpl = `FROM alpine:3.6 RUN adduser -D {{.ProjectName}} USER {{.ProjectName}} ADD tmp/_output/bin/{{.ProjectName}} /usr/local/bin/{{.ProjectName}} - -# just keep this to ignore warnings -ARG NAMESPACEDMAN ` -const testingDockerFileTmpl = `FROM alpine:3.6 +const testingDockerFileTmpl = `ARG BASEIMAGE -RUN adduser -D {{.ProjectName}} -USER {{.ProjectName}} +FROM ${BASEIMAGE} -ADD tmp/_output/bin/{{.ProjectName}} /usr/local/bin/{{.ProjectName}} - -# just keep this to ignore warnings +ADD tmp/_output/bin/memcached-operator-test /usr/local/bin/memcached-operator-test ARG NAMESPACEDMAN - -ADD tmp/_output/bin/{{.ProjectName}}-test /usr/local/bin/{{.ProjectName}}-test ADD $NAMESPACEDMAN /namespaced.yaml ADD tmp/build/go-test.sh /go-test.sh ` From 923505c3e99c8da54edf0d23509b93327aa9e905 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Wed, 12 Sep 2018 13:57:27 -0700 Subject: [PATCH 03/35] commands/.../build.go: remove unused flag --- commands/operator-sdk/cmd/build.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 6ed4dad2a9..b003323e44 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -33,7 +33,6 @@ import ( var ( namespacedManBuild string globalManBuild string - rbacManBuild string testLocationBuild string enableTests bool ) @@ -60,7 +59,6 @@ For example: buildCmd.Flags().StringVarP(&testLocationBuild, "test-location", "t", "./test/e2e", "Location of tests") buildCmd.Flags().StringVarP(&namespacedManBuild, "namespaced", "n", "", "Path of namespaced resources for tests") buildCmd.Flags().StringVarP(&globalManBuild, "global", "g", "deploy/crd.yaml", "Path of global resources for tests") - buildCmd.Flags().StringVarP(&rbacManBuild, "rbac", "r", "deploy/rbac.yaml", "Path of global resources for tests") return buildCmd } From 7cfeb779befe18e940bf8b64cae802368e63bbbc Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Wed, 12 Sep 2018 14:01:00 -0700 Subject: [PATCH 04/35] commands/.../build.go: split some test stuff into new function --- commands/operator-sdk/cmd/build.go | 89 ++++++++++++++++-------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index b003323e44..4b25ff9ac0 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -124,6 +124,52 @@ func verifyDeploymentImage(yamlFile []byte, imageName string) string { return warningMessages } +func renderTestManifests(image string) string { + namespacedRolesBytes := make([]byte, 0) + if namespacedManBuild == "" { + os.Mkdir("deploy/test", os.FileMode(int(0775))) + namespacedManBuild = "deploy/test/namespace-manifests.yaml" + rbac, err := ioutil.ReadFile("deploy/rbac.yaml") + if err != nil { + log.Fatalf("could not find rbac manifest: %v", err) + } + operator, err := ioutil.ReadFile("deploy/operator.yaml") + if err != nil { + log.Fatalf("could not find operator manifest: %v", err) + } + combined := append(rbac, []byte("\n---\n")...) + combined = append(combined, operator...) + err = ioutil.WriteFile(namespacedManBuild, combined, os.FileMode(int(0664))) + if err != nil { + log.Fatalf("could not create temporary namespaced manifest file: %v", err) + } + defer func() { + err := os.Remove(namespacedManBuild) + if err != nil { + log.Fatalf("could not delete temporary namespace manifest file") + } + }() + } + namespacedBytes, err := ioutil.ReadFile(namespacedManBuild) + if err != nil { + log.Fatalf("could not read rbac manifest: %v", err) + } + namespacedRolesBytes, err = parseRoles(namespacedBytes) + if err != nil { + log.Fatalf("could not parse namespaced manifest file for rbac roles: %v", err) + } + genWarning := verifyDeploymentImage(namespacedBytes, image) + global, err := ioutil.ReadFile(globalManBuild) + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to read global manifest: (%v)", err)) + } + c := cmdutil.GetConfig() + if err = generator.RenderTestYaml(c, string(global), string(namespacedRolesBytes), image); err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to generate deploy/test-gen.yaml: (%v)", err)) + } + return genWarning +} + const ( build = "./tmp/build/build.sh" configYaml = "./config/config.yaml" @@ -143,52 +189,11 @@ func buildFunc(cmd *cobra.Command, args []string) { } fmt.Fprintln(os.Stdout, string(o)) - namespacedRolesBytes := make([]byte, 0) genWarning := "" image := args[0] intermediateImageName := image if enableTests { - if namespacedManBuild == "" { - os.Mkdir("deploy/test", os.FileMode(int(0775))) - namespacedManBuild = "deploy/test/namespace-manifests.yaml" - rbac, err := ioutil.ReadFile("deploy/rbac.yaml") - if err != nil { - log.Fatalf("could not find rbac manifest: %v", err) - } - operator, err := ioutil.ReadFile("deploy/operator.yaml") - if err != nil { - log.Fatalf("could not find operator manifest: %v", err) - } - combined := append(rbac, []byte("\n---\n")...) - combined = append(combined, operator...) - err = ioutil.WriteFile(namespacedManBuild, combined, os.FileMode(int(0664))) - if err != nil { - log.Fatalf("could not create temporary namespaced manifest file: %v", err) - } - defer func() { - err := os.Remove(namespacedManBuild) - if err != nil { - log.Fatalf("could not delete temporary namespace manifest file") - } - }() - } - namespacedBytes, err := ioutil.ReadFile(namespacedManBuild) - if err != nil { - log.Fatalf("could not read rbac manifest: %v", err) - } - namespacedRolesBytes, err = parseRoles(namespacedBytes) - if err != nil { - log.Fatalf("could not parse namespaced manifest file for rbac roles: %v", err) - } - genWarning = verifyDeploymentImage(namespacedBytes, image) - global, err := ioutil.ReadFile(globalManBuild) - if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to read global manifest: (%v)", err)) - } - c := cmdutil.GetConfig() - if err = generator.RenderTestYaml(c, string(global), string(namespacedRolesBytes), image); err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to generate deploy/test-gen.yaml: (%v)", err)) - } + genWarning = renderTestManifests(image) intermediateImageName += "-intermediate" } dbcmd := exec.Command("docker", "build", ".", "-f", "tmp/build/Dockerfile", "-t", intermediateImageName) From afffc384ee46b851b2458c1bdb532e852d4ff791 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Wed, 12 Sep 2018 15:51:00 -0700 Subject: [PATCH 05/35] commands,templates: change test manifest handling --- commands/operator-sdk/cmd/build.go | 62 +++--------------------------- pkg/generator/generator.go | 16 +++----- pkg/generator/templates.go | 29 +------------- 3 files changed, 11 insertions(+), 96 deletions(-) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 4b25ff9ac0..78ef5f2d28 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -32,7 +32,6 @@ import ( var ( namespacedManBuild string - globalManBuild string testLocationBuild string enableTests bool ) @@ -58,30 +57,9 @@ For example: buildCmd.Flags().BoolVarP(&enableTests, "enable-tests", "e", false, "Enable in-cluster testing by adding test binary to the image") buildCmd.Flags().StringVarP(&testLocationBuild, "test-location", "t", "./test/e2e", "Location of tests") buildCmd.Flags().StringVarP(&namespacedManBuild, "namespaced", "n", "", "Path of namespaced resources for tests") - buildCmd.Flags().StringVarP(&globalManBuild, "global", "g", "deploy/crd.yaml", "Path of global resources for tests") return buildCmd } -func parseRoles(yamlFile []byte) ([]byte, error) { - res := make([]byte, 0) - yamlSplit := bytes.Split(yamlFile, []byte("\n---\n")) - for _, yamlSpec := range yamlSplit { - yamlMap := make(map[string]interface{}) - err := yaml.Unmarshal(yamlSpec, &yamlMap) - if err != nil { - return nil, err - } - if yamlMap["kind"].(string) == "Role" { - ruleBytes, err := yaml.Marshal(yamlMap["rules"]) - if err != nil { - return nil, err - } - res = append(res, ruleBytes...) - } - } - return res, nil -} - func verifyDeploymentImage(yamlFile []byte, imageName string) string { warningMessages := "" yamlSplit := bytes.Split(yamlFile, []byte("\n---\n")) @@ -124,48 +102,18 @@ func verifyDeploymentImage(yamlFile []byte, imageName string) string { return warningMessages } -func renderTestManifests(image string) string { - namespacedRolesBytes := make([]byte, 0) +func renderTestManifest(image string) string { if namespacedManBuild == "" { - os.Mkdir("deploy/test", os.FileMode(int(0775))) - namespacedManBuild = "deploy/test/namespace-manifests.yaml" - rbac, err := ioutil.ReadFile("deploy/rbac.yaml") - if err != nil { - log.Fatalf("could not find rbac manifest: %v", err) - } - operator, err := ioutil.ReadFile("deploy/operator.yaml") - if err != nil { - log.Fatalf("could not find operator manifest: %v", err) - } - combined := append(rbac, []byte("\n---\n")...) - combined = append(combined, operator...) - err = ioutil.WriteFile(namespacedManBuild, combined, os.FileMode(int(0664))) - if err != nil { - log.Fatalf("could not create temporary namespaced manifest file: %v", err) - } - defer func() { - err := os.Remove(namespacedManBuild) - if err != nil { - log.Fatalf("could not delete temporary namespace manifest file") - } - }() + namespacedManBuild = "deploy/operator.yaml" } namespacedBytes, err := ioutil.ReadFile(namespacedManBuild) if err != nil { log.Fatalf("could not read rbac manifest: %v", err) } - namespacedRolesBytes, err = parseRoles(namespacedBytes) - if err != nil { - log.Fatalf("could not parse namespaced manifest file for rbac roles: %v", err) - } genWarning := verifyDeploymentImage(namespacedBytes, image) - global, err := ioutil.ReadFile(globalManBuild) - if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to read global manifest: (%v)", err)) - } c := cmdutil.GetConfig() - if err = generator.RenderTestYaml(c, string(global), string(namespacedRolesBytes), image); err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to generate deploy/test-gen.yaml: (%v)", err)) + if err = generator.RenderTestYaml(c, image); err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to generate deploy/test-pod.yaml: (%v)", err)) } return genWarning } @@ -193,7 +141,7 @@ func buildFunc(cmd *cobra.Command, args []string) { image := args[0] intermediateImageName := image if enableTests { - genWarning = renderTestManifests(image) + genWarning = renderTestManifest(image) intermediateImageName += "-intermediate" } dbcmd := exec.Command("docker", "build", ".", "-f", "tmp/build/Dockerfile", "-t", intermediateImageName) diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index e25caf0bae..7b8c5e6b26 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -79,7 +79,7 @@ const ( operatorTmplName = "deploy/operator.yaml" rbacTmplName = "deploy/rbac.yaml" crTmplName = "deploy/cr.yaml" - testYamlName = "deploy/test-gen.yaml" + testYamlName = "deploy/test-pod.yaml" pluralSuffix = "s" ) @@ -250,14 +250,12 @@ func renderDeployFiles(deployDir, projectName, apiVersion, kind string) error { return renderWriteFile(filepath.Join(deployDir, "operator.yaml"), operatorTmplName, operatorYamlTmpl, opTd) } -func RenderTestYaml(c *Config, globalManifest, extraRoles, image string) error { +func RenderTestYaml(c *Config, image string) error { opTd := tmplData{ - GlobalManifest: globalManifest, - ExtraRoles: extraRoles, - ProjectName: c.ProjectName, - Image: image, + ProjectName: c.ProjectName, + Image: image, } - return renderWriteFile(filepath.Join(deployDir, "test-gen.yaml"), testYamlName, testYamlTmpl, opTd) + return renderWriteFile(filepath.Join(deployDir, "test-pod.yaml"), testYamlName, testYamlTmpl, opTd) } // RenderOlmCatalog generates catalog manifests "deploy/olm-catalog/*" @@ -479,10 +477,6 @@ type tmplData struct { CRDVersion string CSVName string CatalogVersion string - - // global manifest used for testing - GlobalManifest string - ExtraRoles string } // Creates all the necesary directories for the generated files diff --git a/pkg/generator/templates.go b/pkg/generator/templates.go index 770f8c16a1..ed7dd7440b 100644 --- a/pkg/generator/templates.go +++ b/pkg/generator/templates.go @@ -424,34 +424,7 @@ spec: version: {{.Version}} ` -const testYamlTmpl = `{{.GlobalManifest}} ---- -kind: Role -apiVersion: rbac.authorization.k8s.io/v1beta1 -metadata: - name: {{.ProjectName}}-test -rules: -- apiGroups: - - "rbac.authorization.k8s.io" - resources: - - "*" - verbs: - - "*" -{{.ExtraRoles}} ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1beta1 -metadata: - name: default-account-{{.ProjectName}}-test -subjects: -- kind: ServiceAccount - name: default -roleRef: - kind: Role - name: {{.ProjectName}}-test - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: v1 +const testYamlTmpl = `apiVersion: v1 kind: Pod metadata: name: {{.ProjectName}}-test From e4017710188de352822301f2a8eb2c14c50e246d Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Wed, 12 Sep 2018 17:07:32 -0700 Subject: [PATCH 06/35] commands/.../test: create new test subcommands --- commands/operator-sdk/cmd/test.go | 70 ++------------ commands/operator-sdk/cmd/test/cluster.go | 109 ++++++++++++++++++++++ commands/operator-sdk/cmd/test/local.go | 105 +++++++++++++++++++++ 3 files changed, 221 insertions(+), 63 deletions(-) create mode 100644 commands/operator-sdk/cmd/test/cluster.go create mode 100644 commands/operator-sdk/cmd/test/local.go diff --git a/commands/operator-sdk/cmd/test.go b/commands/operator-sdk/cmd/test.go index 22013fc936..9f7cea7e80 100644 --- a/commands/operator-sdk/cmd/test.go +++ b/commands/operator-sdk/cmd/test.go @@ -15,76 +15,20 @@ package cmd import ( - "io/ioutil" - "log" - "os" - "strings" - - "github.com/operator-framework/operator-sdk/pkg/test" + "github.com/operator-framework/operator-sdk/commands/operator-sdk/cmd/test" "github.com/spf13/cobra" ) -var ( - testLocation string - kubeconfig string - globalManifestPath string - namespacedManifestPath string - goTestFlags string -) - func NewTestCmd() *cobra.Command { testCmd := &cobra.Command{ - Use: "test --test-location [flags]", - Short: "Run End-To-End tests", - Run: testFunc, - } - defaultKubeConfig := "" - homedir, ok := os.LookupEnv("HOME") - if ok { - defaultKubeConfig = homedir + "/.kube/config" + Use: "test", + Short: "Tests the operator", + Long: `The test command has subcommands that can test the operator locally or from within a cluster. +`, } - testCmd.Flags().StringVarP(&testLocation, "test-location", "t", "", "Location of test files (e.g. ./test/e2e/)") - testCmd.MarkFlagRequired("test-location") - testCmd.Flags().StringVarP(&kubeconfig, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path") - testCmd.Flags().StringVarP(&globalManifestPath, "global-init", "g", "deploy/crd.yaml", "Path to manifest for Global resources (e.g. CRD manifest)") - testCmd.Flags().StringVarP(&namespacedManifestPath, "namespaced-init", "n", "", "Path to manifest for per-test, namespaced resources (e.g. RBAC and Operator manifest)") - testCmd.Flags().StringVarP(&goTestFlags, "go-test-flags", "f", "", "Additional flags to pass to go test") + testCmd.AddCommand(cmdtest.NewTestLocalCmd()) + testCmd.AddCommand(cmdtest.NewTestClusterCmd()) return testCmd } - -func testFunc(cmd *cobra.Command, args []string) { - // if no namespaced manifest path is given, combine deploy/rbac.yaml and deploy/operator.yaml - if namespacedManifestPath == "" { - os.Mkdir("deploy/test", os.FileMode(int(0775))) - namespacedManifestPath = "deploy/test/namespace-manifests.yaml" - rbac, err := ioutil.ReadFile("deploy/rbac.yaml") - if err != nil { - log.Fatalf("could not find rbac manifest: %v", err) - } - operator, err := ioutil.ReadFile("deploy/operator.yaml") - if err != nil { - log.Fatalf("could not find operator manifest: %v", err) - } - combined := append(rbac, []byte("\n---\n")...) - combined = append(combined, operator...) - err = ioutil.WriteFile(namespacedManifestPath, combined, os.FileMode(int(0664))) - if err != nil { - log.Fatalf("could not create temporary namespaced manifest file: %v", err) - } - defer func() { - err := os.Remove(namespacedManifestPath) - if err != nil { - log.Fatalf("could not delete temporary namespace manifest file") - } - }() - } - testArgs := []string{"test", testLocation + "/..."} - testArgs = append(testArgs, "-"+test.KubeConfigFlag, kubeconfig) - testArgs = append(testArgs, "-"+test.NamespacedManPathFlag, namespacedManifestPath) - testArgs = append(testArgs, "-"+test.GlobalManPathFlag, globalManifestPath) - testArgs = append(testArgs, "-"+test.ProjRootFlag, mustGetwd()) - testArgs = append(testArgs, strings.Split(goTestFlags, " ")...) - execCmd(os.Stdout, "go", testArgs...) -} diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go new file mode 100644 index 0000000000..27b4b5ca68 --- /dev/null +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -0,0 +1,109 @@ +// Copyright 2018 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 cmdtest + +import ( + "fmt" + "os" + "os/exec" + "time" + + cmdError "github.com/operator-framework/operator-sdk/commands/operator-sdk/error" + + "github.com/spf13/cobra" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +var ( + testNamespace string + kubeconfigCluster string + globalManifestPathCluster string +) + +func NewTestClusterCmd() *cobra.Command { + testCmd := &cobra.Command{ + Use: "test --test-location [flags]", + Short: "Run End-To-End tests", + Run: testClusterFunc, + } + defaultKubeConfig := "" + homedir, ok := os.LookupEnv("HOME") + if ok { + defaultKubeConfig = homedir + "/.kube/config" + } + testCmd.Flags().StringVarP(&testNamespace, "namespace", "n", "default", "Namespace to run tests in") + testCmd.Flags().StringVarP(&kubeconfigCluster, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path") + testCmd.Flags().StringVarP(&globalManifestPathCluster, "global-init", "g", "", "Path to manifest for Global resources (e.g. CRD manifest)") + + return testCmd +} + +func testClusterFunc(cmd *cobra.Command, args []string) { + if globalManifestPathCluster != "" { + globalCmd := exec.Command("kubectl", "create", "-f", globalManifestPathCluster) + cmdOut, err := globalCmd.CombinedOutput() + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("Could not create global resources: %v\nKubectl Output: %v", err, cmdOut)) + } + } + testPod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "operator-test", + }, + Spec: v1.PodSpec{ + RestartPolicy: v1.RestartPolicyNever, + Containers: []v1.Container{{ + Name: "operator-test", + Image: args[0], + ImagePullPolicy: v1.PullAlways, + Command: []string{"/go-test.sh"}, + Env: []v1.EnvVar{{ + Name: "TEST_NAMESPACE", + ValueFrom: &v1.EnvVarSource{FieldRef: &v1.ObjectFieldSelector{FieldPath: "metadata.namespace"}}, + }}, + }}, + }, + } + kubeconfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigCluster) + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to get kubeconfig: %v", err)) + } + kubeclient, err := kubernetes.NewForConfig(kubeconfig) + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to create kubeclient: %v", err)) + } + testPod, err = kubeclient.CoreV1().Pods(testNamespace).Create(testPod) + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to create test pod: %v", err)) + } + for { + testPod, err = kubeclient.CoreV1().Pods(testNamespace).Get("operator-test", metav1.GetOptions{}) + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to get test pod: %v", err)) + } + if testPod.Status.Phase != v1.PodSucceeded && testPod.Status.Phase != v1.PodFailed { + time.Sleep(time.Second * 5) + continue + } else if testPod.Status.Phase == v1.PodSucceeded { + fmt.Printf("Test Successfully Completed") + return + } else if testPod.Status.Phase == v1.PodFailed { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("Test Failed: %+v", kubeclient.CoreV1().Pods(testNamespace).GetLogs("operator-test", &v1.PodLogOptions{}))) + } + } +} diff --git a/commands/operator-sdk/cmd/test/local.go b/commands/operator-sdk/cmd/test/local.go new file mode 100644 index 0000000000..88a8c792f8 --- /dev/null +++ b/commands/operator-sdk/cmd/test/local.go @@ -0,0 +1,105 @@ +// Copyright 2018 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 cmdtest + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "strings" + + cmdError "github.com/operator-framework/operator-sdk/commands/operator-sdk/error" + "github.com/operator-framework/operator-sdk/pkg/test" + + "github.com/spf13/cobra" +) + +var ( + kubeconfig string + globalManifestPath string + namespacedManifestPath string + goTestFlags string +) + +func NewTestLocalCmd() *cobra.Command { + testCmd := &cobra.Command{ + Use: "test [flags]", + Short: "Run End-To-End tests", + Run: testLocalFunc, + } + defaultKubeConfig := "" + homedir, ok := os.LookupEnv("HOME") + if ok { + defaultKubeConfig = homedir + "/.kube/config" + } + testCmd.Flags().StringVarP(&kubeconfig, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path") + testCmd.Flags().StringVarP(&globalManifestPath, "global-init", "g", "deploy/crd.yaml", "Path to manifest for Global resources (e.g. CRD manifest)") + testCmd.Flags().StringVarP(&namespacedManifestPath, "namespaced-init", "n", "", "Path to manifest for per-test, namespaced resources (e.g. RBAC and Operator manifest)") + testCmd.Flags().StringVarP(&goTestFlags, "go-test-flags", "f", "", "Additional flags to pass to go test") + + return testCmd +} + +func testLocalFunc(cmd *cobra.Command, args []string) { + // if no namespaced manifest path is given, combine deploy/rbac.yaml and deploy/operator.yaml + if namespacedManifestPath == "" { + os.Mkdir("deploy/test", os.FileMode(int(0775))) + namespacedManifestPath = "deploy/test/namespace-manifests.yaml" + rbac, err := ioutil.ReadFile("deploy/rbac.yaml") + if err != nil { + log.Fatalf("could not find rbac manifest: %v", err) + } + operator, err := ioutil.ReadFile("deploy/operator.yaml") + if err != nil { + log.Fatalf("could not find operator manifest: %v", err) + } + combined := append(rbac, []byte("\n---\n")...) + combined = append(combined, operator...) + err = ioutil.WriteFile(namespacedManifestPath, combined, os.FileMode(int(0664))) + if err != nil { + log.Fatalf("could not create temporary namespaced manifest file: %v", err) + } + defer func() { + err := os.Remove(namespacedManifestPath) + if err != nil { + log.Fatalf("could not delete temporary namespace manifest file") + } + }() + } + testArgs := []string{"test", args[0] + "/..."} + testArgs = append(testArgs, "-"+test.KubeConfigFlag, kubeconfig) + testArgs = append(testArgs, "-"+test.NamespacedManPathFlag, namespacedManifestPath) + testArgs = append(testArgs, "-"+test.GlobalManPathFlag, globalManifestPath) + testArgs = append(testArgs, "-"+test.ProjRootFlag, mustGetwd()) + testArgs = append(testArgs, strings.Split(goTestFlags, " ")...) + dc := exec.Command("go", testArgs...) + dc.Dir = mustGetwd() + dc.Stdout = os.Stdout + dc.Stderr = os.Stderr + err := dc.Run() + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to exec %s %#v: %v", cmd, args, err)) + } +} + +func mustGetwd() string { + wd, err := os.Getwd() + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to determine the full path of the current directory: %v", err)) + } + return wd +} From 046725f266921e2cbdf50ecc0922fc4c297f6745 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Wed, 12 Sep 2018 17:08:37 -0700 Subject: [PATCH 07/35] .travis.yml: update travis.yml to match test subcommands --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a3aafaefd4..415d7948ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,9 +29,9 @@ script: - go test ./test/e2e/... - cd test/test-framework # test framework with defaults -- operator-sdk test -t . +- operator-sdk test local . # test operator-sdk test flags -- operator-sdk test -t . -g deploy/crd.yaml -n deploy/namespace-init.yaml -f "-parallel 1" -k $HOME/.kube/config +- operator-sdk test local . -g deploy/crd.yaml -n deploy/namespace-init.yaml -f "-parallel 1" -k $HOME/.kube/config # go back to project root - cd ../.. - go vet ./... From aa34ac0f7f7795f8ea15c1ff3cbc8b3fac468f49 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Thu, 13 Sep 2018 09:31:08 -0700 Subject: [PATCH 08/35] commands/.../test/cluster.go: fix error capitalization --- commands/operator-sdk/cmd/test/cluster.go | 4 ++-- commands/operator-sdk/cmd/test/local.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index 27b4b5ca68..1c780b2b68 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -58,7 +58,7 @@ func testClusterFunc(cmd *cobra.Command, args []string) { globalCmd := exec.Command("kubectl", "create", "-f", globalManifestPathCluster) cmdOut, err := globalCmd.CombinedOutput() if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("Could not create global resources: %v\nKubectl Output: %v", err, cmdOut)) + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("could not create global resources: %v\nKubectl Output: %v", err, cmdOut)) } } testPod := &v1.Pod{ @@ -103,7 +103,7 @@ func testClusterFunc(cmd *cobra.Command, args []string) { fmt.Printf("Test Successfully Completed") return } else if testPod.Status.Phase == v1.PodFailed { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("Test Failed: %+v", kubeclient.CoreV1().Pods(testNamespace).GetLogs("operator-test", &v1.PodLogOptions{}))) + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("test Failed: %+v", kubeclient.CoreV1().Pods(testNamespace).GetLogs("operator-test", &v1.PodLogOptions{}))) } } } diff --git a/commands/operator-sdk/cmd/test/local.go b/commands/operator-sdk/cmd/test/local.go index 88a8c792f8..d4edab9e3c 100644 --- a/commands/operator-sdk/cmd/test/local.go +++ b/commands/operator-sdk/cmd/test/local.go @@ -92,7 +92,7 @@ func testLocalFunc(cmd *cobra.Command, args []string) { dc.Stderr = os.Stderr err := dc.Run() if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to exec %s %#v: %v", cmd, args, err)) + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to exec %v %#v: %v", cmd, args, err)) } } From becee993a62fa6a92a6dfd1467004b9f7d0e8f50 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Thu, 13 Sep 2018 09:50:53 -0700 Subject: [PATCH 09/35] commands/.../test/*: fix subcommands --- commands/operator-sdk/cmd/test/cluster.go | 4 ++-- commands/operator-sdk/cmd/test/local.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index 1c780b2b68..998fb5e11b 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -37,8 +37,8 @@ var ( func NewTestClusterCmd() *cobra.Command { testCmd := &cobra.Command{ - Use: "test --test-location [flags]", - Short: "Run End-To-End tests", + Use: "cluster [flags]", + Short: "Run End-To-End tests using image with embedded test binary", Run: testClusterFunc, } defaultKubeConfig := "" diff --git a/commands/operator-sdk/cmd/test/local.go b/commands/operator-sdk/cmd/test/local.go index d4edab9e3c..050fe4d718 100644 --- a/commands/operator-sdk/cmd/test/local.go +++ b/commands/operator-sdk/cmd/test/local.go @@ -37,8 +37,8 @@ var ( func NewTestLocalCmd() *cobra.Command { testCmd := &cobra.Command{ - Use: "test [flags]", - Short: "Run End-To-End tests", + Use: "local [flags]", + Short: "Run End-To-End tests locally", Run: testLocalFunc, } defaultKubeConfig := "" From 2790687c32a2ab61fe242ea463e26e171d909ecf Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Thu, 13 Sep 2018 10:58:59 -0700 Subject: [PATCH 10/35] commands/.../test/*: make the tests work and improve output --- commands/operator-sdk/cmd/test/cluster.go | 33 ++++++++++++++++++++--- commands/operator-sdk/cmd/test/local.go | 3 +++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index 998fb5e11b..619a93d503 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -15,6 +15,7 @@ package cmdtest import ( + "bytes" "fmt" "os" "os/exec" @@ -54,12 +55,22 @@ func NewTestClusterCmd() *cobra.Command { } func testClusterFunc(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmdError.ExitWithError(cmdError.ExitBadArgs, fmt.Errorf("operator-sdk test cluster requires exactly 1 argument")) + } if globalManifestPathCluster != "" { globalCmd := exec.Command("kubectl", "create", "-f", globalManifestPathCluster) cmdOut, err := globalCmd.CombinedOutput() if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("could not create global resources: %v\nKubectl Output: %v", err, cmdOut)) + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("could not create global resources: %v\nKubectl Output: %v", err, string(cmdOut))) } + defer func() { + globalCmd := exec.Command("kubectl", "delete", "-f", globalManifestPathCluster) + cmdOut, err := globalCmd.CombinedOutput() + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("could not delete global resources: %v\nKubectl Output: %v", err, string(cmdOut))) + } + }() } testPod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -91,8 +102,14 @@ func testClusterFunc(cmd *cobra.Command, args []string) { if err != nil { cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to create test pod: %v", err)) } + defer func() { + err = kubeclient.CoreV1().Pods(testNamespace).Delete(testPod.Name, &metav1.DeleteOptions{}) + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to delete test pod")) + } + }() for { - testPod, err = kubeclient.CoreV1().Pods(testNamespace).Get("operator-test", metav1.GetOptions{}) + testPod, err = kubeclient.CoreV1().Pods(testNamespace).Get(testPod.Name, metav1.GetOptions{}) if err != nil { cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to get test pod: %v", err)) } @@ -100,10 +117,18 @@ func testClusterFunc(cmd *cobra.Command, args []string) { time.Sleep(time.Second * 5) continue } else if testPod.Status.Phase == v1.PodSucceeded { - fmt.Printf("Test Successfully Completed") + fmt.Printf("Test Successfully Completed\n") return } else if testPod.Status.Phase == v1.PodFailed { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("test Failed: %+v", kubeclient.CoreV1().Pods(testNamespace).GetLogs("operator-test", &v1.PodLogOptions{}))) + req := kubeclient.CoreV1().Pods(testNamespace).GetLogs(testPod.Name, &v1.PodLogOptions{}) + readCloser, err := req.Stream() + if err != nil { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("test failed and failed to get error logs")) + } + defer readCloser.Close() + buf := new(bytes.Buffer) + buf.ReadFrom(readCloser) + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("test failed:\n%+v", buf.String())) } } } diff --git a/commands/operator-sdk/cmd/test/local.go b/commands/operator-sdk/cmd/test/local.go index 050fe4d718..91096644fc 100644 --- a/commands/operator-sdk/cmd/test/local.go +++ b/commands/operator-sdk/cmd/test/local.go @@ -55,6 +55,9 @@ func NewTestLocalCmd() *cobra.Command { } func testLocalFunc(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmdError.ExitWithError(cmdError.ExitBadArgs, fmt.Errorf("operator-sdk test local requires exactly 1 argument")) + } // if no namespaced manifest path is given, combine deploy/rbac.yaml and deploy/operator.yaml if namespacedManifestPath == "" { os.Mkdir("deploy/test", os.FileMode(int(0775))) From 49fc1c1fd86de61d94d42bcf591e2617cd510817 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Thu, 13 Sep 2018 13:56:05 -0700 Subject: [PATCH 11/35] commands/.../test/cluster.go: return errors instead of cmderror The cluster test has defers that need to run on error, which would not run if cmdError is used due to its use of os.Exit(). Instead, we can just return an error, which allows the defer functions to run. --- commands/operator-sdk/cmd/test/cluster.go | 45 +++++++---------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index 619a93d503..d1a6efcd5e 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -18,11 +18,8 @@ import ( "bytes" "fmt" "os" - "os/exec" "time" - cmdError "github.com/operator-framework/operator-sdk/commands/operator-sdk/error" - "github.com/spf13/cobra" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,16 +28,15 @@ import ( ) var ( - testNamespace string - kubeconfigCluster string - globalManifestPathCluster string + testNamespace string + kubeconfigCluster string ) func NewTestClusterCmd() *cobra.Command { testCmd := &cobra.Command{ Use: "cluster [flags]", Short: "Run End-To-End tests using image with embedded test binary", - Run: testClusterFunc, + RunE: testClusterFunc, } defaultKubeConfig := "" homedir, ok := os.LookupEnv("HOME") @@ -49,28 +45,13 @@ func NewTestClusterCmd() *cobra.Command { } testCmd.Flags().StringVarP(&testNamespace, "namespace", "n", "default", "Namespace to run tests in") testCmd.Flags().StringVarP(&kubeconfigCluster, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path") - testCmd.Flags().StringVarP(&globalManifestPathCluster, "global-init", "g", "", "Path to manifest for Global resources (e.g. CRD manifest)") return testCmd } -func testClusterFunc(cmd *cobra.Command, args []string) { +func testClusterFunc(cmd *cobra.Command, args []string) error { if len(args) != 1 { - cmdError.ExitWithError(cmdError.ExitBadArgs, fmt.Errorf("operator-sdk test cluster requires exactly 1 argument")) - } - if globalManifestPathCluster != "" { - globalCmd := exec.Command("kubectl", "create", "-f", globalManifestPathCluster) - cmdOut, err := globalCmd.CombinedOutput() - if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("could not create global resources: %v\nKubectl Output: %v", err, string(cmdOut))) - } - defer func() { - globalCmd := exec.Command("kubectl", "delete", "-f", globalManifestPathCluster) - cmdOut, err := globalCmd.CombinedOutput() - if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("could not delete global resources: %v\nKubectl Output: %v", err, string(cmdOut))) - } - }() + return fmt.Errorf("operator-sdk test cluster requires exactly 1 argument") } testPod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -92,43 +73,43 @@ func testClusterFunc(cmd *cobra.Command, args []string) { } kubeconfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigCluster) if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to get kubeconfig: %v", err)) + return fmt.Errorf("failed to get kubeconfig: %v", err) } kubeclient, err := kubernetes.NewForConfig(kubeconfig) if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to create kubeclient: %v", err)) + return fmt.Errorf("failed to create kubeclient: %v", err) } testPod, err = kubeclient.CoreV1().Pods(testNamespace).Create(testPod) if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to create test pod: %v", err)) + return fmt.Errorf("failed to create test pod: %v", err) } defer func() { err = kubeclient.CoreV1().Pods(testNamespace).Delete(testPod.Name, &metav1.DeleteOptions{}) if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to delete test pod")) + fmt.Printf("Warning: failed to delete test pod") } }() for { testPod, err = kubeclient.CoreV1().Pods(testNamespace).Get(testPod.Name, metav1.GetOptions{}) if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to get test pod: %v", err)) + return fmt.Errorf("failed to get test pod: %v", err) } if testPod.Status.Phase != v1.PodSucceeded && testPod.Status.Phase != v1.PodFailed { time.Sleep(time.Second * 5) continue } else if testPod.Status.Phase == v1.PodSucceeded { fmt.Printf("Test Successfully Completed\n") - return + return nil } else if testPod.Status.Phase == v1.PodFailed { req := kubeclient.CoreV1().Pods(testNamespace).GetLogs(testPod.Name, &v1.PodLogOptions{}) readCloser, err := req.Stream() if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("test failed and failed to get error logs")) + return fmt.Errorf("test failed and failed to get error logs") } defer readCloser.Close() buf := new(bytes.Buffer) buf.ReadFrom(readCloser) - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("test failed:\n%+v", buf.String())) + return fmt.Errorf("test failed:\n%+v", buf.String()) } } } From d6f22e34b7327372071e3918d646c4dc89bd49ed Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Thu, 13 Sep 2018 16:52:07 -0700 Subject: [PATCH 12/35] *: add tests for incluster test image and fix bugs This adds tests for the new incluster image testing mode as well as fixes some bugs that this test exposed --- commands/operator-sdk/cmd/test/cluster.go | 24 +++- pkg/generator/generator.go | 8 +- .../e2e/incluster-test-code/main_test.go.tmpl | 25 ++++ .../memcached_test.go.tmpl | 124 ++++++++++++++++++ test/e2e/memcached_test.go | 84 +++++++++--- test/test-framework/memcached_test.go | 3 + 6 files changed, 243 insertions(+), 25 deletions(-) create mode 100644 test/e2e/incluster-test-code/main_test.go.tmpl create mode 100644 test/e2e/incluster-test-code/memcached_test.go.tmpl diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index d1a6efcd5e..9a385136e4 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -30,6 +30,7 @@ import ( var ( testNamespace string kubeconfigCluster string + imagePullPolicy bool ) func NewTestClusterCmd() *cobra.Command { @@ -45,6 +46,7 @@ func NewTestClusterCmd() *cobra.Command { } testCmd.Flags().StringVarP(&testNamespace, "namespace", "n", "default", "Namespace to run tests in") testCmd.Flags().StringVarP(&kubeconfigCluster, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path") + testCmd.Flags().BoolVarP(&imagePullPolicy, "imagePullPolicy", "i", false, "Set test pod image pull policy to 'Never'") return testCmd } @@ -53,6 +55,12 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("operator-sdk test cluster requires exactly 1 argument") } + var pullPolicy v1.PullPolicy + if imagePullPolicy { + pullPolicy = v1.PullNever + } else { + pullPolicy = v1.PullAlways + } testPod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "operator-test", @@ -62,7 +70,7 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { Containers: []v1.Container{{ Name: "operator-test", Image: args[0], - ImagePullPolicy: v1.PullAlways, + ImagePullPolicy: pullPolicy, Command: []string{"/go-test.sh"}, Env: []v1.EnvVar{{ Name: "TEST_NAMESPACE", @@ -83,12 +91,14 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("failed to create test pod: %v", err) } - defer func() { - err = kubeclient.CoreV1().Pods(testNamespace).Delete(testPod.Name, &metav1.DeleteOptions{}) - if err != nil { - fmt.Printf("Warning: failed to delete test pod") - } - }() + /* + defer func() { + err = kubeclient.CoreV1().Pods(testNamespace).Delete(testPod.Name, &metav1.DeleteOptions{}) + if err != nil { + fmt.Printf("Warning: failed to delete test pod") + } + }() + */ for { testPod, err = kubeclient.CoreV1().Pods(testNamespace).Get(testPod.Name, metav1.GetOptions{}) if err != nil { diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index 7b8c5e6b26..610a065ac7 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -54,7 +54,7 @@ const ( build = "build.sh" dockerBuild = "docker_build.sh" dockerfile = "Dockerfile" - testingDockerfile = "Dockerfile_Testing" + testingDockerfile = "Dockerfile" goTest = "go-test.sh" boilerplate = "boilerplate.go.txt" updateGenerated = "update-generated.sh" @@ -364,7 +364,11 @@ func renderBuildFiles(buildDir, repoPath, projectName string) error { return err } - return renderWriteFile(filepath.Join(buildDir, goTest), "tmp/build/go-test.sh", goTestScript, tmplData{}) + buf = &bytes.Buffer{} + if err := renderFile(buf, filepath.Join(buildDir, goTest), goTestScript, tmplData{}); err != nil { + return err + } + return writeFileAndPrint(filepath.Join(buildDir, goTest), buf.Bytes(), os.FileMode(int(0755))) } func renderDockerBuildFile(w io.Writer) error { diff --git a/test/e2e/incluster-test-code/main_test.go.tmpl b/test/e2e/incluster-test-code/main_test.go.tmpl new file mode 100644 index 0000000000..210d906294 --- /dev/null +++ b/test/e2e/incluster-test-code/main_test.go.tmpl @@ -0,0 +1,25 @@ +// Copyright 2018 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 + +import ( + "testing" + + f "github.com/operator-framework/operator-sdk/pkg/test" +) + +func TestMain(m *testing.M) { + f.MainEntry(m) +} diff --git a/test/e2e/incluster-test-code/memcached_test.go.tmpl b/test/e2e/incluster-test-code/memcached_test.go.tmpl new file mode 100644 index 0000000000..294ff06bd2 --- /dev/null +++ b/test/e2e/incluster-test-code/memcached_test.go.tmpl @@ -0,0 +1,124 @@ +// Copyright 2018 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 + +import ( + goctx "context" + "fmt" + "testing" + "time" + + operator "github.com/example-inc/memcached-operator/pkg/apis/cache/v1alpha1" + framework "github.com/operator-framework/operator-sdk/pkg/test" + "github.com/operator-framework/operator-sdk/pkg/test/e2eutil" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +var ( + retryInterval = time.Second * 5 + timeout = time.Second * 30 +) + +func TestMemcached(t *testing.T) { + memcachedList := &operator.MemcachedList{ + TypeMeta: metav1.TypeMeta{ + Kind: "Memcached", + APIVersion: "cache.example.com/v1alpha1", + }, + } + err := framework.AddToFrameworkScheme(operator.AddToScheme, memcachedList) + if err != nil { + t.Fatalf("failed to add custom resource scheme to framework: %v", err) + } + // run subtests + t.Run("memcached-group", func(t *testing.T) { + t.Run("Cluster", MemcachedCluster) + t.Run("Cluster2", MemcachedCluster) + }) +} + +func memcachedScaleTest(t *testing.T, f *framework.Framework, ctx *framework.TestCtx) error { + namespace, err := ctx.GetNamespace() + if err != nil { + return fmt.Errorf("could not get namespace: %v", err) + } + // create memcached custom resource + exampleMemcached := &operator.Memcached{ + TypeMeta: metav1.TypeMeta{ + Kind: "Memcached", + APIVersion: "cache.example.com/v1alpha1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "example-memcached", + Namespace: namespace, + }, + Spec: operator.MemcachedSpec{ + Size: 3, + }, + } + err = f.DynamicClient.Create(goctx.TODO(), exampleMemcached) + if err != nil { + return err + } + ctx.AddFinalizerFn(func() error { + return f.DynamicClient.Delete(goctx.TODO(), exampleMemcached) + }) + // wait for example-memcached to reach 3 replicas + err = e2eutil.WaitForDeployment(t, f.KubeClient, namespace, "example-memcached", 3, retryInterval, timeout) + if err != nil { + return err + } + + err = f.DynamicClient.Get(goctx.TODO(), types.NamespacedName{Name: "example-memcached", Namespace: namespace}, exampleMemcached) + if err != nil { + return err + } + exampleMemcached.Spec.Size = 4 + err = f.DynamicClient.Update(goctx.TODO(), exampleMemcached) + if err != nil { + return err + } + + // wait for example-memcached to reach 4 replicas + return e2eutil.WaitForDeployment(t, f.KubeClient, namespace, "example-memcached", 4, retryInterval, timeout) +} + +func MemcachedCluster(t *testing.T) { + t.Parallel() + ctx := framework.NewTestCtx(t) + defer ctx.Cleanup(t) + err := ctx.InitializeClusterResources() + if err != nil { + t.Fatalf("failed to initialize cluster resources: %v", err) + } + t.Log("Initialized cluster resources") + namespace, err := ctx.GetNamespace() + if err != nil { + t.Fatal(err) + } + // get global framework variables + f := framework.Global + // wait for memcached-operator to be ready + err = e2eutil.WaitForDeployment(t, f.KubeClient, namespace, "memcached-operator", 1, retryInterval, timeout) + if err != nil { + t.Fatal(err) + } + + if err = memcachedScaleTest(t, f, ctx); err != nil { + t.Fatal(err) + } +} diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index 5d1175a7fb..cc63224f86 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -61,9 +61,6 @@ func TestMemcached(t *testing.T) { ctx.AddFinalizerFn(func() error { return os.RemoveAll(path.Join(gopath, "/src/github.com/example-inc/memcached-operator")) }) os.Chdir("memcached-operator") - os.RemoveAll("vendor/github.com/operator-framework/operator-sdk/pkg") - os.Symlink(path.Join(gopath, "/src/github.com/operator-framework/operator-sdk/pkg"), - "vendor/github.com/operator-framework/operator-sdk/pkg") handlerFile, err := os.Create("pkg/stub/handler.go") if err != nil { t.Fatal(err) @@ -98,6 +95,33 @@ func TestMemcached(t *testing.T) { t.Fatalf("error: %v\nCommand Output: %s\n", err, string(cmdOut)) } + t.Log("Copying test files to ./test") + if err = os.MkdirAll("./test", os.FileMode(int(0755))); err != nil { + t.Fatalf("could not create test/e2e dir: %v", err) + } + cmdOut, err = exec.Command("cp", "-a", path.Join(gopath, "/src/github.com/operator-framework/operator-sdk/test/e2e/incluster-test-code"), "./test/e2e").CombinedOutput() + if err != nil { + t.Fatalf("could not copy tests to test/e2e: %v\nCommand Output:\n%v", err, string(cmdOut)) + } + // fix naming of files + cmdOut, err = exec.Command("mv", "test/e2e/main_test.go.tmpl", "test/e2e/main_test.go").CombinedOutput() + if err != nil { + t.Fatalf("could not rename test/e2e/main_test.go.tmpl: %v\nCommand Output:\n%v", err, string(cmdOut)) + } + cmdOut, err = exec.Command("mv", "test/e2e/memcached_test.go.tmpl", "test/e2e/memcached_test.go").CombinedOutput() + if err != nil { + t.Fatalf("could not rename test/e2e/memcached_test.go.tmpl: %v\nCommand Output:\n%v", err, string(cmdOut)) + } + t.Log("Pulling new dependencies with dep ensure") + cmdOut, err = exec.Command("dep", "ensure").CombinedOutput() + if err != nil { + t.Fatalf("dep ensure failed: %v\nCommand Output:\n%v", err, string(cmdOut)) + } + // use current operator-sdk code + os.RemoveAll("vendor/github.com/operator-framework/operator-sdk/pkg") + os.Symlink(path.Join(gopath, "/src/github.com/operator-framework/operator-sdk/pkg"), + "vendor/github.com/operator-framework/operator-sdk/pkg") + // create crd crdYAML, err := ioutil.ReadFile("deploy/crd.yaml") err = ctx.CreateFromYAML(crdYAML) @@ -106,9 +130,10 @@ func TestMemcached(t *testing.T) { } t.Log("Created crd") - // run both subtests + // run subtests t.Run("memcached-group", func(t *testing.T) { t.Run("Cluster", MemcachedCluster) + t.Run("ClusterTest", MemcachedClusterTest) t.Run("Local", MemcachedLocal) }) } @@ -209,20 +234,10 @@ func MemcachedCluster(t *testing.T) { f := framework.Global ctx := f.NewTestCtx(t) defer ctx.Cleanup(t) + operatorYAML, err := ioutil.ReadFile("deploy/operator.yaml") local := *f.ImageName == "" if local { *f.ImageName = "quay.io/example/memcached-operator:v0.0.1" - } - t.Log("Building operator docker image") - cmdOut, err := exec.Command("operator-sdk", "build", *f.ImageName).CombinedOutput() - if err != nil { - t.Fatalf("error: %v\nCommand Output: %s\n", err, string(cmdOut)) - } - - operatorYAML, err := ioutil.ReadFile("deploy/operator.yaml") - operatorYAML = bytes.Replace(operatorYAML, []byte("REPLACE_IMAGE"), []byte(*f.ImageName), 1) - - if local { if err != nil { t.Fatal(err) } @@ -231,7 +246,19 @@ func MemcachedCluster(t *testing.T) { if err != nil { t.Fatal(err) } - } else { + } + operatorYAML = bytes.Replace(operatorYAML, []byte("REPLACE_IMAGE"), []byte(*f.ImageName), 1) + err = ioutil.WriteFile("deploy/operator.yaml", operatorYAML, os.FileMode(0644)) + if err != nil { + t.Fatalf("failed to write deploy/operator.yaml: %v", err) + } + t.Log("Building operator docker image") + cmdOut, err := exec.Command("operator-sdk", "build", *f.ImageName, "-e", "-t", "./test/e2e", "-n", "deploy/operator.yaml").CombinedOutput() + if err != nil { + t.Fatalf("error: %v\nCommand Output: %s\n", err, string(cmdOut)) + } + + if !local { t.Log("Pushing docker image to repo") cmdOut, err = exec.Command("docker", "push", *f.ImageName).CombinedOutput() if err != nil { @@ -275,3 +302,28 @@ func MemcachedCluster(t *testing.T) { t.Fatal(err) } } + +func MemcachedClusterTest(t *testing.T) { + // get global framework variables + f := framework.Global + ctx := f.NewTestCtx(t) + defer ctx.Cleanup(t) + + // create rbac + rbacYAML, err := ioutil.ReadFile("deploy/rbac.yaml") + err = ctx.CreateFromYAML(rbacYAML) + if err != nil { + t.Fatalf("failed to create rbac: %v", err) + } + t.Log("Created rbac") + + namespace, err := ctx.GetNamespace() + if err != nil { + t.Fatalf("could not get namespace: %v", err) + } + cmdOut, err := exec.Command("operator-sdk", "test", "cluster", *f.ImageName, "-n", namespace, "-i").CombinedOutput() + if err != nil { + t.Fatalf("in-cluster test failed: %v\nCommand Output:\n%s", err, string(cmdOut)) + } + +} diff --git a/test/test-framework/memcached_test.go b/test/test-framework/memcached_test.go index 131b301a2d..c11370594d 100644 --- a/test/test-framework/memcached_test.go +++ b/test/test-framework/memcached_test.go @@ -74,6 +74,9 @@ func memcachedScaleTest(t *testing.T, f *framework.Framework, ctx *framework.Tes if err != nil { return err } + ctx.AddFinalizerFn(func() error { + return f.DynamicClient.Delete(goctx.TODO(), exampleMemcached) + }) // wait for example-memcached to reach 3 replicas err = e2eutil.WaitForDeployment(t, f.KubeClient, namespace, "example-memcached", 3, retryInterval, timeout) if err != nil { From 2441947f5565a0f899367718ffbaed20eeb5f45d Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Fri, 14 Sep 2018 09:25:57 -0700 Subject: [PATCH 13/35] commands/.../test/cluster.go: uncomment deferred cleanup --- commands/operator-sdk/cmd/test/cluster.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index 9a385136e4..4129352c1b 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -91,14 +91,12 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("failed to create test pod: %v", err) } - /* - defer func() { - err = kubeclient.CoreV1().Pods(testNamespace).Delete(testPod.Name, &metav1.DeleteOptions{}) - if err != nil { - fmt.Printf("Warning: failed to delete test pod") - } - }() - */ + defer func() { + err = kubeclient.CoreV1().Pods(testNamespace).Delete(testPod.Name, &metav1.DeleteOptions{}) + if err != nil { + fmt.Printf("Warning: failed to delete test pod") + } + }() for { testPod, err = kubeclient.CoreV1().Pods(testNamespace).Get(testPod.Name, metav1.GetOptions{}) if err != nil { From 73ea6e21e9fc4c517f56f6e1af53709fcf0f152a Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Fri, 14 Sep 2018 12:00:15 -0700 Subject: [PATCH 14/35] commands/.../build.go: shorten code a bit We don't need to do any extra operations anymore if a user does not supply a namespaced manifest path, so we can just use a default string --- commands/operator-sdk/cmd/build.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 78ef5f2d28..47016beddb 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -56,7 +56,7 @@ For example: } buildCmd.Flags().BoolVarP(&enableTests, "enable-tests", "e", false, "Enable in-cluster testing by adding test binary to the image") buildCmd.Flags().StringVarP(&testLocationBuild, "test-location", "t", "./test/e2e", "Location of tests") - buildCmd.Flags().StringVarP(&namespacedManBuild, "namespaced", "n", "", "Path of namespaced resources for tests") + buildCmd.Flags().StringVarP(&namespacedManBuild, "namespaced", "n", "deploy/operator.yaml", "Path of namespaced resources for tests") return buildCmd } @@ -103,9 +103,6 @@ func verifyDeploymentImage(yamlFile []byte, imageName string) string { } func renderTestManifest(image string) string { - if namespacedManBuild == "" { - namespacedManBuild = "deploy/operator.yaml" - } namespacedBytes, err := ioutil.ReadFile(namespacedManBuild) if err != nil { log.Fatalf("could not read rbac manifest: %v", err) From 4f303c7fd4455521d0708dfb5813a36ab3279565 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Fri, 14 Sep 2018 12:02:41 -0700 Subject: [PATCH 15/35] commands/.../build.go: more code shortening --- commands/operator-sdk/cmd/build.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 47016beddb..b99a9512c5 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -107,12 +107,10 @@ func renderTestManifest(image string) string { if err != nil { log.Fatalf("could not read rbac manifest: %v", err) } - genWarning := verifyDeploymentImage(namespacedBytes, image) - c := cmdutil.GetConfig() - if err = generator.RenderTestYaml(c, image); err != nil { + if err = generator.RenderTestYaml(cmdutil.GetConfig(), image); err != nil { cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to generate deploy/test-pod.yaml: (%v)", err)) } - return genWarning + return verifyDeploymentImage(namespacedBytes, image) } const ( From 128f9354435a093f6ef73753d783b741377e929d Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Fri, 14 Sep 2018 12:06:13 -0700 Subject: [PATCH 16/35] commands/.../build.go: change intermediateImageName to baseImageName --- commands/operator-sdk/cmd/build.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index b99a9512c5..27f376d88d 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -134,20 +134,20 @@ func buildFunc(cmd *cobra.Command, args []string) { genWarning := "" image := args[0] - intermediateImageName := image + baseImageName := image if enableTests { genWarning = renderTestManifest(image) - intermediateImageName += "-intermediate" + baseImageName += "-intermediate" } - dbcmd := exec.Command("docker", "build", ".", "-f", "tmp/build/Dockerfile", "-t", intermediateImageName) + dbcmd := exec.Command("docker", "build", ".", "-f", "tmp/build/Dockerfile", "-t", baseImageName) o, err = dbcmd.CombinedOutput() if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to output build image %v: (%v)", intermediateImageName, string(o))) + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to output build image %v: (%v)", baseImageName, string(o))) } fmt.Fprintln(os.Stdout, string(o)) if enableTests { - testDbcmd := exec.Command("docker", "build", ".", "-f", "tmp/build/test-framework/Dockerfile", "-t", image, "--build-arg", "NAMESPACEDMAN="+namespacedManBuild, "--build-arg", "BASEIMAGE="+intermediateImageName) + testDbcmd := exec.Command("docker", "build", ".", "-f", "tmp/build/test-framework/Dockerfile", "-t", image, "--build-arg", "NAMESPACEDMAN="+namespacedManBuild, "--build-arg", "BASEIMAGE="+baseImageName) o, err = testDbcmd.CombinedOutput() if err != nil { cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to output build image %v: (%v)", image, string(o))) From b4d18dc2985eb2ec8852d5b696df8d60a5a2dc5b Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Fri, 14 Sep 2018 13:54:46 -0700 Subject: [PATCH 17/35] commands/.../test/cluster.go: clean up error messages --- commands/operator-sdk/cmd/test/cluster.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index 4129352c1b..f4132f8866 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -52,9 +52,14 @@ func NewTestClusterCmd() *cobra.Command { } func testClusterFunc(cmd *cobra.Command, args []string) error { + // in main.go, we catch and print errors, so we don't want cobra to print the error itself + cmd.SilenceErrors = true if len(args) != 1 { return fmt.Errorf("operator-sdk test cluster requires exactly 1 argument") } + // cobra prints its help message on error; we silence that here because any errors below + // are due to the test failing, not incorrect user input + cmd.SilenceUsage = true var pullPolicy v1.PullPolicy if imagePullPolicy { pullPolicy = v1.PullNever From 695995d4b8a02fd8eedc92dba6bc5e8bc452e2e7 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Mon, 17 Sep 2018 16:25:18 -0700 Subject: [PATCH 18/35] commands/.../cmd/test: more error message improvements --- commands/operator-sdk/cmd/test/cluster.go | 2 +- commands/operator-sdk/cmd/test/local.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index f4132f8866..52ba82656a 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -122,7 +122,7 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { defer readCloser.Close() buf := new(bytes.Buffer) buf.ReadFrom(readCloser) - return fmt.Errorf("test failed:\n%+v", buf.String()) + return fmt.Errorf("test failed:\n%s", buf.String()) } } } diff --git a/commands/operator-sdk/cmd/test/local.go b/commands/operator-sdk/cmd/test/local.go index 91096644fc..296d928359 100644 --- a/commands/operator-sdk/cmd/test/local.go +++ b/commands/operator-sdk/cmd/test/local.go @@ -95,7 +95,7 @@ func testLocalFunc(cmd *cobra.Command, args []string) { dc.Stderr = os.Stderr err := dc.Run() if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to exec %v %#v: %v", cmd, args, err)) + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to exec `go %s`: %v", strings.Join(testArgs, " "), err)) } } From ddcf87835c19276147b82763c45b35b426516bc5 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Wed, 19 Sep 2018 11:09:43 -0700 Subject: [PATCH 19/35] commands/.../test: changes suggested by shawn-hurley --- commands/operator-sdk/cmd/build.go | 12 ++++++++++-- commands/operator-sdk/cmd/test/cluster.go | 4 +--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 27f376d88d..1997a5fe48 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -70,7 +70,11 @@ func verifyDeploymentImage(yamlFile []byte, imageName string) string { fmt.Printf("WARNING: Could not unmarshal yaml namespaced spec") return "" } - if yamlMap["kind"].(string) == "Deployment" { + kind, ok := yamlMap["kind"].(string) + if !ok { + log.Fatal("Yaml manifest file contains a 'kind' field that is not a string") + } + if kind == "Deployment" { // this is ugly and hacky; we should probably make this cleaner nestedMap, ok := yamlMap["spec"].(map[string]interface{}) if !ok { @@ -142,7 +146,11 @@ func buildFunc(cmd *cobra.Command, args []string) { dbcmd := exec.Command("docker", "build", ".", "-f", "tmp/build/Dockerfile", "-t", baseImageName) o, err = dbcmd.CombinedOutput() if err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to output build image %v: (%v)", baseImageName, string(o))) + if enableTests { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to build intermediate image for %s image: (%s)", image, string(o))) + } else { + cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to output build image %s: (%s)", image, string(o))) + } } fmt.Fprintln(os.Stdout, string(o)) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index 52ba82656a..9add8f9e4a 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -60,11 +60,9 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { // cobra prints its help message on error; we silence that here because any errors below // are due to the test failing, not incorrect user input cmd.SilenceUsage = true - var pullPolicy v1.PullPolicy + pullPolicy := v1.PullAlways if imagePullPolicy { pullPolicy = v1.PullNever - } else { - pullPolicy = v1.PullAlways } testPod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ From 3db2baf4ee1b6331626b8f7a92e8b87a6a1dc2b4 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Wed, 19 Sep 2018 14:42:51 -0700 Subject: [PATCH 20/35] commands/.../test/cluster.go: change imagePullPolicyFlag Now the imagePullPolicy flag is a string that accepts 'Always' or 'Never' --- commands/operator-sdk/cmd/test/cluster.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index 9add8f9e4a..1d31ffac82 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -30,7 +30,7 @@ import ( var ( testNamespace string kubeconfigCluster string - imagePullPolicy bool + imagePullPolicy string ) func NewTestClusterCmd() *cobra.Command { @@ -46,7 +46,7 @@ func NewTestClusterCmd() *cobra.Command { } testCmd.Flags().StringVarP(&testNamespace, "namespace", "n", "default", "Namespace to run tests in") testCmd.Flags().StringVarP(&kubeconfigCluster, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path") - testCmd.Flags().BoolVarP(&imagePullPolicy, "imagePullPolicy", "i", false, "Set test pod image pull policy to 'Never'") + testCmd.Flags().StringVarP(&imagePullPolicy, "imagePullPolicy", "i", "Always", "Set test pod image pull policy. Allowed values: Always, Never") return testCmd } @@ -57,13 +57,17 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("operator-sdk test cluster requires exactly 1 argument") } + var pullPolicy v1.PullPolicy + if imagePullPolicy == "Always" { + pullPolicy = v1.PullAlways + } else if imagePullPolicy == "Never" { + pullPolicy = v1.PullNever + } else { + return fmt.Errorf("Invalid imagePullPolicy '%v'", imagePullPolicy) + } // cobra prints its help message on error; we silence that here because any errors below // are due to the test failing, not incorrect user input cmd.SilenceUsage = true - pullPolicy := v1.PullAlways - if imagePullPolicy { - pullPolicy = v1.PullNever - } testPod := &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: "operator-test", From 07779a0821d03492fe93313bad382a0054d405df Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Wed, 19 Sep 2018 15:02:06 -0700 Subject: [PATCH 21/35] test/e2e/memcached_test.go: update e2e tests --- test/e2e/memcached_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index a61f937977..2455b31eb8 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -328,7 +328,7 @@ func MemcachedClusterTest(t *testing.T) { if err != nil { t.Fatalf("could not get namespace: %v", err) } - cmdOut, err := exec.Command("operator-sdk", "test", "cluster", *f.ImageName, "-n", namespace, "-i").CombinedOutput() + cmdOut, err := exec.Command("operator-sdk", "test", "cluster", *f.ImageName, "-n", namespace, "-i", "Never").CombinedOutput() if err != nil { t.Fatalf("in-cluster test failed: %v\nCommand Output:\n%s", err, string(cmdOut)) } From 38ce471eef6b812ed76affef3c4036c55eeb5454 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Wed, 19 Sep 2018 15:50:12 -0700 Subject: [PATCH 22/35] commands/.../test/cluster.go: handle pending phase Test could hang forever if the pod remaining in the 'Pending' phase, which would occur if it cannot pull the image. This sets a configurable pending timeout with a default of 60 seconds --- commands/operator-sdk/cmd/test/cluster.go | 24 ++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index 1d31ffac82..20ee709416 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -20,6 +20,8 @@ import ( "os" "time" + "k8s.io/apimachinery/pkg/util/wait" + "github.com/spf13/cobra" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,6 +33,7 @@ var ( testNamespace string kubeconfigCluster string imagePullPolicy string + pendingTimeout int ) func NewTestClusterCmd() *cobra.Command { @@ -47,6 +50,7 @@ func NewTestClusterCmd() *cobra.Command { testCmd.Flags().StringVarP(&testNamespace, "namespace", "n", "default", "Namespace to run tests in") testCmd.Flags().StringVarP(&kubeconfigCluster, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path") testCmd.Flags().StringVarP(&imagePullPolicy, "imagePullPolicy", "i", "Always", "Set test pod image pull policy. Allowed values: Always, Never") + testCmd.Flags().IntVarP(&pendingTimeout, "pendingTimout", "p", 60, "Timeout for testing pod in pending state") return testCmd } @@ -63,7 +67,7 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { } else if imagePullPolicy == "Never" { pullPolicy = v1.PullNever } else { - return fmt.Errorf("Invalid imagePullPolicy '%v'", imagePullPolicy) + return fmt.Errorf("invalid imagePullPolicy '%v'", imagePullPolicy) } // cobra prints its help message on error; we silence that here because any errors below // are due to the test failing, not incorrect user input @@ -104,6 +108,24 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { fmt.Printf("Warning: failed to delete test pod") } }() + err = wait.Poll(time.Second*5, time.Second*time.Duration(pendingTimeout), func() (bool, error) { + testPod, err = kubeclient.CoreV1().Pods(testNamespace).Get(testPod.Name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("failed to get test pod: %v", err) + } + if testPod.Status.Phase == v1.PodPending { + return false, nil + } + return true, nil + }) + if err != nil { + testPod, err = kubeclient.CoreV1().Pods(testNamespace).Get(testPod.Name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("failed to get test pod: %v", err) + } + waitingState := testPod.Status.ContainerStatuses[0].State.Waiting + return fmt.Errorf("test pod stuck in 'Pending' phase for longer than %d seconds.\nMessage: %s\nReason: %s", pendingTimeout, waitingState.Message, waitingState.Reason) + } for { testPod, err = kubeclient.CoreV1().Pods(testNamespace).Get(testPod.Name, metav1.GetOptions{}) if err != nil { From e34fcaf251fdded10edc2131fdb0571585f89514 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Thu, 20 Sep 2018 09:56:02 -0700 Subject: [PATCH 23/35] commands/.../build.go: suggestions from LiliC --- commands/operator-sdk/cmd/build.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 1997a5fe48..3e5d5de52d 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -67,8 +67,7 @@ func verifyDeploymentImage(yamlFile []byte, imageName string) string { yamlMap := make(map[string]interface{}) err := yaml.Unmarshal(yamlSpec, &yamlMap) if err != nil { - fmt.Printf("WARNING: Could not unmarshal yaml namespaced spec") - return "" + return fmt.Sprintf("WARNING: Could not unmarshal yaml namespaced spec") } kind, ok := yamlMap["kind"].(string) if !ok { @@ -136,11 +135,9 @@ func buildFunc(cmd *cobra.Command, args []string) { } fmt.Fprintln(os.Stdout, string(o)) - genWarning := "" image := args[0] baseImageName := image if enableTests { - genWarning = renderTestManifest(image) baseImageName += "-intermediate" } dbcmd := exec.Command("docker", "build", ".", "-f", "tmp/build/Dockerfile", "-t", baseImageName) @@ -161,6 +158,8 @@ func buildFunc(cmd *cobra.Command, args []string) { cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to output build image %v: (%v)", image, string(o))) } fmt.Fprintln(os.Stdout, string(o)) + // create test-pod.yaml as well as check image name of deployments in namespaced manifest + genWarning := renderTestManifest(image) if genWarning != "" { fmt.Printf("%s\n", genWarning) } From db5bee2ba32e7251406245e779ef131a7f24db50 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Thu, 20 Sep 2018 10:08:20 -0700 Subject: [PATCH 24/35] commands/.../build.go: add comment to verifyDeploymentImage --- commands/operator-sdk/cmd/build.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 3e5d5de52d..9d82145f03 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -60,6 +60,14 @@ For example: return buildCmd } +/* + * verifyDeploymentImages checks image names of pod 0 in deployments found in the provided yaml file. + * This is done because e2e tests require a namespaced manifest file to configure a namespace with + * required resources. This function is intended to identify if a user used a different image name + * for their operator in the provided yaml, which would result in the testing of the wrong operator + * image. As it is possible for a namespaced yaml to have multiple deployments (such as the vault + * operator, which depends on the etcd-operator), this is just a warning, not a fatal error. + */ func verifyDeploymentImage(yamlFile []byte, imageName string) string { warningMessages := "" yamlSplit := bytes.Split(yamlFile, []byte("\n---\n")) From 276a62f6f27e3639bacd72d4148e73cf649c38c3 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Fri, 21 Sep 2018 14:45:17 -0700 Subject: [PATCH 25/35] commands/.../test/cluster.go: add flag for service account --- commands/operator-sdk/cmd/test/cluster.go | 7 +++++-- test/e2e/memcached_test.go | 13 ++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index 20ee709416..fea6aae88e 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -33,6 +33,7 @@ var ( testNamespace string kubeconfigCluster string imagePullPolicy string + serviceAccount string pendingTimeout int ) @@ -50,7 +51,8 @@ func NewTestClusterCmd() *cobra.Command { testCmd.Flags().StringVarP(&testNamespace, "namespace", "n", "default", "Namespace to run tests in") testCmd.Flags().StringVarP(&kubeconfigCluster, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path") testCmd.Flags().StringVarP(&imagePullPolicy, "imagePullPolicy", "i", "Always", "Set test pod image pull policy. Allowed values: Always, Never") - testCmd.Flags().IntVarP(&pendingTimeout, "pendingTimout", "p", 60, "Timeout for testing pod in pending state") + testCmd.Flags().StringVarP(&serviceAccount, "serviceAccount", "s", "default", "Service account to run tests on") + testCmd.Flags().IntVarP(&pendingTimeout, "pendingTimeout", "p", 60, "Timeout for testing pod in pending state") return testCmd } @@ -77,7 +79,8 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { Name: "operator-test", }, Spec: v1.PodSpec{ - RestartPolicy: v1.RestartPolicyNever, + ServiceAccountName: serviceAccount, + RestartPolicy: v1.RestartPolicyNever, Containers: []v1.Container{{ Name: "operator-test", Image: args[0], diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index a20a754329..9516644562 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -327,6 +327,17 @@ func MemcachedClusterTest(t *testing.T) { ctx := f.NewTestCtx(t) defer ctx.Cleanup(t) + // create sa + saYAML, err := ioutil.ReadFile("deploy/sa.yaml") + if err != nil { + t.Fatal(err) + } + err = ctx.CreateFromYAML(saYAML) + if err != nil { + t.Fatal(err) + } + t.Log("Created sa") + // create rbac rbacYAML, err := ioutil.ReadFile("deploy/rbac.yaml") err = ctx.CreateFromYAML(rbacYAML) @@ -339,7 +350,7 @@ func MemcachedClusterTest(t *testing.T) { if err != nil { t.Fatalf("could not get namespace: %v", err) } - cmdOut, err := exec.Command("operator-sdk", "test", "cluster", *f.ImageName, "-n", namespace, "-i", "Never").CombinedOutput() + cmdOut, err := exec.Command("operator-sdk", "test", "cluster", *f.ImageName, "-n", namespace, "-i", "Never", "-s", "memcached-operator").CombinedOutput() if err != nil { t.Fatalf("in-cluster test failed: %v\nCommand Output:\n%s", err, string(cmdOut)) } From fba6df783bd18434957db8d6098a61dbc6853d3a Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Fri, 21 Sep 2018 15:59:28 -0700 Subject: [PATCH 26/35] commands/.../build{,_test}.go: add unit test for verifyDeploymentImage --- commands/operator-sdk/cmd/build.go | 14 ++- commands/operator-sdk/cmd/build_test.go | 116 ++++++++++++++++++++++++ 2 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 commands/operator-sdk/cmd/build_test.go diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 9d82145f03..91375c37a3 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -16,6 +16,7 @@ package cmd import ( "bytes" + "errors" "fmt" "io/ioutil" "log" @@ -68,14 +69,14 @@ For example: * image. As it is possible for a namespaced yaml to have multiple deployments (such as the vault * operator, which depends on the etcd-operator), this is just a warning, not a fatal error. */ -func verifyDeploymentImage(yamlFile []byte, imageName string) string { +func verifyDeploymentImage(yamlFile []byte, imageName string) error { warningMessages := "" yamlSplit := bytes.Split(yamlFile, []byte("\n---\n")) for _, yamlSpec := range yamlSplit { yamlMap := make(map[string]interface{}) err := yaml.Unmarshal(yamlSpec, &yamlMap) if err != nil { - return fmt.Sprintf("WARNING: Could not unmarshal yaml namespaced spec") + return fmt.Errorf("WARNING: Could not unmarshal yaml namespaced spec") } kind, ok := yamlMap["kind"].(string) if !ok { @@ -110,10 +111,13 @@ func verifyDeploymentImage(yamlFile []byte, imageName string) string { } } } - return warningMessages + if warningMessages == "" { + return nil + } + return errors.New(warningMessages) } -func renderTestManifest(image string) string { +func renderTestManifest(image string) error { namespacedBytes, err := ioutil.ReadFile(namespacedManBuild) if err != nil { log.Fatalf("could not read rbac manifest: %v", err) @@ -168,7 +172,7 @@ func buildFunc(cmd *cobra.Command, args []string) { fmt.Fprintln(os.Stdout, string(o)) // create test-pod.yaml as well as check image name of deployments in namespaced manifest genWarning := renderTestManifest(image) - if genWarning != "" { + if genWarning != nil { fmt.Printf("%s\n", genWarning) } } diff --git a/commands/operator-sdk/cmd/build_test.go b/commands/operator-sdk/cmd/build_test.go new file mode 100644 index 0000000000..054336db87 --- /dev/null +++ b/commands/operator-sdk/cmd/build_test.go @@ -0,0 +1,116 @@ +// Copyright 2018 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 cmd + +import "testing" + +var memcachedNamespaceManExample = `apiVersion: v1 +kind: ServiceAccount +metadata: + name: memcached-operator + +--- + +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: memcached-operator +rules: +- apiGroups: + - cache.example.com + resources: + - "*" + verbs: + - "*" +- apiGroups: + - "" + resources: + - pods + - services + - endpoints + - persistentvolumeclaims + - events + - configmaps + - secrets + verbs: + - "*" +- apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - "*" + +--- + +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: memcached-operator +subjects: +- kind: ServiceAccount + name: memcached-operator +roleRef: + kind: Role + name: memcached-operator + apiGroup: rbac.authorization.k8s.io + +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: memcached-operator +spec: + replicas: 1 + selector: + matchLabels: + name: memcached-operator + template: + metadata: + labels: + name: memcached-operator + spec: + serviceAccountName: memcached-operator + containers: + - name: memcached-operator + image: quay.io/coreos/operator-sdk-dev:test-framework-operator + ports: + - containerPort: 60000 + name: metrics + command: + - memcached-operator + imagePullPolicy: Always + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: OPERATOR_NAME + value: "memcached-operator" + +` + +func TestVerifyDeploymentImage(t *testing.T) { + if err := verifyDeploymentImage([]byte(memcachedNamespaceManExample), "quay.io/coreos/operator-sdk-dev:test-framework-operator"); err != nil { + t.Fatalf("verifyDeploymentImage incorrectly reported an error: %v", err) + } + if err := verifyDeploymentImage([]byte(memcachedNamespaceManExample), "different-image-name"); err == nil { + t.Fatal("verifyDeploymentImage did not report an error on an incorrect manifest") + } +} From 901b22e3b794bb8a9029e85514de614d3452df91 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Fri, 21 Sep 2018 16:00:36 -0700 Subject: [PATCH 27/35] .travis.yml: enable go testing of ./commands/... --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 6f72758ff3..9e0f9fd021 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,7 @@ install: script: - make install +- go test ./commands/... - go test ./pkg/... - go test ./test/e2e/... - cd test/test-framework From 50eb65c560738d12dfbd50b7e7d6c3f1a2e6cc86 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Fri, 21 Sep 2018 16:18:01 -0700 Subject: [PATCH 28/35] commands/.../build.go: make unmarshalling error fatal --- commands/operator-sdk/cmd/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 91375c37a3..255b60ca28 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -76,7 +76,7 @@ func verifyDeploymentImage(yamlFile []byte, imageName string) error { yamlMap := make(map[string]interface{}) err := yaml.Unmarshal(yamlSpec, &yamlMap) if err != nil { - return fmt.Errorf("WARNING: Could not unmarshal yaml namespaced spec") + log.Fatal("Could not unmarshal yaml namespaced spec") } kind, ok := yamlMap["kind"].(string) if !ok { From 5102bb319a652932d8d04e869815b68e41be36cc Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Mon, 24 Sep 2018 11:33:40 -0700 Subject: [PATCH 29/35] commands,pkg: improvements suggested by haseeb --- commands/operator-sdk/cmd/build.go | 14 ++++++-------- commands/operator-sdk/cmd/test/cluster.go | 5 +++-- pkg/generator/generator.go | 10 ++++++++-- pkg/generator/templates.go | 5 +---- pkg/test/framework.go | 3 +-- pkg/test/main_entry.go | 1 + pkg/test/resource_creator.go | 2 +- 7 files changed, 21 insertions(+), 19 deletions(-) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index 255b60ca28..f5d5613968 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -117,15 +117,16 @@ func verifyDeploymentImage(yamlFile []byte, imageName string) error { return errors.New(warningMessages) } -func renderTestManifest(image string) error { +func renderTestManifest(image string) { namespacedBytes, err := ioutil.ReadFile(namespacedManBuild) if err != nil { - log.Fatalf("could not read rbac manifest: %v", err) + log.Fatalf("could not read namespaced manifest: %v", err) } if err = generator.RenderTestYaml(cmdutil.GetConfig(), image); err != nil { - cmdError.ExitWithError(cmdError.ExitError, fmt.Errorf("failed to generate deploy/test-pod.yaml: (%v)", err)) + log.Fatalf("failed to generate deploy/test-pod.yaml: (%v)", err) } - return verifyDeploymentImage(namespacedBytes, image) + // the error from verifyDeploymentImage is just a warning, not fatal error + fmt.Printf("%v", verifyDeploymentImage(namespacedBytes, image)) } const ( @@ -171,9 +172,6 @@ func buildFunc(cmd *cobra.Command, args []string) { } fmt.Fprintln(os.Stdout, string(o)) // create test-pod.yaml as well as check image name of deployments in namespaced manifest - genWarning := renderTestManifest(image) - if genWarning != nil { - fmt.Printf("%s\n", genWarning) - } + renderTestManifest(image) } } diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index fea6aae88e..f1ab13533c 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -20,11 +20,12 @@ import ( "os" "time" - "k8s.io/apimachinery/pkg/util/wait" + "github.com/operator-framework/operator-sdk/pkg/test" "github.com/spf13/cobra" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) @@ -87,7 +88,7 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { ImagePullPolicy: pullPolicy, Command: []string{"/go-test.sh"}, Env: []v1.EnvVar{{ - Name: "TEST_NAMESPACE", + Name: test.TestNamespaceEnv, ValueFrom: &v1.EnvVarSource{FieldRef: &v1.ObjectFieldSelector{FieldPath: "metadata.namespace"}}, }}, }}, diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index 92395b99ff..da0f887d09 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -24,6 +24,8 @@ import ( "strings" "text/template" + "github.com/operator-framework/operator-sdk/pkg/test" + k8sutil "github.com/operator-framework/operator-sdk/pkg/util/k8sutil" ) @@ -261,8 +263,9 @@ func renderDeployFiles(deployDir, projectName, apiVersion, kind string) error { func RenderTestYaml(c *Config, image string) error { opTd := tmplData{ - ProjectName: c.ProjectName, - Image: image, + ProjectName: c.ProjectName, + Image: image, + TestNamespaceEnv: test.TestNamespaceEnv, } return renderWriteFile(filepath.Join(deployDir, "test-pod.yaml"), testYamlName, testYamlTmpl, opTd) } @@ -490,6 +493,9 @@ type tmplData struct { CRDVersion string CSVName string CatalogVersion string + + // for test framework + TestNamespaceEnv string } // Creates all the necesary directories for the generated files diff --git a/pkg/generator/templates.go b/pkg/generator/templates.go index dbcbc710ba..7f11e0e218 100644 --- a/pkg/generator/templates.go +++ b/pkg/generator/templates.go @@ -435,11 +435,8 @@ spec: image: {{.Image}} imagePullPolicy: Always command: ["/go-test.sh"] - resources: - requests: - cpu: 1 env: - - name: TEST_NAMESPACE + - name: {{.TestNamespaceEnv}} valueFrom: fieldRef: fieldPath: metadata.namespace diff --git a/pkg/test/framework.go b/pkg/test/framework.go index fcc4a9e51b..97b1040951 100644 --- a/pkg/test/framework.go +++ b/pkg/test/framework.go @@ -62,7 +62,6 @@ func setup(kubeconfigPath, namespacedManPath *string) error { inCluster := false if *kubeconfigPath == "incluster" { // Work around https://github.com/kubernetes/kubernetes/issues/40973 - // See https://github.com/coreos/etcd-operator/issues/731#issuecomment-283804819 if len(os.Getenv("KUBERNETES_SERVICE_HOST")) == 0 { addrs, err := net.LookupHost("kubernetes.default.svc") if err != nil { @@ -139,7 +138,7 @@ func AddToFrameworkScheme(addToScheme addToSchemeFunc, obj runtime.Object) error Global.DynamicClient, err = dynclient.New(Global.KubeConfig, dynclient.Options{Scheme: Global.Scheme, Mapper: Global.RestMapper}) err = wait.PollImmediate(time.Second, time.Second*10, func() (done bool, err error) { if Global.InCluster { - err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: os.Getenv("TEST_NAMESPACE")}, obj) + err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: os.Getenv(TestNamespaceEnv)}, obj) } else { err = Global.DynamicClient.List(goctx.TODO(), &dynclient.ListOptions{Namespace: "default"}, obj) } diff --git a/pkg/test/main_entry.go b/pkg/test/main_entry.go index 70a3ec23dc..608df58eb3 100644 --- a/pkg/test/main_entry.go +++ b/pkg/test/main_entry.go @@ -27,6 +27,7 @@ const ( KubeConfigFlag = "kubeconfig" NamespacedManPathFlag = "namespacedMan" GlobalManPathFlag = "globalMan" + TestNamespaceEnv = "TEST_NAMESPACE" ) func MainEntry(m *testing.M) { diff --git a/pkg/test/resource_creator.go b/pkg/test/resource_creator.go index 2f0a8ebcab..940f603174 100644 --- a/pkg/test/resource_creator.go +++ b/pkg/test/resource_creator.go @@ -32,7 +32,7 @@ func (ctx *TestCtx) GetNamespace() (string, error) { return ctx.Namespace, nil } if Global.InCluster { - ctx.Namespace = os.Getenv("TEST_NAMESPACE") + ctx.Namespace = os.Getenv(TestNamespaceEnv) return ctx.Namespace, nil } // create namespace From d722d6ed129cb698b537083b757746289833ccca Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Mon, 24 Sep 2018 12:29:13 -0700 Subject: [PATCH 30/35] pkg/generator: remove docker_build.sh generation --- pkg/generator/generator.go | 14 -------------- pkg/generator/generator_test.go | 11 ----------- pkg/generator/templates.go | 13 ------------- 3 files changed, 38 deletions(-) diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index da0f887d09..c23b4e54e6 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -54,7 +54,6 @@ const ( register = "register.go" types = "types.go" build = "build.sh" - dockerBuild = "docker_build.sh" dockerfile = "Dockerfile" testingDockerfile = "Dockerfile" goTest = "go-test.sh" @@ -356,14 +355,6 @@ func renderBuildFiles(buildDir, repoPath, projectName string) error { return err } - buf = &bytes.Buffer{} - if err := renderDockerBuildFile(buf); err != nil { - return err - } - if err := writeFileAndPrint(filepath.Join(buildDir, dockerBuild), buf.Bytes(), defaultExecFileMode); err != nil { - return err - } - dTd := tmplData{ ProjectName: projectName, } @@ -383,11 +374,6 @@ func renderBuildFiles(buildDir, repoPath, projectName string) error { return writeFileAndPrint(filepath.Join(buildDir, goTest), buf.Bytes(), os.FileMode(int(0755))) } -func renderDockerBuildFile(w io.Writer) error { - _, err := w.Write([]byte(dockerBuildTmpl)) - return err -} - func renderCodegenFiles(codegenDir, repoPath, apiDirName, version, projectName string) error { bTd := tmplData{ ProjectName: projectName, diff --git a/pkg/generator/generator_test.go b/pkg/generator/generator_test.go index 3eae5f09ad..bcb23d893c 100644 --- a/pkg/generator/generator_test.go +++ b/pkg/generator/generator_test.go @@ -544,17 +544,6 @@ func TestGenBuild(t *testing.T) { t.Errorf("\nTest failed. Below is the diff of the expected vs actual results.\nRed text is missing and green text is extra.\n\n" + dmp.DiffPrettyText(diffs)) } - buf = &bytes.Buffer{} - if err := renderDockerBuildFile(buf); err != nil { - t.Error(err) - return - } - if dockerBuildTmpl != buf.String() { - dmp := diffmatchpatch.New() - diffs := dmp.DiffMain(dockerBuildTmpl, buf.String(), false) - t.Errorf("\nTest failed. Below is the diff of the expected vs actual results.\nRed text is missing and green text is extra.\n\n" + dmp.DiffPrettyText(diffs)) - } - buf = &bytes.Buffer{} dTd := tmplData{ ProjectName: appProjectName, diff --git a/pkg/generator/templates.go b/pkg/generator/templates.go index 7f11e0e218..1782459b80 100644 --- a/pkg/generator/templates.go +++ b/pkg/generator/templates.go @@ -575,19 +575,6 @@ if $ENABLE_TESTS ; then fi ` -const dockerBuildTmpl = `#!/usr/bin/env bash - -if ! which docker > /dev/null; then - echo "docker needs to be installed" - exit 1 -fi - -: ${IMAGE:?"Need to set IMAGE, e.g. gcr.io//-operator"} - -echo "building container ${IMAGE}..." -docker build -t "${IMAGE}" -f tmp/build/Dockerfile . --build-arg NAMESPACEDMAN=$NAMESPACEDMAN -` - const goTestScript = `#!/bin/sh memcached-operator-test -test.parallel=1 -test.failfast -root=/ -kubeconfig=incluster -namespacedMan=namespaced.yaml -test.v From 81be3d226a43b4fcbab581cd159bc61b20c58499 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Mon, 24 Sep 2018 14:49:02 -0700 Subject: [PATCH 31/35] pkg/generator/generator.go: change defaultExecFileMode to 0755 --- pkg/generator/generator.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index c23b4e54e6..e8893cebd2 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -32,7 +32,7 @@ import ( const ( defaultDirFileMode = 0750 defaultFileMode = 0644 - defaultExecFileMode = 0744 + defaultExecFileMode = 0755 // dirs cmdDir = "cmd" deployDir = "deploy" @@ -371,7 +371,7 @@ func renderBuildFiles(buildDir, repoPath, projectName string) error { if err := renderFile(buf, filepath.Join(buildDir, goTest), goTestScript, tmplData{}); err != nil { return err } - return writeFileAndPrint(filepath.Join(buildDir, goTest), buf.Bytes(), os.FileMode(int(0755))) + return writeFileAndPrint(filepath.Join(buildDir, goTest), buf.Bytes(), defaultExecFileMode) } func renderCodegenFiles(codegenDir, repoPath, apiDirName, version, projectName string) error { From ad18ed6360c36041e9b2ca7fd8bcabc5df5630fe Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Mon, 24 Sep 2018 14:53:33 -0700 Subject: [PATCH 32/35] commands/.../test/cluster: support lowercase pull policy --- commands/operator-sdk/cmd/test/cluster.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index f1ab13533c..7e74d9ce38 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -18,6 +18,7 @@ import ( "bytes" "fmt" "os" + "strings" "time" "github.com/operator-framework/operator-sdk/pkg/test" @@ -65,9 +66,9 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { return fmt.Errorf("operator-sdk test cluster requires exactly 1 argument") } var pullPolicy v1.PullPolicy - if imagePullPolicy == "Always" { + if strings.ToLower(imagePullPolicy) == "always" { pullPolicy = v1.PullAlways - } else if imagePullPolicy == "Never" { + } else if strings.ToLower(imagePullPolicy) == "never" { pullPolicy = v1.PullNever } else { return fmt.Errorf("invalid imagePullPolicy '%v'", imagePullPolicy) From 49ab763f16e4163572e20c804f7f7d00f9064ed7 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Mon, 24 Sep 2018 15:03:31 -0700 Subject: [PATCH 33/35] commands/.../test: use structs for test flags/vars --- commands/operator-sdk/cmd/test/cluster.go | 52 ++++++++++++----------- commands/operator-sdk/cmd/test/local.go | 38 +++++++++-------- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/commands/operator-sdk/cmd/test/cluster.go b/commands/operator-sdk/cmd/test/cluster.go index 7e74d9ce38..7064bbc5c9 100644 --- a/commands/operator-sdk/cmd/test/cluster.go +++ b/commands/operator-sdk/cmd/test/cluster.go @@ -31,13 +31,15 @@ import ( "k8s.io/client-go/tools/clientcmd" ) -var ( - testNamespace string - kubeconfigCluster string - imagePullPolicy string - serviceAccount string - pendingTimeout int -) +type testClusterConfig struct { + namespace string + kubeconfig string + imagePullPolicy string + serviceAccount string + pendingTimeout int +} + +var tcConfig testClusterConfig func NewTestClusterCmd() *cobra.Command { testCmd := &cobra.Command{ @@ -50,11 +52,11 @@ func NewTestClusterCmd() *cobra.Command { if ok { defaultKubeConfig = homedir + "/.kube/config" } - testCmd.Flags().StringVarP(&testNamespace, "namespace", "n", "default", "Namespace to run tests in") - testCmd.Flags().StringVarP(&kubeconfigCluster, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path") - testCmd.Flags().StringVarP(&imagePullPolicy, "imagePullPolicy", "i", "Always", "Set test pod image pull policy. Allowed values: Always, Never") - testCmd.Flags().StringVarP(&serviceAccount, "serviceAccount", "s", "default", "Service account to run tests on") - testCmd.Flags().IntVarP(&pendingTimeout, "pendingTimeout", "p", 60, "Timeout for testing pod in pending state") + testCmd.Flags().StringVarP(&tcConfig.namespace, "namespace", "n", "default", "Namespace to run tests in") + testCmd.Flags().StringVarP(&tcConfig.kubeconfig, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path") + testCmd.Flags().StringVarP(&tcConfig.imagePullPolicy, "imagePullPolicy", "i", "Always", "Set test pod image pull policy. Allowed values: Always, Never") + testCmd.Flags().StringVarP(&tcConfig.serviceAccount, "serviceAccount", "s", "default", "Service account to run tests on") + testCmd.Flags().IntVarP(&tcConfig.pendingTimeout, "pendingTimeout", "p", 60, "Timeout for testing pod in pending state") return testCmd } @@ -66,12 +68,12 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { return fmt.Errorf("operator-sdk test cluster requires exactly 1 argument") } var pullPolicy v1.PullPolicy - if strings.ToLower(imagePullPolicy) == "always" { + if strings.ToLower(tcConfig.imagePullPolicy) == "always" { pullPolicy = v1.PullAlways - } else if strings.ToLower(imagePullPolicy) == "never" { + } else if strings.ToLower(tcConfig.imagePullPolicy) == "never" { pullPolicy = v1.PullNever } else { - return fmt.Errorf("invalid imagePullPolicy '%v'", imagePullPolicy) + return fmt.Errorf("invalid imagePullPolicy '%v'", tcConfig.imagePullPolicy) } // cobra prints its help message on error; we silence that here because any errors below // are due to the test failing, not incorrect user input @@ -81,7 +83,7 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { Name: "operator-test", }, Spec: v1.PodSpec{ - ServiceAccountName: serviceAccount, + ServiceAccountName: tcConfig.serviceAccount, RestartPolicy: v1.RestartPolicyNever, Containers: []v1.Container{{ Name: "operator-test", @@ -95,7 +97,7 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { }}, }, } - kubeconfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigCluster) + kubeconfig, err := clientcmd.BuildConfigFromFlags("", tcConfig.kubeconfig) if err != nil { return fmt.Errorf("failed to get kubeconfig: %v", err) } @@ -103,18 +105,18 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { if err != nil { return fmt.Errorf("failed to create kubeclient: %v", err) } - testPod, err = kubeclient.CoreV1().Pods(testNamespace).Create(testPod) + testPod, err = kubeclient.CoreV1().Pods(tcConfig.namespace).Create(testPod) if err != nil { return fmt.Errorf("failed to create test pod: %v", err) } defer func() { - err = kubeclient.CoreV1().Pods(testNamespace).Delete(testPod.Name, &metav1.DeleteOptions{}) + err = kubeclient.CoreV1().Pods(tcConfig.namespace).Delete(testPod.Name, &metav1.DeleteOptions{}) if err != nil { fmt.Printf("Warning: failed to delete test pod") } }() - err = wait.Poll(time.Second*5, time.Second*time.Duration(pendingTimeout), func() (bool, error) { - testPod, err = kubeclient.CoreV1().Pods(testNamespace).Get(testPod.Name, metav1.GetOptions{}) + err = wait.Poll(time.Second*5, time.Second*time.Duration(tcConfig.pendingTimeout), func() (bool, error) { + testPod, err = kubeclient.CoreV1().Pods(tcConfig.namespace).Get(testPod.Name, metav1.GetOptions{}) if err != nil { return false, fmt.Errorf("failed to get test pod: %v", err) } @@ -124,15 +126,15 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { return true, nil }) if err != nil { - testPod, err = kubeclient.CoreV1().Pods(testNamespace).Get(testPod.Name, metav1.GetOptions{}) + testPod, err = kubeclient.CoreV1().Pods(tcConfig.namespace).Get(testPod.Name, metav1.GetOptions{}) if err != nil { return fmt.Errorf("failed to get test pod: %v", err) } waitingState := testPod.Status.ContainerStatuses[0].State.Waiting - return fmt.Errorf("test pod stuck in 'Pending' phase for longer than %d seconds.\nMessage: %s\nReason: %s", pendingTimeout, waitingState.Message, waitingState.Reason) + return fmt.Errorf("test pod stuck in 'Pending' phase for longer than %d seconds.\nMessage: %s\nReason: %s", tcConfig.pendingTimeout, waitingState.Message, waitingState.Reason) } for { - testPod, err = kubeclient.CoreV1().Pods(testNamespace).Get(testPod.Name, metav1.GetOptions{}) + testPod, err = kubeclient.CoreV1().Pods(tcConfig.namespace).Get(testPod.Name, metav1.GetOptions{}) if err != nil { return fmt.Errorf("failed to get test pod: %v", err) } @@ -143,7 +145,7 @@ func testClusterFunc(cmd *cobra.Command, args []string) error { fmt.Printf("Test Successfully Completed\n") return nil } else if testPod.Status.Phase == v1.PodFailed { - req := kubeclient.CoreV1().Pods(testNamespace).GetLogs(testPod.Name, &v1.PodLogOptions{}) + req := kubeclient.CoreV1().Pods(tcConfig.namespace).GetLogs(testPod.Name, &v1.PodLogOptions{}) readCloser, err := req.Stream() if err != nil { return fmt.Errorf("test failed and failed to get error logs") diff --git a/commands/operator-sdk/cmd/test/local.go b/commands/operator-sdk/cmd/test/local.go index e30476a29b..0e203487d8 100644 --- a/commands/operator-sdk/cmd/test/local.go +++ b/commands/operator-sdk/cmd/test/local.go @@ -28,12 +28,14 @@ import ( "github.com/spf13/cobra" ) -var ( - kubeconfig string - globalManifestPath string - namespacedManifestPath string - goTestFlags string -) +type testLocalConfig struct { + kubeconfig string + globalManPath string + namespacedManPath string + goTestFlags string +} + +var tlConfig testLocalConfig func NewTestLocalCmd() *cobra.Command { testCmd := &cobra.Command{ @@ -46,10 +48,10 @@ func NewTestLocalCmd() *cobra.Command { if ok { defaultKubeConfig = homedir + "/.kube/config" } - testCmd.Flags().StringVarP(&kubeconfig, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path") - testCmd.Flags().StringVarP(&globalManifestPath, "global-init", "g", "deploy/crd.yaml", "Path to manifest for Global resources (e.g. CRD manifest)") - testCmd.Flags().StringVarP(&namespacedManifestPath, "namespaced-init", "n", "", "Path to manifest for per-test, namespaced resources (e.g. RBAC and Operator manifest)") - testCmd.Flags().StringVarP(&goTestFlags, "go-test-flags", "f", "", "Additional flags to pass to go test") + testCmd.Flags().StringVarP(&tlConfig.kubeconfig, "kubeconfig", "k", defaultKubeConfig, "Kubeconfig path") + testCmd.Flags().StringVarP(&tlConfig.globalManPath, "global-init", "g", "deploy/crd.yaml", "Path to manifest for Global resources (e.g. CRD manifest)") + testCmd.Flags().StringVarP(&tlConfig.namespacedManPath, "namespaced-init", "n", "", "Path to manifest for per-test, namespaced resources (e.g. RBAC and Operator manifest)") + testCmd.Flags().StringVarP(&tlConfig.goTestFlags, "go-test-flags", "f", "", "Additional flags to pass to go test") return testCmd } @@ -59,9 +61,9 @@ func testLocalFunc(cmd *cobra.Command, args []string) { cmdError.ExitWithError(cmdError.ExitBadArgs, fmt.Errorf("operator-sdk test local requires exactly 1 argument")) } // if no namespaced manifest path is given, combine deploy/sa.yaml, deploy/rbac.yaml and deploy/operator.yaml - if namespacedManifestPath == "" { + if tlConfig.namespacedManPath == "" { os.Mkdir("deploy/test", os.FileMode(int(0775))) - namespacedManifestPath = "deploy/test/namespace-manifests.yaml" + tlConfig.namespacedManPath = "deploy/test/namespace-manifests.yaml" sa, err := ioutil.ReadFile("deploy/sa.yaml") if err != nil { log.Fatalf("could not find sa manifest: %v", err) @@ -78,23 +80,23 @@ func testLocalFunc(cmd *cobra.Command, args []string) { combined = append(combined, rbac...) combined = append(combined, []byte("\n---\n")...) combined = append(combined, operator...) - err = ioutil.WriteFile(namespacedManifestPath, combined, os.FileMode(int(0664))) + err = ioutil.WriteFile(tlConfig.namespacedManPath, combined, os.FileMode(int(0664))) if err != nil { log.Fatalf("could not create temporary namespaced manifest file: %v", err) } defer func() { - err := os.Remove(namespacedManifestPath) + err := os.Remove(tlConfig.namespacedManPath) if err != nil { log.Fatalf("could not delete temporary namespace manifest file") } }() } testArgs := []string{"test", args[0] + "/..."} - testArgs = append(testArgs, "-"+test.KubeConfigFlag, kubeconfig) - testArgs = append(testArgs, "-"+test.NamespacedManPathFlag, namespacedManifestPath) - testArgs = append(testArgs, "-"+test.GlobalManPathFlag, globalManifestPath) + testArgs = append(testArgs, "-"+test.KubeConfigFlag, tlConfig.kubeconfig) + testArgs = append(testArgs, "-"+test.NamespacedManPathFlag, tlConfig.namespacedManPath) + testArgs = append(testArgs, "-"+test.GlobalManPathFlag, tlConfig.globalManPath) testArgs = append(testArgs, "-"+test.ProjRootFlag, mustGetwd()) - testArgs = append(testArgs, strings.Split(goTestFlags, " ")...) + testArgs = append(testArgs, strings.Split(tlConfig.goTestFlags, " ")...) dc := exec.Command("go", testArgs...) dc.Dir = mustGetwd() dc.Stdout = os.Stdout From 7947e47f4c21cca9cde4db381ae20f774f6f3502 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Mon, 24 Sep 2018 16:22:25 -0700 Subject: [PATCH 34/35] commands/.../build: add nil error check --- commands/operator-sdk/cmd/build.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/commands/operator-sdk/cmd/build.go b/commands/operator-sdk/cmd/build.go index f5d5613968..1110106ff2 100644 --- a/commands/operator-sdk/cmd/build.go +++ b/commands/operator-sdk/cmd/build.go @@ -125,8 +125,11 @@ func renderTestManifest(image string) { if err = generator.RenderTestYaml(cmdutil.GetConfig(), image); err != nil { log.Fatalf("failed to generate deploy/test-pod.yaml: (%v)", err) } + err = verifyDeploymentImage(namespacedBytes, image) // the error from verifyDeploymentImage is just a warning, not fatal error - fmt.Printf("%v", verifyDeploymentImage(namespacedBytes, image)) + if err != nil { + fmt.Printf("%v\n", err) + } } const ( From 3e1dfcb6e0048c5fb66e24a37f2c0f9c19c811f3 Mon Sep 17 00:00:00 2001 From: Alexander Pavel Date: Tue, 25 Sep 2018 09:37:33 -0700 Subject: [PATCH 35/35] test/e2e/memcached_test.go: check error from readfile --- test/e2e/memcached_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/e2e/memcached_test.go b/test/e2e/memcached_test.go index 79d6d07387..beaa95db3d 100644 --- a/test/e2e/memcached_test.go +++ b/test/e2e/memcached_test.go @@ -267,6 +267,9 @@ func MemcachedCluster(t *testing.T) { ctx := f.NewTestCtx(t) defer ctx.Cleanup(t) operatorYAML, err := ioutil.ReadFile("deploy/operator.yaml") + if err != nil { + t.Fatalf("could not read deploy/operator.yaml: %v", err) + } local := *f.ImageName == "" if local { *f.ImageName = "quay.io/example/memcached-operator:v0.0.1"