-
-
Notifications
You must be signed in to change notification settings - Fork 109
Expand file tree
/
Copy pathAppCommandsSpec.cfc
More file actions
199 lines (176 loc) · 10.9 KB
/
Copy pathAppCommandsSpec.cfc
File metadata and controls
199 lines (176 loc) · 10.9 KB
1
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
component extends="wheels.wheelstest.system.BaseSpec" {
function beforeAll() {
variables.cfg = new cli.lucli.services.deploy.config.ConfigLoader()
.load(expandPath("/cli/lucli/tests/_fixtures/deploy/configs/minimal.yml"));
}
function run() {
describe("AppCommands", () => {
it("run() produces expected docker-run string", () => {
var cmd = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg)
.run(variables.cfg.roles()[1], "abc1234");
expect(cmd).toInclude("docker run");
expect(cmd).toInclude("--detach");
expect(cmd).toInclude("--restart unless-stopped");
expect(cmd).toInclude("--name demo-web-abc1234");
expect(cmd).toInclude("--network kamal");
expect(cmd).toInclude("--label service=demo");
expect(cmd).toInclude("--label role=web");
expect(cmd).toInclude("--label version=abc1234");
expect(cmd).toInclude("acme/demo:abc1234");
});
it("container_name follows service-role-version convention", () => {
var cmds = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg);
expect(cmds.container_name(variables.cfg.roles()[1], "v1")).toBe("demo-web-v1");
});
it("containers() filters by service label", () => {
var cmd = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg).containers();
expect(cmd).toInclude("docker ps");
expect(cmd).toInclude("--filter label=service=demo");
});
it("stop() targets the versioned container", () => {
var cmd = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg)
.stop(variables.cfg.roles()[1], "v9");
expect(cmd).toInclude("docker stop");
expect(cmd).toInclude("demo-web-v9");
});
it("start() targets the versioned container", () => {
var cmd = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg)
.start(variables.cfg.roles()[1], "v9");
expect(cmd).toInclude("docker start");
expect(cmd).toInclude("demo-web-v9");
});
it("logs() includes --tail and --follow flags when requested", () => {
var cmds = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg);
var cmd = cmds.logs({tail: 50, follow: true, container: "demo-web-abc"});
expect(cmd).toInclude("docker logs");
expect(cmd).toInclude("--tail 50");
expect(cmd).toInclude("--follow");
expect(cmd).toInclude("demo-web-abc");
});
it("maintenance() touches the marker file", () => {
var cmd = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg)
.maintenance(variables.cfg.roles()[1], "v1");
expect(cmd).toInclude("touch /tmp/kamal-maintenance-demo");
});
it("live() removes the marker file", () => {
var cmd = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg)
.live(variables.cfg.roles()[1], "v1");
expect(cmd).toInclude("rm -f /tmp/kamal-maintenance-demo");
});
it("run() escapes env values containing spaces and metacharacters", () => {
var tmp = getTempFile(getTempDirectory(), "yml");
fileWrite(tmp, "service: demo#chr(10)#image: acme/demo#chr(10)#servers: [1.2.3.4]#chr(10)#"
& "env: {clear: {GREETING: 'hello world; $(whoami)'}}");
var cfg = new cli.lucli.services.deploy.config.ConfigLoader().load(tmp);
var cmd = new cli.lucli.services.deploy.commands.AppCommands(cfg)
.run(cfg.roles()[1], "v1");
expect(cmd).toInclude("-e 'GREETING=hello world; $(whoami)'");
expect(cmd).notToInclude("-e GREETING");
});
// env.secret delivery (#2957, Wave 2b) — secrets reach the container
// via a remote env file (600 perms) referenced by --env-file, never argv.
it("run() references the role env file via --env-file when env.secret is declared (##2957)", () => {
var tmp = getTempFile(getTempDirectory(), "yml");
fileWrite(tmp, "service: demo#chr(10)#image: acme/demo#chr(10)#servers: [1.2.3.4]#chr(10)#"
& "env: {secret: [DATABASE_PASSWORD]}");
var cfg = new cli.lucli.services.deploy.config.ConfigLoader().load(tmp);
var cmd = new cli.lucli.services.deploy.commands.AppCommands(cfg)
.run(cfg.roles()[1], "v1");
expect(cmd).toInclude("--env-file .kamal/apps/demo/env/roles/web.env");
// The secret NAME must never surface as a -e pair.
expect(cmd).notToInclude("-e 'DATABASE_PASSWORD");
expect(cmd).notToInclude("-e DATABASE_PASSWORD");
});
it("run() omits --env-file when no env.secret is declared", () => {
var cmd = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg)
.run(variables.cfg.roles()[1], "v1");
expect(cmd).notToInclude("--env-file");
});
it("ensure_env_file() creates the env dir and pre-locks the file to 600 perms (##2957)", () => {
var cmds = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg);
var cmd = cmds.ensure_env_file(variables.cfg.roles()[1]);
expect(cmd).toInclude("mkdir -p '.kamal/apps/demo/env/roles'");
expect(cmd).toInclude("touch '.kamal/apps/demo/env/roles/web.env'");
expect(cmd).toInclude("chmod 600 '.kamal/apps/demo/env/roles/web.env'");
});
it("relock_env_file() re-locks the role env file to 600 perms after upload (##2957)", () => {
var cmds = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg);
expect(cmds.relock_env_file(variables.cfg.roles()[1]))
.toBe("chmod 600 '.kamal/apps/demo/env/roles/web.env'");
});
it("env_file_path() namespaces by destination when one is set", () => {
var cfg = new cli.lucli.services.deploy.config.Config(
{service: "demo", image: "acme/demo", servers: ["1.2.3.4"]},
{destination: "staging"}
);
var cmds = new cli.lucli.services.deploy.commands.AppCommands(cfg);
expect(cmds.env_file_path(cfg.roles()[1]))
.toBe(".kamal/apps/demo-staging/env/roles/web.env");
});
it("env_file_content() renders KEY=value lines from resolved secrets (##2957)", () => {
var cmds = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg);
var content = cmds.env_file_content(
["DB_PASSWORD", "API_KEY"],
{DB_PASSWORD: "p@ss", API_KEY: "key-1", UNRELATED: "x"}
);
expect(content).toInclude("DB_PASSWORD=p@ss");
expect(content).toInclude("API_KEY=key-1");
expect(content).notToInclude("UNRELATED");
});
it("env_file_content() escapes newlines and backslashes Kamal-style", () => {
var cmds = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg);
var content = cmds.env_file_content(
["CERT"],
{CERT: "line1" & chr(10) & "line2\with-backslash"}
);
// Backslash doubled, literal newline collapsed to \n — one line per key.
expect(content).toInclude("CERT=line1\nline2\\with-backslash");
});
it("env_file_content() throws Wheels.Deploy.EnvSecretMissing naming unresolved keys, values never read (##2957)", () => {
var cmds = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg);
var state = {threw: false, message: ""};
try {
cmds.env_file_content(["PRESENT", "ABSENT_ONE"], {PRESENT: "v"});
} catch (Wheels.Deploy.EnvSecretMissing e) {
state.threw = true;
state.message = e.message;
}
expect(state.threw).toBeTrue();
expect(state.message).toInclude("ABSENT_ONE");
// Only the MISSING names are listed — resolvable keys stay out.
expect(state.message).notToInclude("PRESENT");
});
// #2957 DEP-11a — `docker run --name X` hard-fails when ANY container
// (running or stopped) already holds the name, which is the guaranteed
// state on a same-version redeploy. The conflict guard must be exact-name
// anchored and a no-op when nothing matches (xargs -r).
it("remove_conflicting() force-removes an existing same-name container, idempotently (##2957 DEP-11a)", () => {
var cmd = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg)
.remove_conflicting(variables.cfg.roles()[1], "v1");
expect(cmd).toInclude("docker container ls --all");
expect(cmd).toInclude("--filter name=^demo-web-v1$");
expect(cmd).toInclude("--quiet");
expect(cmd).toInclude("xargs -r docker container rm --force");
});
// #2957 DEP-11a — superseded containers were never stopped after
// cutover, so every old version kept running (and restarting) forever.
it("stop_old_versions() stops same-role containers except the current version (##2957 DEP-11a)", () => {
var cmd = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg)
.stop_old_versions(variables.cfg.roles()[1], "v2");
expect(cmd).toInclude("docker ps");
expect(cmd).toInclude("--filter label=service=demo");
expect(cmd).toInclude("--filter label=role=web");
expect(cmd).toInclude("--filter label=destination=");
expect(cmd).toInclude("grep -v '^demo-web-v2$'");
expect(cmd).toInclude("xargs -r docker stop");
});
it("remove() chains docker stop and docker rm", () => {
var cmd = new cli.lucli.services.deploy.commands.AppCommands(variables.cfg)
.remove(variables.cfg.roles()[1], "v9");
expect(cmd).toInclude("docker stop demo-web-v9");
expect(cmd).toInclude("docker rm demo-web-v9");
expect(cmd).toInclude("&&");
});
});
}
}