Skip to content

Commit 4d7b853

Browse files
authored
hack/tests,test/e2e: separate testing and scaffolding of E2E te… (#1586)
* hack/tests,test/e2e: separate testing and scaffolding of E2E tests
1 parent 998ffb1 commit 4d7b853

File tree

12 files changed

+795
-1494
lines changed

12 files changed

+795
-1494
lines changed

Gopkg.lock

Lines changed: 0 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hack/tests/e2e-go.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
#!/usr/bin/env bash
22
set -ex
33

4+
source hack/tests/scaffolding/e2e-go-scaffold.sh
5+
6+
pushd $BASEPROJECTDIR/memcached-operator
7+
operator-sdk build $IMAGE_NAME
8+
9+
operator-sdk test local ./test/e2e
10+
popd
11+
412
go test ./test/e2e/... -root=. -globalMan=testdata/empty.yaml $1
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env bash
2+
3+
set -ex
4+
5+
source hack/lib/test_lib.sh
6+
7+
ROOTDIR="$(pwd)"
8+
# TODO: remove once PR 1566 is merged
9+
trap_add 'rm -f $ROOTDIR/go.mod' EXIT
10+
BASEPROJECTDIR="$(mktemp -d)"
11+
IMAGE_NAME="quay.io/example/memcached-operator:v0.0.1"
12+
13+
go build -o $BASEPROJECTDIR/scaffold-memcached $ROOTDIR/hack/tests/scaffolding/scaffold-memcached.go
14+
15+
pushd "$BASEPROJECTDIR"
16+
./scaffold-memcached --local-repo $ROOTDIR --image-name=$IMAGE_NAME --local-image
17+
popd
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
// Copyright 2019 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"bytes"
19+
"flag"
20+
"fmt"
21+
"io/ioutil"
22+
"os"
23+
"os/exec"
24+
"path/filepath"
25+
"strings"
26+
27+
"github.com/operator-framework/operator-sdk/internal/util/fileutil"
28+
"github.com/pkg/errors"
29+
30+
log "github.com/sirupsen/logrus"
31+
)
32+
33+
// TODO: Migrate most/all of the cli commands to the bash script instead of keeping them here
34+
35+
const (
36+
sdkRepo = "github.com/operator-framework/operator-sdk"
37+
operatorName = "memcached-operator"
38+
testRepo = "github.com/example-inc/" + operatorName
39+
)
40+
41+
func main() {
42+
localRepo := flag.String("local-repo", "", "Path to local SDK repository being tested. Only use when running e2e tests locally")
43+
imageName := flag.String("image-name", "", "Name of image being used for tests")
44+
noPull := flag.Bool("local-image", false, "Disable pulling images as image is local")
45+
flag.Parse()
46+
// get global framework variables
47+
sdkTestE2EDir, err := os.Getwd()
48+
if err != nil {
49+
log.Fatal(err)
50+
}
51+
defer func() {
52+
if err := os.Chdir(sdkTestE2EDir); err != nil {
53+
log.Errorf("Failed to change back to original working directory: (%v)", err)
54+
}
55+
}()
56+
localSDKPath := *localRepo
57+
if localSDKPath == "" {
58+
localSDKPath = sdkTestE2EDir
59+
}
60+
// For go commands in operator projects.
61+
if err = os.Setenv("GO111MODULE", "on"); err != nil {
62+
log.Fatal(err)
63+
}
64+
65+
log.Print("Creating new operator project")
66+
cmdOut, err := exec.Command("operator-sdk",
67+
"new",
68+
operatorName,
69+
"--repo", testRepo,
70+
"--skip-validation").CombinedOutput()
71+
if err != nil {
72+
log.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut))
73+
}
74+
75+
if err := os.Chdir(operatorName); err != nil {
76+
log.Fatalf("Failed to change to %s directory: (%v)", operatorName, err)
77+
}
78+
79+
replace := getGoModReplace(localSDKPath)
80+
if replace.repo != sdkRepo {
81+
if replace.isLocal {
82+
// A hacky way to get local module substitution to work is to write a
83+
// stub go.mod into the local SDK repo referred to in
84+
// memcached-operator's go.mod, which allows go to recognize
85+
// the local SDK repo as a module.
86+
sdkModPath := filepath.Join(filepath.FromSlash(replace.repo), "go.mod")
87+
if _, err = os.Stat(sdkModPath); err != nil && os.IsNotExist(err) {
88+
err = ioutil.WriteFile(sdkModPath, []byte("module "+sdkRepo), fileutil.DefaultFileMode)
89+
if err != nil {
90+
log.Fatalf("Failed to write main repo go.mod file: %v", err)
91+
}
92+
}
93+
}
94+
modBytes, err := insertGoModReplace(sdkRepo, replace.repo, replace.ref)
95+
if err != nil {
96+
log.Fatalf("Failed to insert go.mod replace: %v", err)
97+
}
98+
log.Printf("go.mod: %v", string(modBytes))
99+
}
100+
101+
cmdOut, err = exec.Command("go", "build", "./...").CombinedOutput()
102+
if err != nil {
103+
log.Fatalf("Command \"go build ./...\" failed after modifying go.mod: %v\nCommand Output:\n%v", err, string(cmdOut))
104+
}
105+
106+
// Set replicas to 2 to test leader election. In production, this should
107+
// almost always be set to 1, because there isn't generally value in having
108+
// a hot spare operator process.
109+
opYaml, err := ioutil.ReadFile("deploy/operator.yaml")
110+
if err != nil {
111+
log.Fatalf("Could not read deploy/operator.yaml: %v", err)
112+
}
113+
newOpYaml := bytes.Replace(opYaml, []byte("replicas: 1"), []byte("replicas: 2"), 1)
114+
err = ioutil.WriteFile("deploy/operator.yaml", newOpYaml, 0644)
115+
if err != nil {
116+
log.Fatalf("Could not write deploy/operator.yaml: %v", err)
117+
}
118+
119+
cmd := exec.Command("operator-sdk",
120+
"add",
121+
"api",
122+
"--api-version=cache.example.com/v1alpha1",
123+
"--kind=Memcached")
124+
cmd.Env = os.Environ()
125+
cmdOut, err = cmd.CombinedOutput()
126+
if err != nil {
127+
log.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut))
128+
}
129+
cmdOut, err = exec.Command("operator-sdk",
130+
"add",
131+
"controller",
132+
"--api-version=cache.example.com/v1alpha1",
133+
"--kind=Memcached").CombinedOutput()
134+
if err != nil {
135+
log.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut))
136+
}
137+
138+
tmplFiles := map[string]string{
139+
filepath.Join(localSDKPath, "example/memcached-operator/memcached_controller.go.tmpl"): "pkg/controller/memcached/memcached_controller.go",
140+
filepath.Join(localSDKPath, "test/e2e/_incluster-test-code/main_test.go"): "test/e2e/main_test.go",
141+
filepath.Join(localSDKPath, "test/e2e/_incluster-test-code/memcached_test.go"): "test/e2e/memcached_test.go",
142+
}
143+
for src, dst := range tmplFiles {
144+
if err := os.MkdirAll(filepath.Dir(dst), fileutil.DefaultDirFileMode); err != nil {
145+
log.Fatalf("Could not create template destination directory: %s", err)
146+
}
147+
cmdOut, err = exec.Command("cp", src, dst).CombinedOutput()
148+
if err != nil {
149+
log.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut))
150+
}
151+
}
152+
153+
memcachedTypesFile, err := ioutil.ReadFile("pkg/apis/cache/v1alpha1/memcached_types.go")
154+
if err != nil {
155+
log.Fatalf("Could not read pkg/apis/cache/v1alpha1/memcached_types.go: %v", err)
156+
}
157+
memcachedTypesFileLines := bytes.Split(memcachedTypesFile, []byte("\n"))
158+
for lineNum, line := range memcachedTypesFileLines {
159+
if strings.Contains(string(line), "type MemcachedSpec struct {") {
160+
memcachedTypesFileLinesIntermediate := append(memcachedTypesFileLines[:lineNum+1], []byte("\tSize int32 `json:\"size\"`"))
161+
memcachedTypesFileLines = append(memcachedTypesFileLinesIntermediate, memcachedTypesFileLines[lineNum+3:]...)
162+
break
163+
}
164+
}
165+
for lineNum, line := range memcachedTypesFileLines {
166+
if strings.Contains(string(line), "type MemcachedStatus struct {") {
167+
memcachedTypesFileLinesIntermediate := append(memcachedTypesFileLines[:lineNum+1], []byte("\tNodes []string `json:\"nodes\"`"))
168+
memcachedTypesFileLines = append(memcachedTypesFileLinesIntermediate, memcachedTypesFileLines[lineNum+3:]...)
169+
break
170+
}
171+
}
172+
if err := os.Remove("pkg/apis/cache/v1alpha1/memcached_types.go"); err != nil {
173+
log.Fatalf("Failed to remove old memcached_type.go file: (%v)", err)
174+
}
175+
err = ioutil.WriteFile("pkg/apis/cache/v1alpha1/memcached_types.go", bytes.Join(memcachedTypesFileLines, []byte("\n")), fileutil.DefaultFileMode)
176+
if err != nil {
177+
log.Fatalf("Could not write to pkg/apis/cache/v1alpha1/memcached_types.go: %v", err)
178+
}
179+
180+
log.Print("Generating k8s")
181+
cmdOut, err = exec.Command("operator-sdk", "generate", "k8s").CombinedOutput()
182+
if err != nil {
183+
log.Fatalf("Error: %v\nCommand Output: %s\n", err, string(cmdOut))
184+
}
185+
186+
log.Print("Pulling new dependencies with go mod")
187+
cmdOut, err = exec.Command("go", "build", "./...").CombinedOutput()
188+
if err != nil {
189+
log.Fatalf("Command \"go build ./...\" failed: %v\nCommand Output:\n%v", err, string(cmdOut))
190+
}
191+
192+
operatorYAML, err := ioutil.ReadFile("deploy/operator.yaml")
193+
if err != nil {
194+
log.Fatalf("Could not read deploy/operator.yaml: %v", err)
195+
}
196+
if *imageName == "" {
197+
*imageName = "quay.io/example/memcached-operator:v0.0.1"
198+
}
199+
if *noPull {
200+
operatorYAML = bytes.Replace(operatorYAML, []byte("imagePullPolicy: Always"), []byte("imagePullPolicy: Never"), 1)
201+
}
202+
operatorYAML = bytes.Replace(operatorYAML, []byte("REPLACE_IMAGE"), []byte(*imageName), 1)
203+
err = ioutil.WriteFile("deploy/operator.yaml", operatorYAML, fileutil.DefaultFileMode)
204+
if err != nil {
205+
log.Fatalf("Failed to write to deploy/operator.yaml: %v", err)
206+
}
207+
}
208+
209+
type goModReplace struct {
210+
repo string
211+
ref string
212+
isLocal bool
213+
}
214+
215+
// getGoModReplace returns a go.mod replacement that is appropriate based on the build's
216+
// environment to support PR, fork/branch, and local builds.
217+
//
218+
// PR:
219+
// 1. Activate when TRAVIS_PULL_REQUEST_SLUG and TRAVIS_PULL_REQUEST_SHA are set
220+
// 2. Modify go.mod to replace osdk import with github.com/${TRAVIS_PULL_REQUEST_SLUG} ${TRAVIS_PULL_REQUEST_SHA}
221+
//
222+
// Fork/branch:
223+
// 1. Activate when TRAVIS_REPO_SLUG and TRAVIS_COMMIT are set
224+
// 2. Modify go.mod to replace osdk import with github.com/${TRAVIS_REPO_SLUG} ${TRAVIS_COMMIT}
225+
//
226+
// Local:
227+
// 1. Activate when none of the above TRAVIS_* variables are set.
228+
// 2. Modify go.mod to replace osdk import with local filesystem path.
229+
//
230+
func getGoModReplace(localSDKPath string) goModReplace {
231+
// PR environment
232+
prSlug, prSlugOk := os.LookupEnv("TRAVIS_PULL_REQUEST_SLUG")
233+
prSha, prShaOk := os.LookupEnv("TRAVIS_PULL_REQUEST_SHA")
234+
if prSlugOk && prSlug != "" && prShaOk && prSha != "" {
235+
return goModReplace{
236+
repo: fmt.Sprintf("github.com/%s", prSlug),
237+
ref: prSha,
238+
}
239+
}
240+
241+
// Fork/branch environment
242+
slug, slugOk := os.LookupEnv("TRAVIS_REPO_SLUG")
243+
sha, shaOk := os.LookupEnv("TRAVIS_COMMIT")
244+
if slugOk && slug != "" && shaOk && sha != "" {
245+
return goModReplace{
246+
repo: fmt.Sprintf("github.com/%s", slug),
247+
ref: sha,
248+
}
249+
}
250+
251+
// If neither of the above cases is applicable, but one of the TRAVIS_*
252+
// variables is nonetheless set, something unexpected is going on. Log
253+
// the vars and exit.
254+
if prSlugOk || prShaOk || slugOk || shaOk {
255+
log.Printf("TRAVIS_PULL_REQUEST_SLUG='%s', set: %t", prSlug, prSlugOk)
256+
log.Printf("TRAVIS_PULL_REQUEST_SHA='%s', set: %t", prSha, prShaOk)
257+
log.Printf("TRAVIS_REPO_SLUG='%s', set: %t", slug, slugOk)
258+
log.Printf("TRAVIS_COMMIT='%s', set: %t", sha, shaOk)
259+
log.Fatal("Invalid travis environment")
260+
}
261+
262+
// Local environment
263+
return goModReplace{
264+
repo: localSDKPath,
265+
isLocal: true,
266+
}
267+
}
268+
269+
func insertGoModReplace(repo, path, sha string) ([]byte, error) {
270+
modBytes, err := ioutil.ReadFile("go.mod")
271+
if err != nil {
272+
return nil, errors.Wrap(err, "failed to read go.mod")
273+
}
274+
sdkReplace := fmt.Sprintf("replace %s => %s", repo, path)
275+
if sha != "" {
276+
sdkReplace = fmt.Sprintf("%s %s", sdkReplace, sha)
277+
}
278+
modBytes = append(modBytes, []byte("\n"+sdkReplace)...)
279+
err = ioutil.WriteFile("go.mod", modBytes, fileutil.DefaultFileMode)
280+
if err != nil {
281+
return nil, errors.Wrap(err, "failed to write go.mod before replacing SDK repo")
282+
}
283+
return modBytes, nil
284+
}

test/e2e/incluster-test-code/main_test.go.tmpl renamed to test/e2e/_incluster-test-code/main_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,19 @@
1515
package e2e
1616

1717
import (
18+
"flag"
1819
"testing"
1920

2021
f "github.com/operator-framework/operator-sdk/pkg/test"
2122
)
2223

24+
type testArgs struct {
25+
e2eImageName *string
26+
}
27+
28+
var args = &testArgs{}
29+
2330
func TestMain(m *testing.M) {
31+
args.e2eImageName = flag.String("image", "", "operator image name <repository>:<tag> used to push the image, defaults to none (builds image to local docker repo)")
2432
f.MainEntry(m)
2533
}

0 commit comments

Comments
 (0)