Skip to content

Commit 45e0fc5

Browse files
bpamiriPeter Amiri
andauthored
test(cli): pin deploy init scaffold env.secret round-trip (#3158) (#3204)
#3008 added a $rejectEnvSecrets guard that hard-failed any non-empty env.secret with Wheels.Deploy.EnvSecretUnsupported, which made the env.secret block scaffolded by `wheels deploy init` (WHEELS_RELOAD_PASSWORD) un-deployable without manual editing. #3167 then retired that guard and implemented env.secret delivery via a remote --env-file (600 perms, SFTP), so the scaffolded block is now correct and deploys end-to-end. This pins that contract with two DeployMainCliSpec regression tests: - the init scaffold round-trips through config() and deploy --dry-run with no EnvSecretUnsupported/EnvSecretMissing, and the dry-run routes the secret through the --env-file path; - a deploy of the scaffold delivers WHEELS_RELOAD_PASSWORD to the role env file over SFTP (FakeSshPool uploadString), never in argv. Both tests fail if the scaffold drops its env.secret block (verified by temporarily removing it), so they guard the scaffold and the deploy engine against drifting apart again. No template or engine change is needed: the scaffold is correct as shipped now that env.secret is a delivered feature. CLI suite (lucee7 docker harness): 1071 pass / 0 fail / 2 tolerated docker-env artifacts (SshClientSpec/SshPoolSpec require docker-in-docker). Refs #3008, #3167 Fixes #3158 Signed-off-by: Peter Amiri <petera@pai.com> Co-authored-by: Peter Amiri <petera@pai.com>
1 parent d02136e commit 45e0fc5

2 files changed

Lines changed: 89 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- `wheels deploy init` scaffolds an `env.secret: [WHEELS_RELOAD_PASSWORD]` block that now round-trips cleanly: with env-file delivery in place (#2957), a freshly-scaffolded config passes `wheels deploy config` and `wheels deploy deploy --dry-run` and ships the secret to containers via the remote `--env-file`, instead of hard-failing with the retired `Wheels.Deploy.EnvSecretUnsupported` guard. The scaffold and the deploy engine are now pinned together by a regression spec (#3158)

cli/lucli/tests/specs/deploy/cli/DeployMainCliSpec.cfc

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,94 @@ component extends="wheels.wheelstest.system.BaseSpec" {
349349
expect(fileExists(root & "templates/deploy/init/dockerignore.mustache")).toBeTrue();
350350
});
351351

352+
// #3158 — the `wheels deploy init` scaffold must round-trip through
353+
// `config` and `deploy --dry-run` without the EnvSecretUnsupported
354+
// hard-fail #3008 introduced. Since #3167 retired that guard and
355+
// delivers env.secret via a remote env file, the scaffolded
356+
// `env.secret: [WHEELS_RELOAD_PASSWORD]` block is now correct and
357+
// must validate + deploy end-to-end. These pin that contract so the
358+
// scaffold and the deploy engine can never drift back apart.
359+
it("the init scaffold round-trips through config() and deploy --dry-run with no EnvSecret error (##3158)", () => {
360+
var tmpCwd = getTempDirectory() & "/wheels-3158-init-roundtrip-" & createUUID();
361+
directoryCreate(tmpCwd, true, true);
362+
try {
363+
var localCli = new cli.lucli.services.deploy.cli.DeployMainCli(
364+
new cli.lucli.services.deploy.lib.FakeSshPool(),
365+
{projectRoot: tmpCwd}
366+
);
367+
localCli.init_stub({cwd: tmpCwd, service: "demo", image: "acme/demo"});
368+
369+
var cfgPath = tmpCwd & "/config/deploy.yml";
370+
// The scaffold declares env.secret: [WHEELS_RELOAD_PASSWORD]
371+
// and .kamal/secrets declares the matching (empty) key, so
372+
// the resolver supplies a value and env_file_content() does
373+
// not raise EnvSecretMissing.
374+
expect(fileRead(cfgPath)).toInclude("WHEELS_RELOAD_PASSWORD");
375+
expect(fileRead(tmpCwd & "/.kamal/secrets")).toInclude("WHEELS_RELOAD_PASSWORD");
376+
377+
// config() must validate and dump the scaffold unchanged.
378+
var configOut = localCli.config({configPath: cfgPath});
379+
expect(configOut).toInclude("service: demo");
380+
381+
// deploy --dry-run must not throw EnvSecretUnsupported/Missing
382+
// and must route the secret through the --env-file path.
383+
var fake2 = new cli.lucli.services.deploy.lib.FakeSshPool();
384+
var dc = new cli.lucli.services.deploy.cli.DeployMainCli(
385+
fake2,
386+
{projectRoot: tmpCwd}
387+
);
388+
var dryOut = dc.deploy({configPath: cfgPath, version: "v1", dryRun: true});
389+
expect(arrayLen(fake2.calls())).toBe(0);
390+
expect(dryOut).toInclude("--env-file .kamal/apps/demo/env/roles/web.env");
391+
expect(dryOut).toInclude("WHEELS_RELOAD_PASSWORD");
392+
} finally {
393+
directoryDelete(tmpCwd, true);
394+
}
395+
});
396+
397+
it("a deploy of the init scaffold delivers WHEELS_RELOAD_PASSWORD via the env file, never argv (##3158)", () => {
398+
var tmpCwd = getTempDirectory() & "/wheels-3158-init-deliver-" & createUUID();
399+
directoryCreate(tmpCwd, true, true);
400+
try {
401+
var localCli = new cli.lucli.services.deploy.cli.DeployMainCli(
402+
new cli.lucli.services.deploy.lib.FakeSshPool(),
403+
{projectRoot: tmpCwd}
404+
);
405+
localCli.init_stub({cwd: tmpCwd, service: "demo", image: "acme/demo"});
406+
407+
// A user populates the scaffolded secrets file; the registry
408+
// password the scaffold also references is set so the deploy
409+
// path resolves cleanly.
410+
fileWrite(
411+
tmpCwd & "/.kamal/secrets",
412+
"KAMAL_REGISTRY_PASSWORD=regpw#chr(10)#WHEELS_RELOAD_PASSWORD=reload-s3cret"
413+
);
414+
415+
var fake = new cli.lucli.services.deploy.lib.FakeSshPool();
416+
var dc = new cli.lucli.services.deploy.cli.DeployMainCli(
417+
fake,
418+
{projectRoot: tmpCwd}
419+
);
420+
dc.deploy({configPath: tmpCwd & "/config/deploy.yml", version: "v1"});
421+
422+
var calls = fake.calls();
423+
var uploadIdx = 0;
424+
for (var i = 1; i <= arrayLen(calls); i++) {
425+
if (!uploadIdx && (calls[i].kind ?: "") == "uploadString") uploadIdx = i;
426+
}
427+
expect(uploadIdx).toBeGT(0);
428+
// The scaffolded secret reaches the role env file over SFTP.
429+
expect(calls[uploadIdx].remote).toBe(".kamal/apps/demo/env/roles/web.env");
430+
expect(calls[uploadIdx].content).toInclude("WHEELS_RELOAD_PASSWORD=reload-s3cret");
431+
// The value must never appear in any command string.
432+
for (var c in calls) {
433+
expect(c.cmd ?: "").notToInclude("reload-s3cret");
434+
}
435+
} finally {
436+
directoryDelete(tmpCwd, true);
437+
}
438+
});
439+
352440
it("audit dispatches tail of audit log to every host", () => {
353441
var fake = new cli.lucli.services.deploy.lib.FakeSshPool();
354442
var dc = new cli.lucli.services.deploy.cli.DeployMainCli(fake);

0 commit comments

Comments
 (0)