Skip to content

Commit c500238

Browse files
committed
hack/tests,test/e2e: separate testing and scaffolding of E2E tests
1 parent 915ddef commit c500238

File tree

12 files changed

+801
-1498
lines changed

12 files changed

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

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)