Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/3158-deploy-init-envsecret-roundtrip.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +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)
88 changes: 88 additions & 0 deletions cli/lucli/tests/specs/deploy/cli/DeployMainCliSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,94 @@ component extends="wheels.wheelstest.system.BaseSpec" {
expect(fileExists(root & "templates/deploy/init/dockerignore.mustache")).toBeTrue();
});

// #3158 — the `wheels deploy init` scaffold must round-trip through
// `config` and `deploy --dry-run` without the EnvSecretUnsupported
// hard-fail #3008 introduced. Since #3167 retired that guard and
// delivers env.secret via a remote env file, the scaffolded
// `env.secret: [WHEELS_RELOAD_PASSWORD]` block is now correct and
// must validate + deploy end-to-end. These pin that contract so the
// scaffold and the deploy engine can never drift back apart.
it("the init scaffold round-trips through config() and deploy --dry-run with no EnvSecret error (##3158)", () => {
var tmpCwd = getTempDirectory() & "/wheels-3158-init-roundtrip-" & createUUID();
directoryCreate(tmpCwd, true, true);
try {
var localCli = new cli.lucli.services.deploy.cli.DeployMainCli(
new cli.lucli.services.deploy.lib.FakeSshPool(),
{projectRoot: tmpCwd}
);
localCli.init_stub({cwd: tmpCwd, service: "demo", image: "acme/demo"});

var cfgPath = tmpCwd & "/config/deploy.yml";
// The scaffold declares env.secret: [WHEELS_RELOAD_PASSWORD]
// and .kamal/secrets declares the matching (empty) key, so
// the resolver supplies a value and env_file_content() does
// not raise EnvSecretMissing.
expect(fileRead(cfgPath)).toInclude("WHEELS_RELOAD_PASSWORD");
expect(fileRead(tmpCwd & "/.kamal/secrets")).toInclude("WHEELS_RELOAD_PASSWORD");

// config() must validate and dump the scaffold unchanged.
var configOut = localCli.config({configPath: cfgPath});
expect(configOut).toInclude("service: demo");

// deploy --dry-run must not throw EnvSecretUnsupported/Missing
// and must route the secret through the --env-file path.
var fake2 = new cli.lucli.services.deploy.lib.FakeSshPool();
var dc = new cli.lucli.services.deploy.cli.DeployMainCli(
fake2,
{projectRoot: tmpCwd}
);
var dryOut = dc.deploy({configPath: cfgPath, version: "v1", dryRun: true});
expect(arrayLen(fake2.calls())).toBe(0);
expect(dryOut).toInclude("--env-file .kamal/apps/demo/env/roles/web.env");
expect(dryOut).toInclude("WHEELS_RELOAD_PASSWORD");
} finally {
directoryDelete(tmpCwd, true);
}
});

it("a deploy of the init scaffold delivers WHEELS_RELOAD_PASSWORD via the env file, never argv (##3158)", () => {
var tmpCwd = getTempDirectory() & "/wheels-3158-init-deliver-" & createUUID();
directoryCreate(tmpCwd, true, true);
try {
var localCli = new cli.lucli.services.deploy.cli.DeployMainCli(
new cli.lucli.services.deploy.lib.FakeSshPool(),
{projectRoot: tmpCwd}
);
localCli.init_stub({cwd: tmpCwd, service: "demo", image: "acme/demo"});

// A user populates the scaffolded secrets file; the registry
// password the scaffold also references is set so the deploy
// path resolves cleanly.
fileWrite(
tmpCwd & "/.kamal/secrets",
"KAMAL_REGISTRY_PASSWORD=regpw#chr(10)#WHEELS_RELOAD_PASSWORD=reload-s3cret"
);

var fake = new cli.lucli.services.deploy.lib.FakeSshPool();
var dc = new cli.lucli.services.deploy.cli.DeployMainCli(
fake,
{projectRoot: tmpCwd}
);
dc.deploy({configPath: tmpCwd & "/config/deploy.yml", version: "v1"});

var calls = fake.calls();
var uploadIdx = 0;
for (var i = 1; i <= arrayLen(calls); i++) {
if (!uploadIdx && (calls[i].kind ?: "") == "uploadString") uploadIdx = i;
}
expect(uploadIdx).toBeGT(0);
// The scaffolded secret reaches the role env file over SFTP.
expect(calls[uploadIdx].remote).toBe(".kamal/apps/demo/env/roles/web.env");
expect(calls[uploadIdx].content).toInclude("WHEELS_RELOAD_PASSWORD=reload-s3cret");
// The value must never appear in any command string.
for (var c in calls) {
expect(c.cmd ?: "").notToInclude("reload-s3cret");
}
} finally {
directoryDelete(tmpCwd, true);
}
});

it("audit dispatches tail of audit log to every host", () => {
var fake = new cli.lucli.services.deploy.lib.FakeSshPool();
var dc = new cli.lucli.services.deploy.cli.DeployMainCli(fake);
Expand Down
Loading