-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parse App errors from CloudExecutables that use sub-shell #32997
Comments
Option A: Serialize and deserialize errors through a fileThe user's TypeScript app is unchanged and executed through On the Toolkit side, we can expect the app failure. In that case, the Cloud Assembly Source can read the log file and deserialize the error into a usable form and re-throw it. Instead of a log file, this could also use a stream and/or numbered file descriptor. Required changes to the CDK codeFor all known CDK lib errors (currently only Usagetry {
const cdk = new Toolkit();
const cx = await cdk.fromTypescriptCdkApp("path/to/user/app.ts");
await cdk.synth(cx);
} catch (error: any) {
console.error(error.name, error.message);
} Library code
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { type CdkAppSourceProps, type ICloudAssemblySource, Toolkit } from "@aws-cdk/toolkit";
import { createRequire } from "node:module";
export class TypeScriptCdkAppToolkit extends Toolkit {
async fromTypescriptCdkApp(entrypoint: string, props: CdkAppSourceProps) {
const requireFunc = createRequire(import.meta.dirname);
const tsx = requireFunc.resolve('.bin/tsx');
const prelude = requireFunc.resolve('./error-aware-prelude.ts');
const app = await this.fromCdkApp(`${tsx} -r ${prelude} ${entrypoint}`, props)
return new TypeScriptCdkApp(app);
}
}
class TypeScriptCdkApp implements ICloudAssemblySource {
private readonly source: ICloudAssemblySource;
constructor(source: ICloudAssemblySource) {
this.source = source;
}
async produce() {
// We are serializing errors into a temporary log file
const errorFolder = await fs.mkdtemp(path.join(os.tmpdir(), "cdk-errors-"));
const errorLog = path.join(errorFolder, "cdk-errors.log");
process.env.CDK_ERROR_LOG = errorLog;
try {
// attempt to run the app
return await this.source.produce();
} catch (originalError) {
// we have encountered an error!
// let's deserialize the error from the log file
const errorLogContents = await fs.readFile(errorLog, { encoding: "utf-8" });
const lastLine = errorLogContents.split("\n").reverse().find(line => line.trim().length > 0) || '';
let errorObject;
try {
errorObject = JSON.parse(lastLine);
} catch {
// if we can't serialize the error => throw the original error
throw originalError;
}
throw errorObject;
} finally {
// cleanup the environment
delete process.env.CDK_ERROR_LOG;
await fs.rm(errorLog, { force: true });
}
}
}
if (process.env.CDK_ERROR_LOG) {
process.on("uncaughtException", function (err) {
const errorJson = JSON.stringify({
name: err.name,
message: err.message,
stack: err.stack,
});
require("fs").writeFileSync(process.env.CDK_ERROR_LOG, errorJson + "\n", { flag: "a" });
process.exitCode = 1;
process.exit();
});
} |
Option B: Dynamic TypeScript importWe use Longer term, this might become possible in vanilla Node.js through the work done on built-in TypeScript support. Implications on the execution context need to be considered, since the userland code is now executing in the same virtual machine as the Toolkit caller. This might be okay, but an approach using Usageimport { Toolkit } from '@aws-cdk/toolkit';
import { tsImport } from 'tsx/esm/api'
import { CloudAssembly } from 'aws-cdk-lib/cx-api';
try {
const outdir = 'cdk.out.custom';
const cdk = new Toolkit();
const cx = await cdk.fromAssemblyBuilder(async () => {
await tsImport('./app.ts', import.meta.url)
return new CloudAssembly(outdir);
}, { outdir });
await cdk.synth(cx);
} catch (error: any) {
console.error(error.name, error.message);
} |
Errors thrown by CDK Apps are just printed to stdout/stderr by the subprocess. we should intercept, capture and re-throw the error. Instead this should be thrown by
toolkit.synth
directly.We need to pass a magic env variable
CDK_SERIALIZE_ERRORS
to the cdk app. When this variable is set, then we add the following snippet:This needs to somehow announce to the parent process that the app is no sending a JSON serialized error through the channel.
In the CLI/Toolkit we need to detect this and NOT print regularly. Instead we deserialize the error, recreate it to a proper Node exception and re throw it (toolkit). In the CLI we can also add some nice formatting on the error.
The text was updated successfully, but these errors were encountered: