Skip to content

Commit 7a305d2

Browse files
authored
e2e: Install and run workflow and verify the result (#661)
This enhances the E2E test suite introduced in #658 to also include the following steps: - Install GitHub Actions workflow - Trigger a workflow run via a git commit - Verify the workflow run result In the workflow, we use `kubectl create cm --from-literal` to create a configmap that contains an unique test ID. In the last step we obtain the configmap from within the E2E test and check the test ID to match the expected one. To install a GitHub Actions workflow, we clone a GitHub repository denoted by the TEST_REPO envvar, progmatically generate a few files with some Go code, run `git-add`, `git-commit`, and then `git-push` to actually push the files to the repository. A single commit containing an updated workflow definition and an updated file seems to run a workflow derived to the definition introduced in the commit, which was a bit surpirising and useful behaviour. At this point, the E2E test fully covers all the steps for a GitHub token based installation. We need to add scenarios for more deployment options, like GitHub App, RunnerDeployment, HRA, and so on. But each of them would worth another pull request.
1 parent 927d6f0 commit 7a305d2

File tree

9 files changed

+426
-53
lines changed

9 files changed

+426
-53
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ bin
1919
!vendor/**/zz_generated.*
2020

2121
# editor and IDE paraphernalia
22+
.vscode
2223
.idea
2324
*.swp
2425
*.swo
@@ -31,3 +32,5 @@ bin
3132

3233
# OS
3334
.DS_STORE
35+
36+
/test-assets

Makefile

+5
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ acceptance/deploy:
211211
acceptance/tests:
212212
acceptance/checks.sh
213213

214+
.PHONY: e2e
215+
e2e:
216+
go clean -testcache
217+
go test -v -timeout 600s -run '^TestE2E$$' ./test/e2e
218+
214219
# Upload release file to GitHub.
215220
github-release: release
216221
ghr ${VERSION} release/

acceptance/deploy.sh

+2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ fi
4747
# Adhocly wait for some time until actions-runner-controller's admission webhook gets ready
4848
sleep 20
4949

50+
RUNNER_LABEL=${RUNNER_LABEL:-self-hosted}
51+
5052
if [ -n "${TEST_REPO}" ]; then
5153
if [ -n "USE_RUNNERSET" ]; then
5254
cat acceptance/testdata/repo.runnerset.yaml | envsubst | kubectl apply -f -

acceptance/testdata/repo.runnerset.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ spec:
3838
# labels:
3939
# - "mylabel 1"
4040
# - "mylabel 2"
41-
41+
labels:
42+
- "${RUNNER_LABEL}"
4243
#
4344
# Non-standard working directory
4445
#

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ require (
3636
sigs.k8s.io/controller-runtime v0.9.0
3737
sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca // indirect
3838
sigs.k8s.io/testing_frameworks v0.1.2 // indirect
39+
sigs.k8s.io/yaml v1.2.0 // indirect
3940
)

test/e2e/e2e_test.go

+205-52
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,37 @@ package e2e
33
import (
44
"context"
55
"fmt"
6+
"math/rand"
67
"os"
8+
"path/filepath"
79
"time"
810

911
"github.com/actions-runner-controller/actions-runner-controller/testing"
12+
"github.com/onsi/gomega"
13+
"sigs.k8s.io/yaml"
1014
)
1115

12-
// If you're willing to run this test via VS Code "run test" or "debug test",
13-
// almost certainly you'd want to make the default go test timeout from 30s to longer and enough value.
14-
// Press Cmd + Shift + P, type "Workspace Settings" and open it, and type "go test timeout" and set e.g. 600s there.
15-
// See https://github.com/golang/vscode-go/blob/master/docs/settings.md#gotesttimeout for more information.
16-
//
17-
// This tests ues testing.Logf extensively for debugging purpose.
18-
// But messages logged via Logf shows up only when the test failed by default.
19-
// To always enable logging, do not forget to pass `-test.v` to `go test`.
20-
// If you're using VS Code, open `Workspace Settings` and search for `go test flags`, edit the `settings.json` and put the below:
21-
// "go.testFlags": ["-v"]
22-
func TestE2E(t *testing.T) {
23-
if testing.Short() {
24-
t.Skip("Skipped as -short is set")
25-
}
26-
27-
Img := func(repo, tag string) testing.ContainerImage {
16+
var (
17+
Img = func(repo, tag string) testing.ContainerImage {
2818
return testing.ContainerImage{
2919
Repo: repo,
3020
Tag: tag,
3121
}
3222
}
3323

34-
controllerImageRepo := "actionsrunnercontrollere2e/actions-runner-controller"
35-
controllerImageTag := "e2e"
36-
controllerImage := Img(controllerImageRepo, controllerImageTag)
37-
runnerImageRepo := "actionsrunnercontrollere2e/actions-runner"
38-
runnerImageTag := "e2e"
39-
runnerImage := Img(runnerImageRepo, runnerImageTag)
24+
controllerImageRepo = "actionsrunnercontrollere2e/actions-runner-controller"
25+
controllerImageTag = "e2e"
26+
controllerImage = Img(controllerImageRepo, controllerImageTag)
27+
runnerImageRepo = "actionsrunnercontrollere2e/actions-runner"
28+
runnerImageTag = "e2e"
29+
runnerImage = Img(runnerImageRepo, runnerImageTag)
4030

41-
prebuildImages := []testing.ContainerImage{
31+
prebuildImages = []testing.ContainerImage{
4232
controllerImage,
4333
runnerImage,
4434
}
4535

46-
builds := []testing.DockerBuild{
36+
builds = []testing.DockerBuild{
4737
{
4838
Dockerfile: "../../Dockerfile",
4939
Args: []testing.BuildArg{},
@@ -56,15 +46,35 @@ func TestE2E(t *testing.T) {
5646
},
5747
}
5848

59-
certManagerVersion := "v1.1.1"
49+
certManagerVersion = "v1.1.1"
6050

61-
images := []testing.ContainerImage{
51+
images = []testing.ContainerImage{
6252
Img("docker", "dind"),
6353
Img("quay.io/brancz/kube-rbac-proxy", "v0.10.0"),
6454
Img("quay.io/jetstack/cert-manager-controller", certManagerVersion),
6555
Img("quay.io/jetstack/cert-manager-cainjector", certManagerVersion),
6656
Img("quay.io/jetstack/cert-manager-webhook", certManagerVersion),
6757
}
58+
)
59+
60+
// If you're willing to run this test via VS Code "run test" or "debug test",
61+
// almost certainly you'd want to make the default go test timeout from 30s to longer and enough value.
62+
// Press Cmd + Shift + P, type "Workspace Settings" and open it, and type "go test timeout" and set e.g. 600s there.
63+
// See https://github.com/golang/vscode-go/blob/master/docs/settings.md#gotesttimeout for more information.
64+
//
65+
// This tests ues testing.Logf extensively for debugging purpose.
66+
// But messages logged via Logf shows up only when the test failed by default.
67+
// To always enable logging, do not forget to pass `-test.v` to `go test`.
68+
// If you're using VS Code, open `Workspace Settings` and search for `go test flags`, edit the `settings.json` and put the below:
69+
// "go.testFlags": ["-v"]
70+
//
71+
// This function requires a few environment variables to be set to provide some test data.
72+
// If you're using VS Code and wanting to run this test locally,
73+
// Browse "Workspace Settings" and search for "go test env file" and put e.g. "${workspaceFolder}/.test.env" there.
74+
func TestE2E(t *testing.T) {
75+
if testing.Short() {
76+
t.Skip("Skipped as -short is set")
77+
}
6878

6979
k := testing.Start(t, testing.Cluster{}, testing.Preload(images...))
7080

@@ -88,27 +98,27 @@ func TestE2E(t *testing.T) {
8898
}
8999

90100
t.Run("install cert-manager", func(t *testing.T) {
91-
certmanagerVersion := "v1.1.1"
101+
applyCfg := testing.KubectlConfig{NoValidate: true, Env: kubectlEnv}
92102

93-
if err := k.Apply(ctx, fmt.Sprintf("https://github.com/jetstack/cert-manager/releases/download/%s/cert-manager.yaml", certmanagerVersion), testing.KubectlConfig{NoValidate: true}); err != nil {
103+
if err := k.Apply(ctx, fmt.Sprintf("https://github.com/jetstack/cert-manager/releases/download/%s/cert-manager.yaml", certManagerVersion), applyCfg); err != nil {
94104
t.Fatal(err)
95105
}
96106

97-
certmanagerKubectlCfg := testing.KubectlConfig{
107+
waitCfg := testing.KubectlConfig{
98108
Env: kubectlEnv,
99109
Namespace: "cert-manager",
100110
Timeout: 90 * time.Second,
101111
}
102112

103-
if err := k.WaitUntilDeployAvailable(ctx, "cert-manager-cainjector", certmanagerKubectlCfg); err != nil {
113+
if err := k.WaitUntilDeployAvailable(ctx, "cert-manager-cainjector", waitCfg); err != nil {
104114
t.Fatal(err)
105115
}
106116

107-
if err := k.WaitUntilDeployAvailable(ctx, "cert-manager-webhook", certmanagerKubectlCfg.WithTimeout(60*time.Second)); err != nil {
117+
if err := k.WaitUntilDeployAvailable(ctx, "cert-manager-webhook", waitCfg.WithTimeout(60*time.Second)); err != nil {
108118
t.Fatal(err)
109119
}
110120

111-
if err := k.WaitUntilDeployAvailable(ctx, "cert-manager", certmanagerKubectlCfg.WithTimeout(60*time.Second)); err != nil {
121+
if err := k.WaitUntilDeployAvailable(ctx, "cert-manager", waitCfg.WithTimeout(60*time.Second)); err != nil {
112122
t.Fatal(err)
113123
}
114124

@@ -117,32 +127,175 @@ func TestE2E(t *testing.T) {
117127
}
118128
})
119129

120-
// If you're using VS Code and wanting to run this test locally,
121-
// Browse "Workspace Settings" and search for "go test env file" and put e.g. "${workspaceFolder}/.test.env" there
122-
githubToken := os.Getenv("GITHUB_TOKEN")
123-
if githubToken == "" {
124-
t.Fatal("GITHUB_TOKEN must be set")
130+
t.Run("make default serviceaccount cluster-admin", func(t *testing.T) {
131+
cfg := testing.KubectlConfig{Env: kubectlEnv}
132+
bindingName := "default-admin"
133+
if _, err := k.GetClusterRoleBinding(ctx, bindingName, cfg); err != nil {
134+
if err := k.CreateClusterRoleBindingServiceAccount(ctx, bindingName, "cluster-admin", "default:default", cfg); err != nil {
135+
t.Fatal(err)
136+
}
137+
}
138+
})
139+
140+
cmCfg := testing.KubectlConfig{
141+
Env: kubectlEnv,
125142
}
143+
testInfoName := "test-info"
126144

127-
scriptEnv := []string{
128-
"KUBECONFIG=" + k.Kubeconfig(),
129-
"NAME=" + controllerImageRepo,
130-
"VERSION=" + controllerImageTag,
131-
"RUNNER_NAME=" + runnerImageRepo,
132-
"RUNNER_TAG=" + runnerImageTag,
133-
"TEST_REPO=" + "actions-runner-controller/mumoshu-actions-test",
134-
"TEST_ORG=" + "actions-runner-controller",
135-
"TEST_ORG_REPO=" + "actions-runner-controller/mumoshu-actions-test-org-runners",
136-
"SYNC_PERIOD=" + "10s",
137-
"USE_RUNNERSET=" + "1",
138-
"ACCEPTANCE_TEST_DEPLOYMENT_TOOL=" + "helm",
139-
"ACCEPTANCE_TEST_SECRET_TYPE=token",
140-
"GITHUB_TOKEN=" + githubToken,
145+
m, _ := k.GetCMLiterals(ctx, testInfoName, cmCfg)
146+
147+
t.Run("Save test ID", func(t *testing.T) {
148+
if m == nil {
149+
id := RandStringBytesRmndr(10)
150+
m = map[string]string{"id": id}
151+
if err := k.CreateCMLiterals(ctx, testInfoName, m, cmCfg); err != nil {
152+
t.Fatal(err)
153+
}
154+
}
155+
})
156+
157+
id := m["id"]
158+
159+
runnerLabel := "test-" + id
160+
161+
testID := t.Name() + " " + id
162+
163+
t.Logf("Using test id %s", testID)
164+
165+
githubToken := getenv(t, "GITHUB_TOKEN")
166+
testRepo := getenv(t, "TEST_REPO")
167+
testOrg := getenv(t, "TEST_ORG")
168+
testOrgRepo := getenv(t, "TEST_ORG_REPO")
169+
170+
if t.Failed() {
171+
return
141172
}
142173

143-
t.Run("install actions-runner-controller", func(t *testing.T) {
174+
t.Run("install actions-runner-controller and runners", func(t *testing.T) {
175+
scriptEnv := []string{
176+
"KUBECONFIG=" + k.Kubeconfig(),
177+
"ACCEPTANCE_TEST_DEPLOYMENT_TOOL=" + "helm",
178+
"ACCEPTANCE_TEST_SECRET_TYPE=token",
179+
"NAME=" + controllerImageRepo,
180+
"VERSION=" + controllerImageTag,
181+
"RUNNER_NAME=" + runnerImageRepo,
182+
"RUNNER_TAG=" + runnerImageTag,
183+
"TEST_REPO=" + testRepo,
184+
"TEST_ORG=" + testOrg,
185+
"TEST_ORG_REPO=" + testOrgRepo,
186+
"SYNC_PERIOD=" + "10s",
187+
"USE_RUNNERSET=" + "1",
188+
"GITHUB_TOKEN=" + githubToken,
189+
"RUNNER_LABEL=" + runnerLabel,
190+
}
191+
144192
if err := k.RunScript(ctx, "../../acceptance/deploy.sh", testing.ScriptConfig{Dir: "../..", Env: scriptEnv}); err != nil {
145193
t.Fatal(err)
146194
}
147195
})
196+
197+
testResultCMName := fmt.Sprintf("test-result-%s", id)
198+
199+
if t.Failed() {
200+
return
201+
}
202+
203+
t.Run("Install workflow", func(t *testing.T) {
204+
wfName := "E2E " + testID
205+
wf := testing.Workflow{
206+
Name: wfName,
207+
On: testing.On{
208+
Push: &testing.Push{
209+
Branches: []string{"main"},
210+
},
211+
},
212+
Jobs: map[string]testing.Job{
213+
"test": {
214+
RunsOn: runnerLabel,
215+
Steps: []testing.Step{
216+
{
217+
Uses: testing.ActionsCheckoutV2,
218+
},
219+
{
220+
Uses: "azure/setup-kubectl@v1",
221+
With: &testing.With{
222+
Version: "v1.20.2",
223+
},
224+
},
225+
{
226+
Run: "./test.sh",
227+
},
228+
},
229+
},
230+
},
231+
}
232+
233+
wfContent, err := yaml.Marshal(wf)
234+
if err != nil {
235+
t.Fatal(err)
236+
}
237+
238+
script := []byte(fmt.Sprintf(`#!/usr/bin/env bash
239+
set -vx
240+
echo hello from %s
241+
kubectl delete cm %s || true
242+
kubectl create cm %s --from-literal=status=ok
243+
`, testID, testResultCMName, testResultCMName))
244+
245+
g := testing.GitRepo{
246+
Dir: filepath.Join(t.TempDir(), "gitrepo"),
247+
Name: testRepo,
248+
CommitMessage: wfName,
249+
Contents: map[string][]byte{
250+
".github/workflows/workflow.yaml": wfContent,
251+
"test.sh": script,
252+
},
253+
}
254+
255+
if err := g.Sync(ctx); err != nil {
256+
t.Fatal(err)
257+
}
258+
})
259+
260+
if t.Failed() {
261+
return
262+
}
263+
264+
t.Run("Verify workflow run result", func(t *testing.T) {
265+
gomega.NewGomegaWithT(t).Eventually(func() (string, error) {
266+
m, err := k.GetCMLiterals(ctx, testResultCMName, cmCfg)
267+
if err != nil {
268+
return "", err
269+
}
270+
271+
result := m["status"]
272+
273+
return result, nil
274+
}, 60*time.Second, 10*time.Second).Should(gomega.Equal("ok"))
275+
})
276+
}
277+
278+
func getenv(t *testing.T, name string) string {
279+
t.Helper()
280+
281+
v := os.Getenv(name)
282+
if v == "" {
283+
t.Fatal(name + " must be set")
284+
}
285+
return v
286+
}
287+
288+
func init() {
289+
rand.Seed(time.Now().UnixNano())
290+
}
291+
292+
const letterBytes = "abcdefghijklmnopqrstuvwxyz"
293+
294+
// Copied from https://stackoverflow.com/a/31832326 with thanks
295+
func RandStringBytesRmndr(n int) string {
296+
b := make([]byte, n)
297+
for i := range b {
298+
b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))]
299+
}
300+
return string(b)
148301
}

0 commit comments

Comments
 (0)