Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit e890e66

Browse files
Merge pull request #755 from aiordache/app-257_cnab_api_tests
[APP-257] Add integration tests to check CNAB bundle API
2 parents 06944d9 + ec4a452 commit e890e66

File tree

7 files changed

+493
-27
lines changed

7 files changed

+493
-27
lines changed

docker.Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ test-unit: build_dev_image ## run unit tests
7272
docker run --rm -v $(CURDIR)/_build/test-results:/test-results $(DEV_IMAGE_NAME) make TEST_RESULTS_PREFIX=$(TEST_RESULTS_PREFIX) test-unit
7373

7474
test-e2e: build_dev_image invocation-image ## run end-to-end tests
75-
docker run -v /var/run:/var/run:ro --rm --network="host" $(DEV_IMAGE_NAME) make TEST_RESULTS_PREFIX=$(TEST_RESULTS_PREFIX) bin/$(BIN_NAME) test-e2e
75+
docker run -v /var/run:/var/run:ro --rm --network="host" $(DEV_IMAGE_NAME) make TEST_RESULTS_PREFIX=$(TEST_RESULTS_PREFIX) bin/$(BIN_NAME) E2E_TESTS=$(E2E_TESTS) test-e2e
7676

7777
COV_LABEL := com.docker.app.cov-run=$(TAG)
7878
coverage-run: build_dev_image ## run tests with coverage

e2e/compatibility_test.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package e2e
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"io/ioutil"
8+
"net/http"
9+
"os"
10+
"path/filepath"
11+
"regexp"
12+
"strings"
13+
"testing"
14+
"time"
15+
16+
"gotest.tools/assert"
17+
"k8s.io/apimachinery/pkg/util/wait"
18+
19+
"gotest.tools/fs"
20+
)
21+
22+
const (
23+
pollTimeout = 30 * time.Second
24+
)
25+
26+
func loadAndTagImage(info dindSwarmAndRegistryInfo, tmpDir *fs.Dir, tag string, url string) error {
27+
err := downloadImageTarball(tmpDir.Join("image.tar"), url)
28+
if err != nil {
29+
return err
30+
}
31+
32+
digest := ""
33+
combined := info.dockerCmd("load", "-q", "-i", tmpDir.Join("image.tar"))
34+
for _, line := range strings.Split(combined, "\n") {
35+
if strings.Contains(line, "sha256:") {
36+
digest = strings.Split(line, "sha256:")[1]
37+
}
38+
}
39+
if digest == "" {
40+
return errors.New("Image digest not found in docker load's stdout")
41+
}
42+
43+
digest = strings.Trim(digest, " \r\n")
44+
info.dockerCmd("tag", digest, tag)
45+
46+
return nil
47+
}
48+
49+
func downloadImageTarball(filepath string, url string) error {
50+
client := http.Client{Timeout: time.Minute * 1}
51+
res, err := client.Get(url)
52+
if err != nil {
53+
return err
54+
}
55+
defer res.Body.Close()
56+
57+
// Create the file
58+
out, err := os.Create(filepath)
59+
if err != nil {
60+
return err
61+
}
62+
defer out.Close()
63+
// Write the body to file
64+
_, err = io.Copy(out, res.Body)
65+
return err
66+
}
67+
68+
func TestBackwardsCompatibilityV1(t *testing.T) {
69+
runWithDindSwarmAndRegistry(t, func(info dindSwarmAndRegistryInfo) {
70+
appName := "app-e2e"
71+
72+
data, err := ioutil.ReadFile(filepath.Join("testdata", "compatibility", "bundle-v0.9.0.json"))
73+
assert.NilError(t, err)
74+
// update bundle
75+
bundleDir := filepath.Join(info.configDir, "app", "bundles", "docker.io", "library", "app-e2e", "_tags", "v0.9.0")
76+
assert.NilError(t, os.MkdirAll(bundleDir, os.FileMode(0777)))
77+
assert.NilError(t, ioutil.WriteFile(filepath.Join(bundleDir, "bundle.json"), data, os.FileMode(0644)))
78+
79+
// load images build with an old Docker App version
80+
assert.NilError(t, loadAndTagImage(info, info.tmpDir, "app-e2e:0.1.0-invoc", "https://github.com/docker/app-e2e/raw/master/images/v0.9.0/app-e2e-invoc.tar"))
81+
assert.NilError(t, loadAndTagImage(info, info.tmpDir, "app-e2e/backend", "https://github.com/docker/app-e2e/raw/master/images/v0.9.0/backend.tar"))
82+
assert.NilError(t, loadAndTagImage(info, info.tmpDir, "app-e2e/frontend", "https://github.com/docker/app-e2e/raw/master/images/v0.9.0/frontend.tar"))
83+
84+
// list images
85+
output := info.dockerCmd("app", "image", "ls")
86+
checkContains(t, output, []string{appName})
87+
// inspect bundle
88+
output = info.dockerCmd("app", "image", "inspect", "app-e2e:v0.9.0", "--pretty")
89+
checkContains(t, output,
90+
[]string{
91+
`name:\s+app-e2e`,
92+
`backend\s+1\s+app-e2e/backend`,
93+
`frontend\s+1\s+8080\s+app-e2e/frontend`,
94+
`ports.frontend\s+8080`,
95+
})
96+
97+
// render bundle
98+
output = info.dockerCmd("app", "image", "render", "app-e2e:v0.9.0")
99+
checkContains(t, output,
100+
[]string{
101+
"image: app-e2e/frontend",
102+
"image: app-e2e/backend",
103+
"published: 8080",
104+
"target: 80",
105+
})
106+
107+
// Install app
108+
output = info.dockerCmd("app", "run", "app-e2e:v0.9.0", "--name", appName)
109+
checkContains(t, output,
110+
[]string{
111+
fmt.Sprintf("Creating service %s_backend", appName),
112+
fmt.Sprintf("Creating service %s_frontend", appName),
113+
fmt.Sprintf("Creating network %s_default", appName),
114+
})
115+
116+
// Status check -- poll app list
117+
checkStatus := func(lastAction string) {
118+
err = wait.Poll(2*time.Second, pollTimeout, func() (bool, error) {
119+
output = info.dockerCmd("app", "ls")
120+
expectedLines := []string{
121+
`RUNNING APP\s+APP NAME\s+SERVICES\s+LAST ACTION\s+RESULT\s+CREATED\s+MODIFIED\s+REFERENCE`,
122+
fmt.Sprintf(`%s\s+%s \(0.1.0\)\s+2/2\s+%s\s+success\s+.+second[s]?\sago\s+.+second[s]?\sago\s+`, appName, appName, lastAction),
123+
}
124+
matches := true
125+
for _, expected := range expectedLines {
126+
exp := regexp.MustCompile(expected)
127+
matches = matches && exp.MatchString(output)
128+
}
129+
return matches, nil
130+
})
131+
assert.NilError(t, err)
132+
}
133+
134+
queryService := func(port string) {
135+
err = wait.Poll(2*time.Second, pollTimeout, func() (bool, error) {
136+
// Check the frontend service responds
137+
url := `http://localhost:` + port
138+
output = info.execCmd("/usr/bin/wget", "-O", "-", url)
139+
return strings.Contains(output, `Hi there, I love Docker!`), nil
140+
})
141+
assert.NilError(t, err)
142+
}
143+
144+
// Check status on install
145+
checkStatus("install")
146+
147+
// query deployed service
148+
queryService("8080")
149+
150+
// Inspect app
151+
output = info.dockerCmd("app", "inspect", appName, "--pretty")
152+
checkContains(t, output,
153+
[]string{
154+
"Running App:",
155+
fmt.Sprintf("Name: %s", appName),
156+
"Result: success",
157+
`ports.frontend: "8080"`,
158+
})
159+
160+
// Update the application, changing the port
161+
output = info.dockerCmd("app", "update", appName, "--set", "ports.frontend=8081")
162+
checkContains(t, output,
163+
[]string{
164+
fmt.Sprintf("Updating service %s_backend", appName),
165+
fmt.Sprintf("Updating service %s_frontend", appName),
166+
})
167+
168+
// check status on upgrade
169+
checkStatus("upgrade")
170+
171+
// Check the frontend service responds on the new port
172+
queryService("8081")
173+
174+
// Uninstall the application
175+
output = info.dockerCmd("app", "rm", appName)
176+
checkContains(t, output,
177+
[]string{
178+
fmt.Sprintf("Removing service %s_backend", appName),
179+
fmt.Sprintf("Removing service %s_frontend", appName),
180+
fmt.Sprintf("Removing network %s_default", appName),
181+
})
182+
})
183+
}

