Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit 95d21fa

Browse files
committed
First kube e2e. Adapted context create kubernetes command to allow non interactive mode.
Signed-off-by: Guillaume Tardif <[email protected]>
1 parent 6215445 commit 95d21fa

File tree

6 files changed

+143
-19
lines changed

6 files changed

+143
-19
lines changed

.github/workflows/kube-tests.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: Kube integration tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
check-optional-tests:
11+
name: Check if needs to run Kube tests
12+
runs-on: ubuntu-latest
13+
outputs:
14+
trigger-kube: ${{steps.runkubetest.outputs.triggered}}
15+
steps:
16+
- uses: khan/pull-request-comment-trigger@master
17+
name: Check if test Kube
18+
if: github.event_name == 'pull_request'
19+
id: runkubetest
20+
with:
21+
trigger: '/test-kube'
22+
23+
24+
kube-tests:
25+
name: Kube e2e tests
26+
runs-on: ubuntu-latest
27+
env:
28+
GO111MODULE: "on"
29+
needs: check-optional-tests
30+
if: github.ref == 'refs/heads/main' || needs.check-optional-tests.outputs.trigger-kube == 'true'
31+
steps:
32+
- name: Set up Go 1.15
33+
uses: actions/setup-go@v1
34+
with:
35+
go-version: 1.15
36+
id: go
37+
38+
- name: Setup docker CLI
39+
run: |
40+
curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.2.tgz | tar xz
41+
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
42+
43+
- name: Setup Kube tools
44+
run: |
45+
sudo apt-get install jq && jq --version
46+
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.10.0/kind-linux-amd64 && chmod +x ./kind && sudo mv ./kind /usr/bin/ && kind version
47+
curl -LO "https://dl.k8s.io/release/v1.20.2/bin/linux/amd64/kubectl" && sudo mv kubectl /usr/bin/ && kubectl version --client
48+
49+
- name: Checkout code into the Go module directory
50+
uses: actions/checkout@v2
51+
52+
- uses: actions/cache@v2
53+
with:
54+
path: ~/go/pkg/mod
55+
key: go-${{ hashFiles('**/go.sum') }}
56+
57+
- name: Build for Kube e2e tests
58+
env:
59+
BUILD_TAGS: kube
60+
run: make -f builder.Makefile cli
61+
62+
- name: Kube e2e Test
63+
run: make e2e-kube

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ e2e-local: ## Run End to end local tests. Set E2E_TEST=TestName to run a single
4949
e2e-win-ci: ## Run end to end local tests on Windows CI, no Docker for Linux containers available ATM. Set E2E_TEST=TestName to run a single test
5050
go test -count=1 -v $(TEST_FLAGS) ./local/e2e/cli-only
5151

52+
e2e-kube: ## Run End to end Kube tests. Set E2E_TEST=TestName to run a single test
53+
go test -timeout 10m -count=1 -v $(TEST_FLAGS) ./kube/e2e
54+
5255
e2e-aci: ## Run End to end ACI tests. Set E2E_TEST=TestName to run a single test
5356
go test -timeout 15m -count=1 -v $(TEST_FLAGS) ./aci/e2e
5457

cli/cmd/context/create_kube.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ func createKubeCommand() *cobra.Command {
5050
}
5151

5252
addDescriptionFlag(cmd, &opts.Description)
53-
cmd.Flags().StringVar(&opts.KubeconfigPath, "kubeconfig", "", "The endpoint of the Kubernetes manager")
53+
cmd.Flags().StringVar(&opts.KubeConfigPath, "kubeconfig", "", "The endpoint of the Kubernetes manager")
54+
cmd.Flags().StringVar(&opts.KubeContextName, "kubecontext", "", "The name of the context to use in kubeconfig")
5455
cmd.Flags().BoolVar(&opts.FromEnvironment, "from-env", false, "Get endpoint and creds from env vars")
5556
return cmd
5657
}

