stack_test.go
102 lines1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package platform
import (
"strings"
"testing"
)
func demoStack() *Stack {
return &Stack{
Volumes: []string{"data"},
Services: []Service{
{
Name: "app",
Image: "${APP_IMAGE}",
Ports: []Port{{Host: 80, Container: 5000}},
Env: map[string]string{"ENV": "production", "DB_PATH": "/data/app.db"},
Volumes: []Mount{{Source: "data", Target: "/data"}},
DependsOn: []string{"cache"},
Healthcheck: &Healthcheck{Cmd: "curl -sf http://localhost:5000/health", Interval: "10s", Retries: 3},
},
{
Name: "cache",
ContainerName: "cache",
Image: "redis:7",
Command: []string{"redis-server", "--save", ""},
},
},
}
}
func TestComposeYAML(t *testing.T) {
yaml := demoStack().ComposeYAML()
for _, want := range []string{
"services:",
" app:",
" image: ${APP_IMAGE}",
" restart: unless-stopped",
`- "80:5000"`,
" DB_PATH: '/data/app.db'", // sorted before ENV, single-quoted
" ENV: 'production'",
"- data:/data",
"depends_on:",
"- cache",
"healthcheck:",
"container_name: cache",
"volumes:\n data:\n",
} {
if !strings.Contains(yaml, want) {
t.Errorf("compose YAML missing %q\n---\n%s", want, yaml)
}
}
}
func TestComposeDeterministic(t *testing.T) {
// Env key ordering must be stable across renders (for hashing/diffs).
if a, b := demoStack().ComposeYAML(), demoStack().ComposeYAML(); a != b {
t.Fatal("ComposeYAML is not deterministic")
}
}
func TestCloudInit(t *testing.T) {
init := demoStack().CloudInit(CloudInitOptions{
Env: map[string]string{"APP_IMAGE": "registry.example/app:v1"},
Registry: &RegistryAuth{Host: "registry.example", Username: "u", Password: "p"},
OpenPorts: []int{80},
})
for _, want := range []string{
"#cloud-config",
"ufw allow 80/tcp",
"mkdir -p /opt/app",
"docker login registry.example -u u --password-stdin",
"base64 -d > /opt/app/docker-compose.yml",
"echo 'APP_IMAGE=registry.example/app:v1' >> /opt/app/.env",
"docker compose -f /opt/app/docker-compose.yml pull",
"docker compose -f /opt/app/docker-compose.yml up -d",
} {
if !strings.Contains(init, want) {
t.Errorf("cloud-init missing %q\n---\n%s", want, init)
}
}
}
func TestNewStackOptionsMatchStruct(t *testing.T) {
opt := NewStack(
WithVolume("data"),
WithService("app",
WithImage("${APP_IMAGE}"),
WithPort(80, 5000),
WithEnv("ENV", "production"),
WithEnv("DB_PATH", "/data/app.db"),
WithMount("data", "/data"),
WithDependsOn("cache"),
WithHealthcheck("curl -sf http://localhost:5000/health"),
),
WithService("cache", WithContainerName("cache"), WithImage("redis:7"), WithCommand("redis-server", "--save", "")),
)
opt.Services[0].Healthcheck.Interval = "10s"
opt.Services[0].Healthcheck.Retries = 3
if a, b := opt.ComposeYAML(), demoStack().ComposeYAML(); a != b {
t.Fatalf("Options-built stack differs from struct-built:\n--- options ---\n%s\n--- struct ---\n%s", a, b)
}
}