e2e/helper_test.go

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ type dindSwarmAndRegistryInfo struct {
2929
swarmAddress string
3030
registryAddress string
3131
configuredCmd icmd.Cmd
32+
configDir string
33+
tmpDir *fs.Dir
3234
stopRegistry func()
3335
registryLogs func() string
36+
dockerCmd func(...string) string
37+
execCmd func(...string) string
38+
localCmd func(...string) string
3439
}
3540

3641
func runWithDindSwarmAndRegistry(t *testing.T, todo func(dindSwarmAndRegistryInfo)) {
@@ -40,15 +45,40 @@ func runWithDindSwarmAndRegistry(t *testing.T, todo func(dindSwarmAndRegistryInf
4045
tmpDir := fs.NewDir(t, t.Name())
4146
defer tmpDir.Remove()
4247

48+
var configDir string
49+
for _, val := range cmd.Env {
50+
if ok := strings.HasPrefix(val, "DOCKER_CONFIG="); ok {
51+
configDir = strings.Replace(val, "DOCKER_CONFIG=", "", 1)
52+
}
53+
}
54+
55+
// Initialize the info struct
56+
runner := dindSwarmAndRegistryInfo{configuredCmd: cmd, configDir: configDir, tmpDir: tmpDir}
57+
58+
// Func to execute command locally
59+
runLocalCmd := func(params ...string) string {
60+
if len(params) == 0 {
61+
return ""
62+
}
63+
cmd := icmd.Command(params[0], params[1:]...)
64+
result := icmd.RunCmd(cmd)
65+
result.Assert(t, icmd.Success)
66+
return result.Combined()
67+
}
68+
// Func to execute docker cli commands
69+
runDockerCmd := func(params ...string) string {
70+
runner.configuredCmd.Command = dockerCli.Command(params...)
71+
result := icmd.RunCmd(runner.configuredCmd)
72+
result.Assert(t, icmd.Success)
73+
return result.Combined()
74+
}
75+
4376
// The dind doesn't have the cnab-app-base image so we save it in order to load it later
44-
saveCmd := icmd.Cmd{Command: dockerCli.Command("save", fmt.Sprintf("docker/cnab-app-base:%s", internal.Version), "-o", tmpDir.Join("cnab-app-base.tar.gz"))}
45-
icmd.RunCmd(saveCmd).Assert(t, icmd.Success)
77+
runDockerCmd("save", fmt.Sprintf("docker/cnab-app-base:%s", internal.Version), "-o", tmpDir.Join("cnab-app-base.tar.gz"))
4678

4779
// Busybox is used in a few e2e test, let's pre-load it
48-
cmd.Command = dockerCli.Command("pull", "busybox:1.30.1")
49-
icmd.RunCmd(cmd).Assert(t, icmd.Success)
50-
saveCmd = icmd.Cmd{Command: dockerCli.Command("save", "busybox:1.30.1", "-o", tmpDir.Join("busybox.tar.gz"))}
51-
icmd.RunCmd(saveCmd).Assert(t, icmd.Success)
80+
runDockerCmd("pull", "busybox:1.30.1")
81+
runDockerCmd("save", "busybox:1.30.1", "-o", tmpDir.Join("busybox.tar.gz"))
5282

5383
// we have a difficult constraint here:
5484
// - the registry must be reachable from the client side (for cnab-to-oci, which does not use the docker daemon to access the registry)
@@ -67,28 +97,30 @@ func runWithDindSwarmAndRegistry(t *testing.T, todo func(dindSwarmAndRegistryInf
6797
defer swarm.Stop(t)
6898
swarmAddress := swarm.GetAddress(t)
6999

70-
cmd.Command = dockerCli.Command("context", "create", "swarm-context", "--docker", fmt.Sprintf(`"host=tcp://%s"`, swarmAddress), "--default-stack-orchestrator", "swarm")
71-
icmd.RunCmd(cmd).Assert(t, icmd.Success)
100+
// Initialize the info struct
101+
runner.registryAddress = registryAddress
102+
runner.swarmAddress = swarmAddress
103+
runner.stopRegistry = registry.StopNoFail
104+
runner.registryLogs = registry.Logs(t)
105+
106+
runDockerCmd("context", "create", "swarm-context", "--docker", fmt.Sprintf(`"host=tcp://%s"`, swarmAddress), "--default-stack-orchestrator", "swarm")
107+
108+
runner.configuredCmd.Env = append(runner.configuredCmd.Env, "DOCKER_CONTEXT=swarm-context", "DOCKER_INSTALLER_CONTEXT=swarm-context")
72109

73-
cmd.Env = append(cmd.Env, "DOCKER_CONTEXT=swarm-context", "DOCKER_INSTALLER_CONTEXT=swarm-context")
74110
// Initialize the swarm
75-
cmd.Command = dockerCli.Command("swarm", "init")
76-
icmd.RunCmd(cmd).Assert(t, icmd.Success)
111+
runDockerCmd("swarm", "init")
77112
// Load the needed base cnab image into the swarm docker engine
78-
cmd.Command = dockerCli.Command("load", "-i", tmpDir.Join("cnab-app-base.tar.gz"))
79-
icmd.RunCmd(cmd).Assert(t, icmd.Success)
113+
runDockerCmd("load", "-i", tmpDir.Join("cnab-app-base.tar.gz"))
80114
// Pre-load busybox image used by a few e2e tests
81-
cmd.Command = dockerCli.Command("load", "-i", tmpDir.Join("busybox.tar.gz"))
82-
icmd.RunCmd(cmd).Assert(t, icmd.Success)
115+
runDockerCmd("load", "-i", tmpDir.Join("busybox.tar.gz"))
83116

84-
info := dindSwarmAndRegistryInfo{
85-
configuredCmd: cmd,
86-
registryAddress: registryAddress,
87-
swarmAddress: swarmAddress,
88-
stopRegistry: registry.StopNoFail,
89-
registryLogs: registry.Logs(t),
117+
runner.localCmd = runLocalCmd
118+
runner.dockerCmd = runDockerCmd
119+
runner.execCmd = func(params ...string) string {
120+
args := append([]string{"docker", "exec", "-t", swarm.container}, params...)
121+
return runLocalCmd(args...)
90122
}
91-
todo(info)
123+
todo(runner)
92124
}
93125

94126
func build(t *testing.T, cmd icmd.Cmd, dockerCli dockerCliCommand, ref, path string) {

0 commit comments

Comments
 (0)