kube/context.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ import (
2929

3030
// ContextParams options for creating a Kubernetes context
3131
type ContextParams struct {
32-
ContextName string
32+
KubeContextName string
3333
Description string
34-
KubeconfigPath string
34+
KubeConfigPath string
3535
FromEnvironment bool
3636
}
3737

@@ -45,7 +45,7 @@ func (cp ContextParams) CreateContextData() (interface{}, string, error) {
4545
}
4646
user := prompt.User{}
4747
selectContext := func() error {
48-
contexts, err := kubernetes.ListAvailableKubeConfigContexts(cp.KubeconfigPath)
48+
contexts, err := kubernetes.ListAvailableKubeConfigContexts(cp.KubeConfigPath)
4949
if err != nil {
5050
return err
5151
}
@@ -57,11 +57,18 @@ func (cp ContextParams) CreateContextData() (interface{}, string, error) {
5757
}
5858
return err
5959
}
60-
cp.ContextName = contexts[selected]
60+
cp.KubeContextName = contexts[selected]
6161
return nil
6262
}
6363

64-
if cp.KubeconfigPath != "" {
64+
if cp.KubeConfigPath != "" {
65+
if cp.KubeContextName != "" {
66+
return store.KubeContext{
67+
ContextName: cp.KubeContextName,
68+
KubeconfigPath: cp.KubeConfigPath,
69+
FromEnvironment: cp.FromEnvironment,
70+
}, cp.Description, nil
71+
}
6572
err := selectContext()
6673
if err != nil {
6774
return nil, "", err
@@ -95,8 +102,8 @@ func (cp ContextParams) CreateContextData() (interface{}, string, error) {
95102
}
96103
}
97104
return store.KubeContext{
98-
ContextName: cp.ContextName,
99-
KubeconfigPath: cp.KubeconfigPath,
105+
ContextName: cp.KubeContextName,
106+
KubeconfigPath: cp.KubeConfigPath,
100107
FromEnvironment: cp.FromEnvironment,
101108
}, cp.Description, nil
102109
}

kube/e2e/compose_test.go

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ package e2e
1818

1919
import (
2020
"fmt"
21-
"net/http"
2221
"os"
22+
"path/filepath"
2323
"strings"
2424
"testing"
2525
"time"
@@ -48,31 +48,50 @@ func TestComposeUp(t *testing.T) {
4848
c := NewParallelE2eCLI(t, binDir)
4949

5050
const projectName = "compose-kube-demo"
51+
kubeconfig := filepath.Join(c.ConfigDir, "kubeconfig")
52+
kindClusterName := "e2e"
53+
kubeContextName := "kind-" + kindClusterName
54+
dockerContextName := "kube-e2e-ctx"
55+
56+
t.Run("create kube cluster", func(t *testing.T) {
57+
c.RunCmd("kind", "create", "cluster", "--name", kindClusterName, "--kubeconfig", kubeconfig, "--wait", "180s")
58+
})
59+
defer func() {
60+
c.RunDockerCmd("context", "use", "default")
61+
c.RunCmd("kind", "delete", "cluster", "--name", kindClusterName, "--kubeconfig", kubeconfig)
62+
}()
5163

5264
t.Run("create kube context", func(t *testing.T) {
53-
res := c.RunDockerCmd("context", "create", "kubernetes", "--kubeconfig", "/Users/gtardif/.kube/config", "--kubecontext", "docker-desktop", "kube-e2e")
54-
res.Assert(t, icmd.Expected{Out: `Successfully created kube context "kube-e2e"`})
55-
c.RunDockerCmd("context", "use", "kube-e2e")
65+
res := c.RunDockerCmd("context", "create", "kubernetes", "--kubeconfig", kubeconfig, "--kubecontext", kubeContextName, dockerContextName)
66+
res.Assert(t, icmd.Expected{Out: fmt.Sprintf("Successfully created kube context %q", dockerContextName)})
67+
c.RunDockerCmd("context", "use", dockerContextName)
5668
})
5769

5870
t.Run("up", func(t *testing.T) {
5971
c.RunDockerCmd("compose", "-f", "./kube-simple-demo/demo_sentences.yaml", "--project-name", projectName, "up", "-d")
6072
})
6173

74+
t.Run("compose ls", func(t *testing.T) {
75+
res := c.RunDockerCmd("compose", "ls", "--format", "json")
76+
res.Assert(t, icmd.Expected{Out: `[{"Name":"compose-kube-demo","Status":"deployed"}]`})
77+
})
78+
6279
t.Run("check running project", func(t *testing.T) {
63-
res := c.RunDockerCmd("compose", "-p", projectName, "ps")
64-
res.Assert(t, icmd.Expected{Out: `web`})
80+
// Docker Desktop kube cluster automatically exposes ports on the host, this is not the case with kind on Desktop,
81+
//we need to connect to the clusterIP, from the kind container
82+
res := c.RunCmd("sh", "-c", "kubectl --kubeconfig "+kubeconfig+" get service/web -o json | jq -r '.spec.clusterIP'")
83+
clusterIP := strings.ReplaceAll(strings.TrimSpace(res.Stdout()), `"`, "")
6584

66-
endpoint := "http://localhost:95"
67-
output := HTTPGetWithRetry(t, endpoint+"/words/noun", http.StatusOK, 2*time.Second, 20*time.Second)
68-
assert.Assert(t, strings.Contains(output, `"word":`))
85+
endpoint := fmt.Sprintf("http://%s:80/words/noun", clusterIP)
86+
c.WaitForCmdResult(icmd.Command("docker", "--context", "default", "exec", "e2e-control-plane", "curl", endpoint), StdoutContains(`"word":`), 3*time.Minute, 3*time.Second)
6987
})
88+
7089
t.Run("down", func(t *testing.T) {
7190
_ = c.RunDockerCmd("compose", "--project-name", projectName, "down")
7291
})
7392

74-
t.Run("check containers after down", func(t *testing.T) {
75-
res := c.RunDockerCmd("ps", "--all")
93+
t.Run("check stack after down", func(t *testing.T) {
94+
res := c.RunDockerCmd("compose", "ls")
7695
assert.Assert(t, !strings.Contains(res.Combined(), projectName), res.Combined())
7796
})
7897
}

utils/e2e/framework.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,44 @@ func (c *E2eCLI) RunDockerOrExitError(args ...string) *icmd.Result {
203203
return icmd.RunCmd(c.NewDockerCmd(args...))
204204
}
205205

206+
// RunCmd runs a command, expects no error and returns a result
207+
func (c *E2eCLI) RunCmd(args ...string) *icmd.Result {
208+
fmt.Printf(" [%s] %s\n", c.test.Name(), strings.Join(args, " "))
209+
assert.Assert(c.test, len(args) >= 1, "require at least one command in parameters")
210+
res := icmd.RunCmd(c.NewCmd(args[0], args[1:]...))
211+
res.Assert(c.test, icmd.Success)
212+
return res
213+
}
214+
206215
// RunDockerCmd runs a docker command, expects no error and returns a result
207216
func (c *E2eCLI) RunDockerCmd(args ...string) *icmd.Result {
208217
res := c.RunDockerOrExitError(args...)
209218
res.Assert(c.test, icmd.Success)
210219
return res
211220
}
212221

222+
// StdoutContains returns a predicate on command result expecting a string in stdout
223+
func StdoutContains(expected string) func(*icmd.Result) bool {
224+
return func(res *icmd.Result) bool {
225+
return strings.Contains(res.Stdout(), expected)
226+
}
227+
}
228+
229+
// WaitForCmdResult try to execute a cmd until resulting output matches given predicate
230+
func (c *E2eCLI) WaitForCmdResult(command icmd.Cmd, predicate func(*icmd.Result) bool, timeout time.Duration, delay time.Duration) {
231+
assert.Assert(c.test, timeout.Nanoseconds() > delay.Nanoseconds(), "timeout must be greater than delay")
232+
var res *icmd.Result
233+
checkStopped := func(logt poll.LogT) poll.Result {
234+
fmt.Printf(" [%s] %s\n", c.test.Name(), strings.Join(command.Command, " "))
235+
res = icmd.RunCmd(command)
236+
if !predicate(res) {
237+
return poll.Continue("Cmd output did not match requirement: %q", res.Combined())
238+
}
239+
return poll.Success()
240+
}
241+
poll.WaitOn(c.test, checkStopped, poll.WithDelay(delay), poll.WithTimeout(timeout))
242+
}
243+
213244
// PathEnvVar returns path (os sensitive) for running test
214245
func (c *E2eCLI) PathEnvVar() string {
215246
path := c.BinDir + ":" + os.Getenv("PATH")

0 commit comments

Comments
 (0)