mirror of
https://github.com/morluto/rea
synced 2026-10-28 06:46:47 -04:00
fix(environment): retain tarball descriptor ownership during export
Use the shared positional reader for both tarball passes so early output refusal cannot close the descriptor ahead of its owner. Closes #5684
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
appendFile,
|
||||
chmod,
|
||||
mkdir,
|
||||
readFile,
|
||||
@@ -70,6 +71,58 @@ it("retains the admitted tarball descriptor and refuses a pathname replacement d
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps tarball ownership when the admitted inode grows during output", async () => {
|
||||
const root = await createTestTempDirectory("rea-tarball-growth-");
|
||||
const files = await writeEnvironmentServeFiles(root);
|
||||
const inputs = await readEnvironmentExportInputs(
|
||||
files.manifestPath,
|
||||
files.capturePath,
|
||||
);
|
||||
if (!inputs.ok) throw inputs.error;
|
||||
const tarball = join(root, "package.tgz");
|
||||
await writeFile(tarball, Buffer.alloc(2 * 1024 * 1024, 0x41));
|
||||
const directory = join(root, "bundle");
|
||||
const original = SafeOutputTree.prototype.write;
|
||||
let grown = false;
|
||||
const writer = vi
|
||||
.spyOn(SafeOutputTree.prototype, "write")
|
||||
.mockImplementation(async function (
|
||||
this: SafeOutputTree,
|
||||
...args: Parameters<SafeOutputTree["write"]>
|
||||
) {
|
||||
const [relative] = args;
|
||||
if (!grown && relative === "envs/grafana-example/rea-agents.tgz") {
|
||||
grown = true;
|
||||
await appendFile(tarball, Buffer.alloc(1024 * 1024, 0x42));
|
||||
}
|
||||
return original.apply(this, args);
|
||||
});
|
||||
try {
|
||||
const result = await exportEnvironmentBundle({
|
||||
...inputs.value,
|
||||
source: environmentServeSource(),
|
||||
directory,
|
||||
packageTarball: tarball,
|
||||
});
|
||||
expect(grown).toBe(true);
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "AnalysisOutputError",
|
||||
operation: "environment-export",
|
||||
reason:
|
||||
"Extracted content exceeds the inventoried size: envs/grafana-example/rea-agents.tgz",
|
||||
},
|
||||
});
|
||||
if (result.ok)
|
||||
throw new Error("Expected the changed tarball to be refused");
|
||||
expect(result.error.cleanup).toBeUndefined();
|
||||
expect(result.error.message).not.toContain("invalidated");
|
||||
} finally {
|
||||
writer.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
it("uses confirmed publication after the output owner's descriptor cleanup completes", async () => {
|
||||
const root = await createTestTempDirectory("rea-bundle-published-");
|
||||
const files = await writeEnvironmentServeFiles(root);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { AnalysisCleanupLease } from "../../artifacts/AnalysisCleanupLease.js";
|
||||
import { ArtifactFileCleanupFailure } from "../../artifacts/ArtifactFileOperation.js";
|
||||
import { RegularFileCleanupFailure } from "../../filesystem/OwnedRegularFile.js";
|
||||
import { readFileHandleChunks } from "../../filesystem/readFileHandleChunks.js";
|
||||
import { streamChunkToBuffer } from "../../artifacts/StreamBytes.js";
|
||||
import { ArtifactResourceScope } from "../../artifacts/ArtifactResourceScope.js";
|
||||
import { SafeOutputTree } from "../../artifacts/SafeOutputTree.js";
|
||||
@@ -51,10 +52,7 @@ const writeTarball = async (
|
||||
): Promise<WrittenBundleFile> => {
|
||||
const hash = createHash("sha256");
|
||||
let observed = 0;
|
||||
for await (const chunk of source.handle.createReadStream({
|
||||
start: 0,
|
||||
autoClose: false,
|
||||
})) {
|
||||
for await (const chunk of readFileHandleChunks(source.handle)) {
|
||||
const bytes = streamChunkToBuffer(chunk);
|
||||
observed += bytes.length;
|
||||
if (observed > source.size) throw new RegularFileChangedError(source.path);
|
||||
@@ -63,7 +61,7 @@ const writeTarball = async (
|
||||
if (observed !== source.size) throw new RegularFileChangedError(source.path);
|
||||
const written = await tree.write(
|
||||
relative,
|
||||
source.handle.createReadStream({ start: 0, autoClose: false }),
|
||||
readFileHandleChunks(source.handle),
|
||||
{
|
||||
bytes: source.size,
|
||||
sha256: hash.digest("hex"),
|
||||
|
||||
Reference in New Issue
Block a user