cdk-local 0.154.0 → 0.156.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/internal.d.ts +294 -24
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -2
- package/dist/{local-studio-CJfNARFz.js → local-studio-BhFxp-jM.js} +2071 -88
- package/dist/local-studio-BhFxp-jM.js.map +1 -0
- package/dist/{local-studio-DDFmguSG.d.ts → local-studio-DXVr171Y.d.ts} +158 -2
- package/dist/local-studio-DXVr171Y.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/local-studio-CJfNARFz.js.map +0 -1
- package/dist/local-studio-DDFmguSG.d.ts.map +0 -1
|
@@ -30,7 +30,7 @@ import { StringDecoder } from "node:string_decoder";
|
|
|
30
30
|
import { promisify } from "node:util";
|
|
31
31
|
import { ECRClient, GetAuthorizationTokenCommand } from "@aws-sdk/client-ecr";
|
|
32
32
|
import { readFile } from "fs/promises";
|
|
33
|
-
import { basename as basename$1, dirname as dirname$1, join as join$1 } from "path";
|
|
33
|
+
import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, posix, relative as relative$1, resolve as resolve$1, sep as sep$1 } from "path";
|
|
34
34
|
import { WebSocket, WebSocketServer } from "ws";
|
|
35
35
|
import { Readable } from "node:stream";
|
|
36
36
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
@@ -46,6 +46,11 @@ import * as vm from "node:vm";
|
|
|
46
46
|
import "@aws-sdk/signature-v4a";
|
|
47
47
|
import { CloudFrontClient, paginateListKeyValueStores } from "@aws-sdk/client-cloudfront";
|
|
48
48
|
import { CloudFrontKeyValueStoreClient, GetKeyCommand, ResourceNotFoundException } from "@aws-sdk/client-cloudfront-keyvaluestore";
|
|
49
|
+
import { existsSync as existsSync$1, readFileSync as readFileSync$1, rmSync as rmSync$1, statSync as statSync$1 } from "fs";
|
|
50
|
+
import { request as request$2 } from "http";
|
|
51
|
+
import { execFile as execFile$1, spawn as spawn$1 } from "child_process";
|
|
52
|
+
import { promisify as promisify$1 } from "util";
|
|
53
|
+
import { randomUUID as randomUUID$1 } from "crypto";
|
|
49
54
|
import { EventEmitter } from "node:events";
|
|
50
55
|
|
|
51
56
|
//#region src/utils/url-authority.ts
|
|
@@ -1595,14 +1600,14 @@ function parseTarget(target) {
|
|
|
1595
1600
|
function resolveLambdaTarget(target, stacks) {
|
|
1596
1601
|
if (stacks.length === 0) throw new LocalInvokeResolutionError("No stacks found in the synthesized assembly.");
|
|
1597
1602
|
const parsed = parseTarget(target);
|
|
1598
|
-
const stack = pickStack$
|
|
1603
|
+
const stack = pickStack$6(parsed, stacks, target);
|
|
1599
1604
|
const template = stack.template;
|
|
1600
1605
|
const resources = template.Resources ?? {};
|
|
1601
1606
|
let match;
|
|
1602
1607
|
if (parsed.isPath) {
|
|
1603
1608
|
const index = buildCdkPathIndex(template);
|
|
1604
1609
|
const lambdaMatches = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId }) => resources[logicalId]?.Type === "AWS::Lambda::Function");
|
|
1605
|
-
if (lambdaMatches.length === 0) throw notFoundError$
|
|
1610
|
+
if (lambdaMatches.length === 0) throw notFoundError$4(target, stack, resources);
|
|
1606
1611
|
if (lambdaMatches.length > 1) throw new LocalInvokeResolutionError(`Target '${target}' matches ${lambdaMatches.length} Lambda functions in ${displayStackName(stack.stackName)}: ` + lambdaMatches.map((m) => displayIdent(m.logicalId, { listMember: true })).join(", ") + ". Refine the path or use the stack:LogicalId form.");
|
|
1607
1612
|
const m = lambdaMatches[0];
|
|
1608
1613
|
match = {
|
|
@@ -1611,7 +1616,7 @@ function resolveLambdaTarget(target, stacks) {
|
|
|
1611
1616
|
};
|
|
1612
1617
|
} else {
|
|
1613
1618
|
const resource = resources[parsed.pathOrId];
|
|
1614
|
-
if (!resource) throw notFoundError$
|
|
1619
|
+
if (!resource) throw notFoundError$4(target, stack, resources);
|
|
1615
1620
|
match = {
|
|
1616
1621
|
logicalId: parsed.pathOrId,
|
|
1617
1622
|
resource
|
|
@@ -1624,7 +1629,7 @@ function resolveLambdaTarget(target, stacks) {
|
|
|
1624
1629
|
}
|
|
1625
1630
|
return extractLambdaProperties(stack, logicalId, resource, resources);
|
|
1626
1631
|
}
|
|
1627
|
-
function pickStack$
|
|
1632
|
+
function pickStack$6(parsed, stacks, target) {
|
|
1628
1633
|
const selection = selectTargetStack(parsed, target, stacks);
|
|
1629
1634
|
switch (selection.kind) {
|
|
1630
1635
|
case "found": return selection.stack;
|
|
@@ -1957,7 +1962,7 @@ function describeLayerEntry(entry) {
|
|
|
1957
1962
|
return Object.prototype.toString.call(entry);
|
|
1958
1963
|
}
|
|
1959
1964
|
}
|
|
1960
|
-
function notFoundError$
|
|
1965
|
+
function notFoundError$4(target, stack, resources) {
|
|
1961
1966
|
const lambdas = [];
|
|
1962
1967
|
for (const [logicalId, resource] of Object.entries(resources)) {
|
|
1963
1968
|
if (resource.Type !== "AWS::Lambda::Function") continue;
|
|
@@ -2002,7 +2007,7 @@ var AgentCoreResolutionError = class AgentCoreResolutionError extends Error {
|
|
|
2002
2007
|
function resolveAgentCoreTarget(target, stacks, imageContext) {
|
|
2003
2008
|
if (stacks.length === 0) throw new AgentCoreResolutionError("No stacks found in the synthesized assembly.");
|
|
2004
2009
|
const parsed = parseTarget(target);
|
|
2005
|
-
const stack = pickStack$
|
|
2010
|
+
const stack = pickStack$5(parsed, stacks, target);
|
|
2006
2011
|
const resources = stack.template.Resources ?? {};
|
|
2007
2012
|
const { logicalId, resource } = matchRuntime(parsed, target, stack, resources);
|
|
2008
2013
|
if (resource.Type !== "AWS::BedrockAgentCore::Runtime") throw new AgentCoreResolutionError(`Resource ${displayIdent(logicalId)} in ${stack.stackName} is ${resource.Type}, not ${AGENTCORE_RUNTIME_TYPE}. ${getEmbedConfig().cliName} invoke-agentcore only runs Bedrock AgentCore Runtime resources.`);
|
|
@@ -2011,7 +2016,7 @@ function resolveAgentCoreTarget(target, stacks, imageContext) {
|
|
|
2011
2016
|
function pickAgentCoreCandidateStack(target, stacks) {
|
|
2012
2017
|
return candidateTargetStack(parseTarget(target), target, stacks);
|
|
2013
2018
|
}
|
|
2014
|
-
function pickStack$
|
|
2019
|
+
function pickStack$5(parsed, stacks, target) {
|
|
2015
2020
|
const selection = selectTargetStack(parsed, target, stacks);
|
|
2016
2021
|
switch (selection.kind) {
|
|
2017
2022
|
case "found": return selection.stack;
|
|
@@ -2024,7 +2029,7 @@ function matchRuntime(parsed, target, stack, resources) {
|
|
|
2024
2029
|
if (parsed.isPath) {
|
|
2025
2030
|
const index = buildCdkPathIndex(stack.template);
|
|
2026
2031
|
const runtimeMatches = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId }) => resources[logicalId]?.Type === AGENTCORE_RUNTIME_TYPE);
|
|
2027
|
-
if (runtimeMatches.length === 0) throw notFoundError$
|
|
2032
|
+
if (runtimeMatches.length === 0) throw notFoundError$3(target, stack, resources);
|
|
2028
2033
|
if (runtimeMatches.length > 1) throw new AgentCoreResolutionError(`Target '${target}' matches ${runtimeMatches.length} AgentCore Runtimes in ${stack.stackName}: ` + runtimeMatches.map((m) => m.logicalId).join(", ") + ". Refine the path or use the stack:LogicalId form.");
|
|
2029
2034
|
const m = runtimeMatches[0];
|
|
2030
2035
|
return {
|
|
@@ -2033,13 +2038,13 @@ function matchRuntime(parsed, target, stack, resources) {
|
|
|
2033
2038
|
};
|
|
2034
2039
|
}
|
|
2035
2040
|
const resource = resources[parsed.pathOrId];
|
|
2036
|
-
if (!resource) throw notFoundError$
|
|
2041
|
+
if (!resource) throw notFoundError$3(target, stack, resources);
|
|
2037
2042
|
return {
|
|
2038
2043
|
logicalId: parsed.pathOrId,
|
|
2039
2044
|
resource
|
|
2040
2045
|
};
|
|
2041
2046
|
}
|
|
2042
|
-
function notFoundError$
|
|
2047
|
+
function notFoundError$3(target, stack, resources) {
|
|
2043
2048
|
const available = Object.entries(resources).filter(([, r]) => r.Type === AGENTCORE_RUNTIME_TYPE).map(([id]) => id);
|
|
2044
2049
|
const hint = available.length > 0 ? `Available AgentCore Runtimes in ${stack.stackName}: ${available.join(", ")}.` : `No ${AGENTCORE_RUNTIME_TYPE} resources found in ${stack.stackName}.`;
|
|
2045
2050
|
return new AgentCoreResolutionError(`Target '${target}' not found. ${hint}`);
|
|
@@ -3531,7 +3536,9 @@ function listTargets(stacks) {
|
|
|
3531
3536
|
ecsTaskDefinitions: sortEntries(scanByType(stacks, "AWS::ECS::TaskDefinition")),
|
|
3532
3537
|
agentCoreRuntimes: sortEntries(scanAgentCoreRuntimes(stacks)),
|
|
3533
3538
|
loadBalancers: sortEntries(scanApplicationLoadBalancers(stacks)),
|
|
3534
|
-
cloudFrontDistributions: sortEntries(scanByType(stacks, "AWS::CloudFront::Distribution"))
|
|
3539
|
+
cloudFrontDistributions: sortEntries(scanByType(stacks, "AWS::CloudFront::Distribution")),
|
|
3540
|
+
webFunctions: sortEntries(scanByType(stacks, "AWS::Lambda::WebFunction")),
|
|
3541
|
+
microvmImages: sortEntries(scanByType(stacks, "AWS::Lambda::MicrovmImage"))
|
|
3535
3542
|
};
|
|
3536
3543
|
}
|
|
3537
3544
|
const pathOf$1 = (e) => e.displayPath ?? e.qualifiedId;
|
|
@@ -3554,7 +3561,7 @@ function sortApiEntries(entries) {
|
|
|
3554
3561
|
});
|
|
3555
3562
|
}
|
|
3556
3563
|
function countTargets(listing) {
|
|
3557
|
-
return listing.lambdas.length + listing.apis.length + listing.ecsServices.length + listing.ecsTaskDefinitions.length + listing.agentCoreRuntimes.length + listing.loadBalancers.length + listing.cloudFrontDistributions.length;
|
|
3564
|
+
return listing.lambdas.length + listing.apis.length + listing.ecsServices.length + listing.ecsTaskDefinitions.length + listing.agentCoreRuntimes.length + listing.loadBalancers.length + listing.cloudFrontDistributions.length + listing.webFunctions.length + listing.microvmImages.length;
|
|
3558
3565
|
}
|
|
3559
3566
|
|
|
3560
3567
|
//#endregion
|
|
@@ -5095,28 +5102,28 @@ function parseEcsTarget(target) {
|
|
|
5095
5102
|
function resolveEcsTaskTarget(target, stacks, context) {
|
|
5096
5103
|
if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
|
|
5097
5104
|
const parsed = parseEcsTarget(target);
|
|
5098
|
-
const stack = pickStack$
|
|
5105
|
+
const stack = pickStack$4(parsed, stacks, target);
|
|
5099
5106
|
const resources = stack.template.Resources ?? {};
|
|
5100
5107
|
let logicalId;
|
|
5101
5108
|
let resource;
|
|
5102
5109
|
if (parsed.isPath) {
|
|
5103
5110
|
const index = buildCdkPathIndex(stack.template);
|
|
5104
5111
|
const taskDefs = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId: l }) => resources[l]?.Type === "AWS::ECS::TaskDefinition");
|
|
5105
|
-
if (taskDefs.length === 0) throw notFoundError$
|
|
5112
|
+
if (taskDefs.length === 0) throw notFoundError$2(target, stack, resources);
|
|
5106
5113
|
if (taskDefs.length > 1) throw new EcsTaskResolutionError(`Target '${target}' matches ${taskDefs.length} task definitions in ${stack.stackName}: ` + taskDefs.map((t) => t.logicalId).join(", ") + ". Refine the path or use the stack:LogicalId form.");
|
|
5107
5114
|
logicalId = taskDefs[0].logicalId;
|
|
5108
5115
|
resource = resources[logicalId];
|
|
5109
5116
|
} else {
|
|
5110
5117
|
resource = resources[parsed.pathOrId];
|
|
5111
|
-
if (!resource) throw notFoundError$
|
|
5118
|
+
if (!resource) throw notFoundError$2(target, stack, resources);
|
|
5112
5119
|
logicalId = parsed.pathOrId;
|
|
5113
5120
|
}
|
|
5114
|
-
if (!logicalId || !resource) throw notFoundError$
|
|
5121
|
+
if (!logicalId || !resource) throw notFoundError$2(target, stack, resources);
|
|
5115
5122
|
if (resource.Type === "AWS::Lambda::Function") throw new EcsTaskResolutionError(`Resource ${displayIdent(logicalId)} in ${stack.stackName} is a Lambda function, not an ECS task definition. Use \`${getEmbedConfig().cliName} invoke\` for Lambda; \`${getEmbedConfig().cliName} run-task\` is ECS only.`);
|
|
5116
5123
|
if (resource.Type !== "AWS::ECS::TaskDefinition") throw new EcsTaskResolutionError(`Resource ${displayIdent(logicalId)} in ${stack.stackName} is ${displayIdent(resource.Type)}, not an AWS::ECS::TaskDefinition.`);
|
|
5117
5124
|
return extractTaskDefinitionProperties(stack, logicalId, resource, context);
|
|
5118
5125
|
}
|
|
5119
|
-
function pickStack$
|
|
5126
|
+
function pickStack$4(parsed, stacks, target) {
|
|
5120
5127
|
const selection = selectTargetStack(parsed, target, stacks);
|
|
5121
5128
|
switch (selection.kind) {
|
|
5122
5129
|
case "found": return selection.stack;
|
|
@@ -5545,7 +5552,7 @@ function pickStringArray$1(value) {
|
|
|
5545
5552
|
for (const v of value) if (typeof v === "string") out.push(v);
|
|
5546
5553
|
return out;
|
|
5547
5554
|
}
|
|
5548
|
-
function notFoundError$
|
|
5555
|
+
function notFoundError$2(target, stack, resources) {
|
|
5549
5556
|
const tasks = [];
|
|
5550
5557
|
for (const [logicalId, resource] of Object.entries(resources)) {
|
|
5551
5558
|
if (resource.Type !== "AWS::ECS::TaskDefinition") continue;
|
|
@@ -6488,7 +6495,7 @@ function pushMap(hash, name, value) {
|
|
|
6488
6495
|
|
|
6489
6496
|
//#endregion
|
|
6490
6497
|
//#region src/local/docker-runner.ts
|
|
6491
|
-
const execFileAsync$
|
|
6498
|
+
const execFileAsync$7 = promisify(execFile);
|
|
6492
6499
|
var DockerRunnerError = class DockerRunnerError extends Error {
|
|
6493
6500
|
constructor(message) {
|
|
6494
6501
|
super(message);
|
|
@@ -6535,7 +6542,7 @@ async function pullImage(image, skipPull, platform) {
|
|
|
6535
6542
|
}
|
|
6536
6543
|
}
|
|
6537
6544
|
async function runDetached(opts) {
|
|
6538
|
-
const args = [
|
|
6545
|
+
const args = opts.autoRemove === false ? ["run", "-d"] : [
|
|
6539
6546
|
"run",
|
|
6540
6547
|
"-d",
|
|
6541
6548
|
"--rm"
|
|
@@ -6550,6 +6557,7 @@ async function runDetached(opts) {
|
|
|
6550
6557
|
const host = opts.host ?? "127.0.0.1";
|
|
6551
6558
|
args.push("-p", `${host}:${opts.hostPort}:${opts.containerPort ?? 8080}`);
|
|
6552
6559
|
if (opts.debugPort !== void 0) args.push("-p", `${host}:${opts.debugPort}:${opts.debugPort}`);
|
|
6560
|
+
for (const extra of opts.extraPorts ?? []) args.push("-p", `${host}:${extra.hostPort}:${extra.containerPort}`);
|
|
6553
6561
|
for (const mount of opts.mounts) {
|
|
6554
6562
|
const ro = mount.readOnly ? ":ro" : "";
|
|
6555
6563
|
args.push("-v", `${mount.hostPath}:${mount.containerPath}${ro}`);
|
|
@@ -6579,7 +6587,7 @@ async function runDetached(opts) {
|
|
|
6579
6587
|
if (refusal !== void 0) throw new DockerRunnerError(refusal);
|
|
6580
6588
|
warnFinchArgvExposure(forwardedKeys);
|
|
6581
6589
|
try {
|
|
6582
|
-
const { stdout } = await execFileAsync$
|
|
6590
|
+
const { stdout } = await execFileAsync$7(getDockerCmd(), args, {
|
|
6583
6591
|
maxBuffer: 10 * 1024 * 1024,
|
|
6584
6592
|
...execEnvForSecrets(passthroughEnv)
|
|
6585
6593
|
});
|
|
@@ -6620,7 +6628,7 @@ function createPrefixedLineRelay(prefix, out) {
|
|
|
6620
6628
|
}
|
|
6621
6629
|
};
|
|
6622
6630
|
}
|
|
6623
|
-
function streamLogs(containerId) {
|
|
6631
|
+
function streamLogs(containerId, opts = {}) {
|
|
6624
6632
|
const proc = spawn(getDockerCmd(), [
|
|
6625
6633
|
"logs",
|
|
6626
6634
|
"-f",
|
|
@@ -6630,9 +6638,14 @@ function streamLogs(containerId) {
|
|
|
6630
6638
|
"pipe",
|
|
6631
6639
|
"pipe"
|
|
6632
6640
|
] });
|
|
6641
|
+
if (opts.onOutput) {
|
|
6642
|
+
const tap = opts.onOutput;
|
|
6643
|
+
proc.stdout?.on("data", (chunk) => tap(chunk));
|
|
6644
|
+
proc.stderr?.on("data", (chunk) => tap(chunk));
|
|
6645
|
+
}
|
|
6633
6646
|
let flush = () => void 0;
|
|
6634
|
-
if (process.env["CDKL_PREFIX_CONTAINER_OUTPUT"] === "1") {
|
|
6635
|
-
const tag = `[${flattenToOneLine(containerId.slice(0, 12)).replace(/[\]\s]/g, "_")}] `;
|
|
6647
|
+
if (opts.prefix !== void 0 || process.env["CDKL_PREFIX_CONTAINER_OUTPUT"] === "1") {
|
|
6648
|
+
const tag = opts.prefix ?? `[${flattenToOneLine(containerId.slice(0, 12)).replace(/[\]\s]/g, "_")}] `;
|
|
6636
6649
|
const stdoutRelay = createPrefixedLineRelay(tag, process.stdout);
|
|
6637
6650
|
const stderrRelay = createPrefixedLineRelay(tag, process.stderr);
|
|
6638
6651
|
proc.stdout?.on("data", (chunk) => stdoutRelay.push(chunk));
|
|
@@ -6656,7 +6669,7 @@ async function removeContainer(containerId) {
|
|
|
6656
6669
|
if (!containerId) return;
|
|
6657
6670
|
const logger = getLogger().child("docker");
|
|
6658
6671
|
try {
|
|
6659
|
-
await execFileAsync$
|
|
6672
|
+
await execFileAsync$7(getDockerCmd(), [
|
|
6660
6673
|
"rm",
|
|
6661
6674
|
"-f",
|
|
6662
6675
|
containerId
|
|
@@ -6670,7 +6683,7 @@ async function removeContainer(containerId) {
|
|
|
6670
6683
|
async function ensureDockerAvailable() {
|
|
6671
6684
|
const cmd = getDockerCmd();
|
|
6672
6685
|
try {
|
|
6673
|
-
await execFileAsync$
|
|
6686
|
+
await execFileAsync$7(cmd, [
|
|
6674
6687
|
"version",
|
|
6675
6688
|
"--format",
|
|
6676
6689
|
"{{.Server.Version}}"
|
|
@@ -7203,14 +7216,14 @@ async function handleConnectionsRequest(opts) {
|
|
|
7203
7216
|
const { req, res, registry } = opts;
|
|
7204
7217
|
const parsed = parseConnectionsPath(req.url ?? "");
|
|
7205
7218
|
if (!parsed) {
|
|
7206
|
-
writeJson(res, 404, { message: "Not Found" });
|
|
7219
|
+
writeJson$1(res, 404, { message: "Not Found" });
|
|
7207
7220
|
return;
|
|
7208
7221
|
}
|
|
7209
7222
|
const { connectionId } = parsed;
|
|
7210
7223
|
const entry = registry.get(connectionId);
|
|
7211
7224
|
const method = (req.method ?? "").toUpperCase();
|
|
7212
7225
|
if (!entry) {
|
|
7213
|
-
writeJson(res, 410, { message: "GoneException" });
|
|
7226
|
+
writeJson$1(res, 410, { message: "GoneException" });
|
|
7214
7227
|
return;
|
|
7215
7228
|
}
|
|
7216
7229
|
if (method === "POST") {
|
|
@@ -7218,17 +7231,17 @@ async function handleConnectionsRequest(opts) {
|
|
|
7218
7231
|
try {
|
|
7219
7232
|
body = await readRequestBody$1(req);
|
|
7220
7233
|
} catch (err) {
|
|
7221
|
-
writeJson(res, 500, { message: `Failed to read request body: ${err instanceof Error ? err.message : String(err)}` });
|
|
7234
|
+
writeJson$1(res, 500, { message: `Failed to read request body: ${err instanceof Error ? err.message : String(err)}` });
|
|
7222
7235
|
return;
|
|
7223
7236
|
}
|
|
7224
7237
|
if (entry.socket.readyState !== entry.socket.OPEN) {
|
|
7225
|
-
writeJson(res, 410, { message: "GoneException" });
|
|
7238
|
+
writeJson$1(res, 410, { message: "GoneException" });
|
|
7226
7239
|
return;
|
|
7227
7240
|
}
|
|
7228
7241
|
try {
|
|
7229
7242
|
entry.socket.send(body);
|
|
7230
7243
|
} catch (err) {
|
|
7231
|
-
writeJson(res, 500, { message: `Failed to deliver to socket: ${err instanceof Error ? err.message : String(err)}` });
|
|
7244
|
+
writeJson$1(res, 500, { message: `Failed to deliver to socket: ${err instanceof Error ? err.message : String(err)}` });
|
|
7232
7245
|
return;
|
|
7233
7246
|
}
|
|
7234
7247
|
res.writeHead(200);
|
|
@@ -7239,7 +7252,7 @@ async function handleConnectionsRequest(opts) {
|
|
|
7239
7252
|
try {
|
|
7240
7253
|
entry.socket.close(1e3, "DeleteConnection");
|
|
7241
7254
|
} catch (err) {
|
|
7242
|
-
writeJson(res, 500, { message: `Failed to close socket: ${err instanceof Error ? err.message : String(err)}` });
|
|
7255
|
+
writeJson$1(res, 500, { message: `Failed to close socket: ${err instanceof Error ? err.message : String(err)}` });
|
|
7243
7256
|
return;
|
|
7244
7257
|
}
|
|
7245
7258
|
res.writeHead(204);
|
|
@@ -7247,7 +7260,7 @@ async function handleConnectionsRequest(opts) {
|
|
|
7247
7260
|
return;
|
|
7248
7261
|
}
|
|
7249
7262
|
if (method === "GET") {
|
|
7250
|
-
writeJson(res, 200, {
|
|
7263
|
+
writeJson$1(res, 200, {
|
|
7251
7264
|
ConnectedAt: new Date(entry.connectedAt).toISOString(),
|
|
7252
7265
|
Identity: { SourceIp: "127.0.0.1" },
|
|
7253
7266
|
LastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -7255,9 +7268,9 @@ async function handleConnectionsRequest(opts) {
|
|
|
7255
7268
|
return;
|
|
7256
7269
|
}
|
|
7257
7270
|
res.setHeader("Allow", "POST, GET, DELETE");
|
|
7258
|
-
writeJson(res, 405, { message: "MethodNotAllowedException" });
|
|
7271
|
+
writeJson$1(res, 405, { message: "MethodNotAllowedException" });
|
|
7259
7272
|
}
|
|
7260
|
-
function writeJson(res, status, body) {
|
|
7273
|
+
function writeJson$1(res, status, body) {
|
|
7261
7274
|
const json = JSON.stringify(body);
|
|
7262
7275
|
res.writeHead(status, {
|
|
7263
7276
|
"Content-Type": "application/json",
|
|
@@ -11039,7 +11052,7 @@ async function startApiServer(opts) {
|
|
|
11039
11052
|
const logger = getLogger().child("start-api");
|
|
11040
11053
|
let currentState = opts.state;
|
|
11041
11054
|
const requestHandler = (req, res) => {
|
|
11042
|
-
handleRequest$
|
|
11055
|
+
handleRequest$3(req, res, currentState, opts).catch((err) => {
|
|
11043
11056
|
logger.error(`Unhandled request error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`);
|
|
11044
11057
|
if (!res.headersSent) writeError$1(res, 502);
|
|
11045
11058
|
});
|
|
@@ -11091,7 +11104,7 @@ async function startApiServer(opts) {
|
|
|
11091
11104
|
getServerState: () => currentState
|
|
11092
11105
|
};
|
|
11093
11106
|
}
|
|
11094
|
-
async function handleRequest$
|
|
11107
|
+
async function handleRequest$3(req, res, state, opts) {
|
|
11095
11108
|
const logger = getLogger().child("start-api");
|
|
11096
11109
|
if (opts.preDispatch) try {
|
|
11097
11110
|
if (await opts.preDispatch(req, res)) return;
|
|
@@ -14095,7 +14108,7 @@ async function resolveLambdaContainerEnvWith(lambda, options, profileCredentials
|
|
|
14095
14108
|
for (const key of audit.resolvedKeys) logger.debug(`${label}: substituted env var ${key}`);
|
|
14096
14109
|
let unresolved = audit.unresolved;
|
|
14097
14110
|
const resolvedKeys = [...audit.resolvedKeys];
|
|
14098
|
-
if (unresolved.length > 0 && stateProvider.resolveDeployedFunctionEnv) {
|
|
14111
|
+
if (unresolved.length > 0 && options.deployedFunctionLookups !== false && stateProvider.resolveDeployedFunctionEnv) {
|
|
14099
14112
|
const physicalId = loaded.resources[lambda.logicalId]?.physicalId;
|
|
14100
14113
|
if (physicalId) {
|
|
14101
14114
|
const deployedEnv = await stateProvider.resolveDeployedFunctionEnv(physicalId);
|
|
@@ -14137,7 +14150,9 @@ async function resolveLambdaContainerEnvWith(lambda, options, profileCredentials
|
|
|
14137
14150
|
});
|
|
14138
14151
|
let resolvedAssumeRoleArn;
|
|
14139
14152
|
try {
|
|
14140
|
-
|
|
14153
|
+
const fromTemplate = options.assumeRole === true && options.deployedFunctionLookups === false && stateForRoleHint ? await resolveTemplateRoleArnFromState(lambda.resource.Properties?.["Role"], stateForRoleHint, lambda.stack.template, () => resolvePseudoParametersForInvoke(lambda.stack.region, options)) : void 0;
|
|
14154
|
+
if (fromTemplate !== void 0) logger.info(`--assume-role: auto-resolved execution role from state: ${flattenToOneLine(fromTemplate)}`);
|
|
14155
|
+
resolvedAssumeRoleArn = fromTemplate ?? await resolveAssumeRoleArnForLambda(options.assumeRole, stateForRoleHint, options.deployedFunctionLookups === false ? void 0 : stateProvider, lambda.logicalId);
|
|
14141
14156
|
} finally {
|
|
14142
14157
|
stateProvider?.dispose();
|
|
14143
14158
|
}
|
|
@@ -14248,6 +14263,28 @@ function resolveExecutionRoleArnFromState(state, logicalId, roleProperty = "Role
|
|
|
14248
14263
|
}
|
|
14249
14264
|
}
|
|
14250
14265
|
}
|
|
14266
|
+
async function resolveTemplateRoleArnFromState(templateRole, state, template, pseudo) {
|
|
14267
|
+
if (isIamRoleArn(templateRole)) return templateRole;
|
|
14268
|
+
if (!templateRole || typeof templateRole !== "object" || Array.isArray(templateRole)) return;
|
|
14269
|
+
const ref = pickReferencedLogicalId(templateRole);
|
|
14270
|
+
if (ref === void 0) return void 0;
|
|
14271
|
+
const roleResource = template.Resources?.[ref];
|
|
14272
|
+
if (roleResource?.Type !== "AWS::IAM::Role") return void 0;
|
|
14273
|
+
const deployed = state.resources[ref];
|
|
14274
|
+
if (!deployed) return void 0;
|
|
14275
|
+
const cached = deployed.attributes?.["Arn"];
|
|
14276
|
+
if (isIamRoleArn(cached)) return cached;
|
|
14277
|
+
const physicalId = deployed.physicalId;
|
|
14278
|
+
if (isIamRoleArn(physicalId)) return physicalId;
|
|
14279
|
+
if (typeof physicalId !== "string" || !/^[\w+=,.@-]{1,64}$/.test(physicalId)) return void 0;
|
|
14280
|
+
const rawPath = roleResource.Properties?.["Path"];
|
|
14281
|
+
if (rawPath !== void 0 && typeof rawPath !== "string") return void 0;
|
|
14282
|
+
const rolePath = rawPath ?? "/";
|
|
14283
|
+
const bag = await pseudo();
|
|
14284
|
+
if (!bag?.accountId) return void 0;
|
|
14285
|
+
const arn = `arn:${bag.partition ?? "aws"}:iam::${bag.accountId}:role${rolePath}${physicalId}`;
|
|
14286
|
+
return isIamRoleArn(arn) ? arn : void 0;
|
|
14287
|
+
}
|
|
14251
14288
|
function pickReferencedLogicalId(intrinsic) {
|
|
14252
14289
|
if ("Ref" in intrinsic && typeof intrinsic["Ref"] === "string") return intrinsic["Ref"];
|
|
14253
14290
|
if ("Fn::GetAtt" in intrinsic) {
|
|
@@ -15996,7 +16033,7 @@ async function runAgentCoreWatchLoop(args) {
|
|
|
15996
16033
|
}
|
|
15997
16034
|
}
|
|
15998
16035
|
}
|
|
15999
|
-
const execFileAsync$
|
|
16036
|
+
const execFileAsync$6 = promisify(execFile);
|
|
16000
16037
|
function describeExecError(err) {
|
|
16001
16038
|
if (!(err instanceof Error)) return String(err);
|
|
16002
16039
|
const stderr = err.stderr;
|
|
@@ -16008,7 +16045,7 @@ async function softReloadAgentContainer(containerId, newAssetSourceDir) {
|
|
|
16008
16045
|
const dockerCmd = getDockerCmd();
|
|
16009
16046
|
let workdir;
|
|
16010
16047
|
try {
|
|
16011
|
-
const { stdout } = await execFileAsync$
|
|
16048
|
+
const { stdout } = await execFileAsync$6(dockerCmd, [
|
|
16012
16049
|
"inspect",
|
|
16013
16050
|
"--format",
|
|
16014
16051
|
"{{.Config.WorkingDir}}",
|
|
@@ -16021,7 +16058,7 @@ async function softReloadAgentContainer(containerId, newAssetSourceDir) {
|
|
|
16021
16058
|
const workdirDest = workdir.endsWith("/") ? workdir : `${workdir}/`;
|
|
16022
16059
|
logger.info(`Soft-reload: docker cp ${flattenToOneLine(newAssetSourceDir)} -> ${containerId}:${workdirDest}; restart.`);
|
|
16023
16060
|
try {
|
|
16024
|
-
await execFileAsync$
|
|
16061
|
+
await execFileAsync$6(dockerCmd, [
|
|
16025
16062
|
"cp",
|
|
16026
16063
|
`${newAssetSourceDir}/.`,
|
|
16027
16064
|
`${containerId}:${workdirDest}`
|
|
@@ -16030,7 +16067,7 @@ async function softReloadAgentContainer(containerId, newAssetSourceDir) {
|
|
|
16030
16067
|
throw new CdkLocalError(`softReloadAgentContainer: docker cp into '${containerId}:${workdir}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_CP_FAILED");
|
|
16031
16068
|
}
|
|
16032
16069
|
try {
|
|
16033
|
-
await execFileAsync$
|
|
16070
|
+
await execFileAsync$6(dockerCmd, ["restart", containerId]);
|
|
16034
16071
|
} catch (err) {
|
|
16035
16072
|
throw new CdkLocalError(`softReloadAgentContainer: docker restart of '${containerId}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_RESTART_FAILED");
|
|
16036
16073
|
}
|
|
@@ -16206,7 +16243,7 @@ function writePrefixedLines(prefix, buffer, out) {
|
|
|
16206
16243
|
|
|
16207
16244
|
//#endregion
|
|
16208
16245
|
//#region src/local/ecs-network.ts
|
|
16209
|
-
const execFileAsync$
|
|
16246
|
+
const execFileAsync$5 = promisify(execFile);
|
|
16210
16247
|
const METADATA_ENDPOINT_IMAGE = "amazon/amazon-ecs-local-container-endpoints:latest-amd64";
|
|
16211
16248
|
const METADATA_ENDPOINT_IP = "169.254.170.2";
|
|
16212
16249
|
const DEFAULT_METADATA_ENDPOINT_SUBNET = "169.254.170.0/24";
|
|
@@ -16242,7 +16279,7 @@ async function sweepOrphanedSvcNetworks(prefix) {
|
|
|
16242
16279
|
const filter = `${prefix}-svc-`;
|
|
16243
16280
|
let names;
|
|
16244
16281
|
try {
|
|
16245
|
-
const { stdout } = await execFileAsync$
|
|
16282
|
+
const { stdout } = await execFileAsync$5(getDockerCmd(), [
|
|
16246
16283
|
"network",
|
|
16247
16284
|
"ls",
|
|
16248
16285
|
"--filter",
|
|
@@ -16260,7 +16297,7 @@ async function sweepOrphanedSvcNetworks(prefix) {
|
|
|
16260
16297
|
for (const name of names) {
|
|
16261
16298
|
let attached;
|
|
16262
16299
|
try {
|
|
16263
|
-
const { stdout } = await execFileAsync$
|
|
16300
|
+
const { stdout } = await execFileAsync$5(getDockerCmd(), [
|
|
16264
16301
|
"network",
|
|
16265
16302
|
"inspect",
|
|
16266
16303
|
name,
|
|
@@ -16288,7 +16325,7 @@ async function createNetworkAndSidecar(args) {
|
|
|
16288
16325
|
await pullImage(METADATA_ENDPOINT_IMAGE, skipPull);
|
|
16289
16326
|
logger.info(`Creating docker network ${networkName} (subnet ${cidr})...`);
|
|
16290
16327
|
try {
|
|
16291
|
-
await execFileAsync$
|
|
16328
|
+
await execFileAsync$5(getDockerCmd(), [
|
|
16292
16329
|
"network",
|
|
16293
16330
|
"create",
|
|
16294
16331
|
"--driver",
|
|
@@ -16325,7 +16362,7 @@ async function createNetworkAndSidecar(args) {
|
|
|
16325
16362
|
warnFinchArgvExposure(Object.keys(sidecarPassthroughEnv));
|
|
16326
16363
|
logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
|
|
16327
16364
|
try {
|
|
16328
|
-
const { stdout } = await execFileAsync$
|
|
16365
|
+
const { stdout } = await execFileAsync$5(getDockerCmd(), sidecarArgs, {
|
|
16329
16366
|
maxBuffer: 10 * 1024 * 1024,
|
|
16330
16367
|
...execEnvForSecrets(sidecarPassthroughEnv)
|
|
16331
16368
|
});
|
|
@@ -16373,7 +16410,7 @@ async function destroyNetworkOnly(networkName) {
|
|
|
16373
16410
|
if (!networkName) return;
|
|
16374
16411
|
const logger = getLogger().child("ecs-network");
|
|
16375
16412
|
try {
|
|
16376
|
-
await execFileAsync$
|
|
16413
|
+
await execFileAsync$5(getDockerCmd(), [
|
|
16377
16414
|
"network",
|
|
16378
16415
|
"rm",
|
|
16379
16416
|
networkName
|
|
@@ -16487,7 +16524,7 @@ async function resolveSsm(entry, shape, client) {
|
|
|
16487
16524
|
|
|
16488
16525
|
//#endregion
|
|
16489
16526
|
//#region src/local/ecs-task-runner.ts
|
|
16490
|
-
const execFileAsync$
|
|
16527
|
+
const execFileAsync$4 = promisify(execFile);
|
|
16491
16528
|
var EcsTaskRunnerError = class EcsTaskRunnerError extends Error {
|
|
16492
16529
|
constructor(message) {
|
|
16493
16530
|
super(message);
|
|
@@ -16530,7 +16567,7 @@ async function cleanupEcsRun(state, options) {
|
|
|
16530
16567
|
if (state.network && !state.network.ownedByCaller) await destroyTaskNetwork(state.network);
|
|
16531
16568
|
state.network = void 0;
|
|
16532
16569
|
for (const v of state.dockerVolumeNames) try {
|
|
16533
|
-
await execFileAsync$
|
|
16570
|
+
await execFileAsync$4(getDockerCmd(), [
|
|
16534
16571
|
"volume",
|
|
16535
16572
|
"rm",
|
|
16536
16573
|
v
|
|
@@ -16681,7 +16718,7 @@ async function runEcsTask(task, options, state) {
|
|
|
16681
16718
|
let id;
|
|
16682
16719
|
warnFinchArgvExposure(Object.keys(sensitiveEnv));
|
|
16683
16720
|
try {
|
|
16684
|
-
const { stdout } = await execFileAsync$
|
|
16721
|
+
const { stdout } = await execFileAsync$4(getDockerCmd(), args, {
|
|
16685
16722
|
maxBuffer: 10 * 1024 * 1024,
|
|
16686
16723
|
...execEnvForSecrets(sensitiveEnv)
|
|
16687
16724
|
});
|
|
@@ -16773,7 +16810,7 @@ async function waitForContainerHealthy(containerId, displayName) {
|
|
|
16773
16810
|
let lastStatus = "";
|
|
16774
16811
|
while (Date.now() < deadline) {
|
|
16775
16812
|
try {
|
|
16776
|
-
const { stdout } = await execFileAsync$
|
|
16813
|
+
const { stdout } = await execFileAsync$4(getDockerCmd(), [
|
|
16777
16814
|
"inspect",
|
|
16778
16815
|
"--format",
|
|
16779
16816
|
"{{.State.Health.Status}}",
|
|
@@ -16796,7 +16833,7 @@ async function waitForContainerHealthy(containerId, displayName) {
|
|
|
16796
16833
|
}
|
|
16797
16834
|
async function waitForContainerExit(containerId) {
|
|
16798
16835
|
try {
|
|
16799
|
-
const { stdout } = await execFileAsync$
|
|
16836
|
+
const { stdout } = await execFileAsync$4(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
|
|
16800
16837
|
const code = Number.parseInt(stdout.trim(), 10);
|
|
16801
16838
|
return Number.isFinite(code) ? code : 1;
|
|
16802
16839
|
} catch (err) {
|
|
@@ -16806,7 +16843,7 @@ async function waitForContainerExit(containerId) {
|
|
|
16806
16843
|
}
|
|
16807
16844
|
async function stopContainer(containerId, graceSeconds) {
|
|
16808
16845
|
try {
|
|
16809
|
-
await execFileAsync$
|
|
16846
|
+
await execFileAsync$4(getDockerCmd(), [
|
|
16810
16847
|
"stop",
|
|
16811
16848
|
"-t",
|
|
16812
16849
|
String(graceSeconds),
|
|
@@ -16901,7 +16938,7 @@ async function realizeDockerVolumes(volumes, state) {
|
|
|
16901
16938
|
const dockerVolumeName = `${getEmbedConfig().resourceNamePrefix}-${v.name}-${randHex(4)}`;
|
|
16902
16939
|
args.push(dockerVolumeName);
|
|
16903
16940
|
try {
|
|
16904
|
-
await execFileAsync$
|
|
16941
|
+
await execFileAsync$4(getDockerCmd(), args);
|
|
16905
16942
|
state.dockerVolumeNames.push(dockerVolumeName);
|
|
16906
16943
|
logger.debug(`Created docker volume ${displayIdent(dockerVolumeName)} for task volume ${displayIdent(v.name)}`);
|
|
16907
16944
|
} catch (err) {
|
|
@@ -17095,23 +17132,23 @@ function shellJoin(parts) {
|
|
|
17095
17132
|
function resolveEcsServiceTarget(target, stacks, context, options) {
|
|
17096
17133
|
if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
|
|
17097
17134
|
const parsed = parseEcsTarget(target);
|
|
17098
|
-
const stack = pickStack$
|
|
17135
|
+
const stack = pickStack$3(parsed, stacks, target);
|
|
17099
17136
|
const resources = stack.template.Resources ?? {};
|
|
17100
17137
|
let serviceLogicalId;
|
|
17101
17138
|
let serviceResource;
|
|
17102
17139
|
if (parsed.isPath) {
|
|
17103
17140
|
const index = buildCdkPathIndex(stack.template);
|
|
17104
17141
|
const services = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId: l }) => resources[l]?.Type === "AWS::ECS::Service");
|
|
17105
|
-
if (services.length === 0) throw notFoundError(target, stack, resources, parsed);
|
|
17142
|
+
if (services.length === 0) throw notFoundError$1(target, stack, resources, parsed);
|
|
17106
17143
|
if (services.length > 1) throw new EcsTaskResolutionError(`Target '${target}' matches ${services.length} ECS services in ${stack.stackName}: ` + services.map((s) => s.logicalId).join(", ") + ". Refine the path or use the stack:LogicalId form.");
|
|
17107
17144
|
serviceLogicalId = services[0].logicalId;
|
|
17108
17145
|
serviceResource = resources[serviceLogicalId];
|
|
17109
17146
|
} else {
|
|
17110
17147
|
serviceResource = resources[parsed.pathOrId];
|
|
17111
|
-
if (!serviceResource) throw notFoundError(target, stack, resources, parsed);
|
|
17148
|
+
if (!serviceResource) throw notFoundError$1(target, stack, resources, parsed);
|
|
17112
17149
|
serviceLogicalId = parsed.pathOrId;
|
|
17113
17150
|
}
|
|
17114
|
-
if (!serviceLogicalId || !serviceResource) throw notFoundError(target, stack, resources, parsed);
|
|
17151
|
+
if (!serviceLogicalId || !serviceResource) throw notFoundError$1(target, stack, resources, parsed);
|
|
17115
17152
|
if (serviceResource.Type === "AWS::ECS::TaskDefinition") throw new EcsTaskResolutionError(`Resource ${displayIdent(serviceLogicalId)} in ${stack.stackName} is an ECS TaskDefinition, not a Service. Use \`${getEmbedConfig().cliName} run-task\` for one-shot tasks; \`${getEmbedConfig().cliName} start-service\` is Service-only.`);
|
|
17116
17153
|
if (serviceResource.Type !== "AWS::ECS::Service") throw new EcsTaskResolutionError(`Resource ${displayIdent(serviceLogicalId)} in ${stack.stackName} is ${displayIdent(serviceResource.Type)}, not an AWS::ECS::Service.`);
|
|
17117
17154
|
return extractServiceProperties(stack, serviceLogicalId, serviceResource, stacks, context, options);
|
|
@@ -17267,7 +17304,7 @@ function deriveServiceDisplayName(rawServiceName, serviceLogicalId, metadata) {
|
|
|
17267
17304
|
}
|
|
17268
17305
|
return serviceLogicalId;
|
|
17269
17306
|
}
|
|
17270
|
-
function pickStack$
|
|
17307
|
+
function pickStack$3(parsed, stacks, target) {
|
|
17271
17308
|
const selection = selectTargetStack(parsed, target, stacks);
|
|
17272
17309
|
switch (selection.kind) {
|
|
17273
17310
|
case "found": return selection.stack;
|
|
@@ -17276,7 +17313,7 @@ function pickStack$2(parsed, stacks, target) {
|
|
|
17276
17313
|
case "ambiguous": throw new EcsTaskResolutionError(`Multiple stacks match '${selection.pattern}': ${describeStacks(selection.matched)}. Refine the pattern, or use the stack's display path (path form).`);
|
|
17277
17314
|
}
|
|
17278
17315
|
}
|
|
17279
|
-
function notFoundError(target, stack, resources, parsed) {
|
|
17316
|
+
function notFoundError$1(target, stack, resources, parsed) {
|
|
17280
17317
|
const services = [];
|
|
17281
17318
|
for (const [logicalId, r] of Object.entries(resources)) {
|
|
17282
17319
|
if (r.Type !== "AWS::ECS::Service") continue;
|
|
@@ -17301,11 +17338,11 @@ function notFoundError(target, stack, resources, parsed) {
|
|
|
17301
17338
|
|
|
17302
17339
|
//#endregion
|
|
17303
17340
|
//#region src/local/docker-inspect.ts
|
|
17304
|
-
const execFileAsync$
|
|
17341
|
+
const execFileAsync$3 = promisify(execFile);
|
|
17305
17342
|
async function getContainerNetworkIp(containerId, networkName) {
|
|
17306
17343
|
const format = `{{with index .NetworkSettings.Networks "${networkName}"}}{{.IPAddress}}{{end}}`;
|
|
17307
17344
|
try {
|
|
17308
|
-
const { stdout } = await execFileAsync$
|
|
17345
|
+
const { stdout } = await execFileAsync$3(getDockerCmd(), [
|
|
17309
17346
|
"inspect",
|
|
17310
17347
|
"--format",
|
|
17311
17348
|
format,
|
|
@@ -17321,7 +17358,7 @@ async function getContainerNetworkIp(containerId, networkName) {
|
|
|
17321
17358
|
async function getPublishedHostPort(containerId, containerPort, protocol = "tcp") {
|
|
17322
17359
|
const format = `{{with index .NetworkSettings.Ports "${`${containerPort}/${protocol}`}"}}{{with index . 0}}{{.HostPort}}{{end}}{{end}}`;
|
|
17323
17360
|
try {
|
|
17324
|
-
const { stdout } = await execFileAsync$
|
|
17361
|
+
const { stdout } = await execFileAsync$3(getDockerCmd(), [
|
|
17325
17362
|
"inspect",
|
|
17326
17363
|
"--format",
|
|
17327
17364
|
format,
|
|
@@ -18619,7 +18656,7 @@ function buildHeaderMaps(headers) {
|
|
|
18619
18656
|
multi
|
|
18620
18657
|
};
|
|
18621
18658
|
}
|
|
18622
|
-
function firstHeader(headers, name) {
|
|
18659
|
+
function firstHeader$1(headers, name) {
|
|
18623
18660
|
const lower = name.toLowerCase();
|
|
18624
18661
|
for (const [k, v] of Object.entries(headers)) if (k.toLowerCase() === lower) return v[0];
|
|
18625
18662
|
}
|
|
@@ -18627,8 +18664,8 @@ function buildAlbLambdaEvent(req, opts) {
|
|
|
18627
18664
|
const { path, rawQuery } = splitRawUrl(req.rawUrl);
|
|
18628
18665
|
const query = parseQuery(rawQuery);
|
|
18629
18666
|
const headerMaps = buildHeaderMaps(req.headers);
|
|
18630
|
-
const contentEncoding = firstHeader(req.headers, "content-encoding");
|
|
18631
|
-
const contentType = firstHeader(req.headers, "content-type") ?? "";
|
|
18667
|
+
const contentEncoding = firstHeader$1(req.headers, "content-encoding");
|
|
18668
|
+
const contentType = firstHeader$1(req.headers, "content-type") ?? "";
|
|
18632
18669
|
const isBase64Encoded = req.body.length > 0 && (contentEncoding !== void 0 ? true : !isTextualContentType(contentType));
|
|
18633
18670
|
const body = isBase64Encoded ? req.body.toString("base64") : req.body.toString("utf-8");
|
|
18634
18671
|
const event = {
|
|
@@ -18731,7 +18768,7 @@ async function startFrontDoorServer(opts) {
|
|
|
18731
18768
|
const scheme = opts.tls ? "https" : "http";
|
|
18732
18769
|
server.on("connection", (socket) => socket.setNoDelay(true));
|
|
18733
18770
|
server.on("upgrade", (req, clientSocket, head) => {
|
|
18734
|
-
handleUpgrade(req, clientSocket, head, effectiveOpts).catch((err) => {
|
|
18771
|
+
handleUpgrade$1(req, clientSocket, head, effectiveOpts).catch((err) => {
|
|
18735
18772
|
logger.debug(`front-door upgrade error: ${err instanceof Error ? err.message : String(err)}`);
|
|
18736
18773
|
if (!clientSocket.destroyed) clientSocket.destroy();
|
|
18737
18774
|
});
|
|
@@ -19075,7 +19112,7 @@ function writeError(res, statusCode, message) {
|
|
|
19075
19112
|
res.writeHead(statusCode, { "content-type": "text/plain; charset=utf-8" });
|
|
19076
19113
|
res.end(`${message}\n`);
|
|
19077
19114
|
}
|
|
19078
|
-
function handleUpgrade(req, clientSocket, head, opts) {
|
|
19115
|
+
function handleUpgrade$1(req, clientSocket, head, opts) {
|
|
19079
19116
|
clientSocket.setNoDelay?.(true);
|
|
19080
19117
|
if (!opts.route) {
|
|
19081
19118
|
writeRawHttpError(clientSocket, 404, "Not Found");
|
|
@@ -19262,7 +19299,9 @@ function sanitizeRawHeaderValue(value) {
|
|
|
19262
19299
|
const STATUS_TEXT$1 = {
|
|
19263
19300
|
301: "Moved Permanently",
|
|
19264
19301
|
302: "Found",
|
|
19302
|
+
400: "Bad Request",
|
|
19265
19303
|
401: "Unauthorized",
|
|
19304
|
+
403: "Forbidden",
|
|
19266
19305
|
404: "Not Found",
|
|
19267
19306
|
502: "Bad Gateway",
|
|
19268
19307
|
503: "Service Unavailable"
|
|
@@ -19516,7 +19555,7 @@ function matchBitPrefix(a, b, prefixLength) {
|
|
|
19516
19555
|
|
|
19517
19556
|
//#endregion
|
|
19518
19557
|
//#region src/local/front-door-tls.ts
|
|
19519
|
-
const execFileAsync = promisify(execFile);
|
|
19558
|
+
const execFileAsync$2 = promisify(execFile);
|
|
19520
19559
|
async function resolveFrontDoorTlsMaterials(opts) {
|
|
19521
19560
|
const hasCert = opts.certPath !== void 0 && opts.certPath !== "";
|
|
19522
19561
|
const hasKey = opts.keyPath !== void 0 && opts.keyPath !== "";
|
|
@@ -19606,7 +19645,7 @@ function readCertNotAfter(certPath) {
|
|
|
19606
19645
|
}
|
|
19607
19646
|
}
|
|
19608
19647
|
async function runOpenssl(args) {
|
|
19609
|
-
await execFileAsync("openssl", args, { timeout: 3e4 });
|
|
19648
|
+
await execFileAsync$2("openssl", args, { timeout: 3e4 });
|
|
19610
19649
|
}
|
|
19611
19650
|
function readPemOrThrow(path, flagName) {
|
|
19612
19651
|
try {
|
|
@@ -21809,7 +21848,7 @@ function resolveAlbFrontDoor(stack, albLogicalId) {
|
|
|
21809
21848
|
if (resource.Type !== LISTENER_TYPE) continue;
|
|
21810
21849
|
const props = resource.Properties ?? {};
|
|
21811
21850
|
if (refOf(props["LoadBalancerArn"]) !== albLogicalId) continue;
|
|
21812
|
-
const port = parsePort$
|
|
21851
|
+
const port = parsePort$3(props["Port"]);
|
|
21813
21852
|
if (port === void 0) continue;
|
|
21814
21853
|
const protocol = typeof props["Protocol"] === "string" ? props["Protocol"] : "HTTP";
|
|
21815
21854
|
if (protocol !== "HTTP" && protocol !== "HTTPS") {
|
|
@@ -22174,7 +22213,7 @@ function refOf(raw) {
|
|
|
22174
22213
|
if (typeof ref === "string" && ref.length > 0) return ref;
|
|
22175
22214
|
}
|
|
22176
22215
|
}
|
|
22177
|
-
function parsePort$
|
|
22216
|
+
function parsePort$3(raw) {
|
|
22178
22217
|
if (typeof raw === "number" && Number.isInteger(raw) && raw >= 1 && raw <= 65535) return raw;
|
|
22179
22218
|
if (typeof raw === "string" && /^\d+$/.test(raw)) {
|
|
22180
22219
|
const n = parseInt(raw, 10);
|
|
@@ -22182,7 +22221,7 @@ function parsePort$1(raw) {
|
|
|
22182
22221
|
}
|
|
22183
22222
|
}
|
|
22184
22223
|
function parseContainerPort(raw) {
|
|
22185
|
-
return parsePort$
|
|
22224
|
+
return parsePort$3(raw);
|
|
22186
22225
|
}
|
|
22187
22226
|
function parseWeight(raw) {
|
|
22188
22227
|
if (typeof raw === "number" && Number.isFinite(raw)) return raw < 0 ? 0 : raw;
|
|
@@ -22276,7 +22315,7 @@ function parseLbPortOverrides(values) {
|
|
|
22276
22315
|
function resolveAlbTarget(target, stacks) {
|
|
22277
22316
|
if (stacks.length === 0) throw new LocalStartServiceError("No stacks found in the synthesized assembly.");
|
|
22278
22317
|
const parsed = parseEcsTarget(target);
|
|
22279
|
-
const stack = pickStack$
|
|
22318
|
+
const stack = pickStack$2(parsed, stacks, target);
|
|
22280
22319
|
const resources = stack.template.Resources ?? {};
|
|
22281
22320
|
if (parsed.isPath) {
|
|
22282
22321
|
const index = buildCdkPathIndex(stack.template);
|
|
@@ -22298,7 +22337,7 @@ function resolveAlbTarget(target, stacks) {
|
|
|
22298
22337
|
albLogicalId: parsed.pathOrId
|
|
22299
22338
|
};
|
|
22300
22339
|
}
|
|
22301
|
-
function pickStack$
|
|
22340
|
+
function pickStack$2(parsed, stacks, target) {
|
|
22302
22341
|
const selection = selectTargetStack(parsed, target, stacks);
|
|
22303
22342
|
switch (selection.kind) {
|
|
22304
22343
|
case "found": return selection.stack;
|
|
@@ -23520,7 +23559,7 @@ async function startCloudFrontServer(options) {
|
|
|
23520
23559
|
const s3OriginReaders = options.s3OriginReaders ?? /* @__PURE__ */ new Map();
|
|
23521
23560
|
const state = { distribution: options.distribution };
|
|
23522
23561
|
const handler = (req, res) => {
|
|
23523
|
-
handleRequest$
|
|
23562
|
+
handleRequest$2(req, res, state, lambdaInvokers, edgeInvokers, s3OriginReaders, logger).catch((err) => {
|
|
23524
23563
|
logger.warn(`Request handling failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
23525
23564
|
if (!res.headersSent) {
|
|
23526
23565
|
res.statusCode = 500;
|
|
@@ -23549,7 +23588,7 @@ async function startCloudFrontServer(options) {
|
|
|
23549
23588
|
}
|
|
23550
23589
|
};
|
|
23551
23590
|
}
|
|
23552
|
-
async function handleRequest$
|
|
23591
|
+
async function handleRequest$2(req, res, state, lambdaInvokers, edgeInvokers, s3OriginReaders, logger) {
|
|
23553
23592
|
const distribution = state.distribution;
|
|
23554
23593
|
const rawUrl = req.url ?? "/";
|
|
23555
23594
|
const queryIdx = rawUrl.indexOf("?");
|
|
@@ -24213,7 +24252,7 @@ async function attachKvsModules(distribution, stacks, options, profileCredential
|
|
|
24213
24252
|
function resolveCloudFrontTarget(target, stacks) {
|
|
24214
24253
|
if (stacks.length === 0) throw new LocalStartCloudFrontError("No stacks found in the synthesized assembly.");
|
|
24215
24254
|
const parsed = parseEcsTarget(target);
|
|
24216
|
-
const stack = pickStack(parsed, stacks, target);
|
|
24255
|
+
const stack = pickStack$1(parsed, stacks, target);
|
|
24217
24256
|
const resources = stack.template.Resources ?? {};
|
|
24218
24257
|
if (parsed.isPath) {
|
|
24219
24258
|
const index = buildCdkPathIndex(stack.template);
|
|
@@ -24235,7 +24274,7 @@ function resolveCloudFrontTarget(target, stacks) {
|
|
|
24235
24274
|
logicalId: parsed.pathOrId
|
|
24236
24275
|
};
|
|
24237
24276
|
}
|
|
24238
|
-
function pickStack(parsed, stacks, target) {
|
|
24277
|
+
function pickStack$1(parsed, stacks, target) {
|
|
24239
24278
|
const selection = selectTargetStack(parsed, target, stacks);
|
|
24240
24279
|
switch (selection.kind) {
|
|
24241
24280
|
case "found": return selection.stack;
|
|
@@ -24858,7 +24897,7 @@ function readAuthorizationHeader(headers) {
|
|
|
24858
24897
|
|
|
24859
24898
|
//#endregion
|
|
24860
24899
|
//#region src/cli/commands/local-start-agentcore.ts
|
|
24861
|
-
function parsePort(raw) {
|
|
24900
|
+
function parsePort$2(raw) {
|
|
24862
24901
|
const n = Number(raw);
|
|
24863
24902
|
if (!Number.isInteger(n) || n < 0 || n > 65535) throw new InvalidArgumentError(`--port must be an integer 0-65535, got '${raw}'.`);
|
|
24864
24903
|
return n;
|
|
@@ -25186,7 +25225,1949 @@ function createLocalStartAgentCoreCommand(opts = {}) {
|
|
|
25186
25225
|
return cmd;
|
|
25187
25226
|
}
|
|
25188
25227
|
function addStartAgentCoreSpecificOptions(cmd) {
|
|
25189
|
-
return cmd.addOption(new Option("--port <n>", "Serve bind port the client connects to (HTTP contract + /ws on the same port). Default 0 (OS-assigned).").default(0).argParser(parsePort)).addOption(new Option("--host <ip>", "Serve bind host. Default 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--session-id <id>", "Pin one AgentCore runtime session id header value for every forwarded request / /ws connection (default: a fresh random UUID each, so each is its own session).")).addOption(new Option("--bearer-token <jwt>", "Bearer JWT to present when the runtime declares a customJwtAuthorizer. Verified against the runtime's OIDC discovery URL before the container starts, then injected as Authorization: Bearer <jwt> on every forwarded request and the container /ws upgrade.")).addOption(new Option("--no-verify-auth", "Skip inbound JWT verification even when the runtime declares a customJwtAuthorizer (local-dev escape hatch). A --bearer-token, if given, is still forwarded.")).addOption(new Option("--sigv4", "Sign each forwarded request with AWS SigV4 (service bedrock-agentcore) when the runtime declares NO customJwtAuthorizer, so the warm container sees the same Authorization / X-Amz-* headers the cloud receives. Mutually exclusive with --bearer-token; ignored (with a warning) when a customJwtAuthorizer is declared.")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64). Defaults to linux/arm64 because the cloud AgentCore Runtime requires arm64.").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).addOption(new Option("--watch", "Re-synth + reload the warm container in place on a CDK source change, keeping the serve up. A per-firing classifier picks rebuild (boot a fresh container, re-point the serve) vs soft-reload (docker cp + docker restart the same container) — the same pattern as start-service / invoke-agentcore --ws --watch.")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for the local-build path")).addOption(new Option("--no-build", "Skip docker build on the local-asset path (use the previously-built tag). No-op for the ECR / registry pull paths.")).addOption(new Option("--container-host <host>", "Host IP used to bind the agent container port. Must be a numeric IP. Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Maximum time in milliseconds to wait for the container to become ready (the GET /ping readiness deadline). Default 120000.").default(12e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the runtime's execution role and forward STS-issued temp credentials to the container. (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's literal RoleArn; (3) `--no-assume-role` opts out. Off by default.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack and substitute Ref / Fn::ImportValue in env vars / image URIs with the deployed physical IDs / exports. Bare form uses the resolved stack name.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
|
|
25228
|
+
return cmd.addOption(new Option("--port <n>", "Serve bind port the client connects to (HTTP contract + /ws on the same port). Default 0 (OS-assigned).").default(0).argParser(parsePort$2)).addOption(new Option("--host <ip>", "Serve bind host. Default 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--session-id <id>", "Pin one AgentCore runtime session id header value for every forwarded request / /ws connection (default: a fresh random UUID each, so each is its own session).")).addOption(new Option("--bearer-token <jwt>", "Bearer JWT to present when the runtime declares a customJwtAuthorizer. Verified against the runtime's OIDC discovery URL before the container starts, then injected as Authorization: Bearer <jwt> on every forwarded request and the container /ws upgrade.")).addOption(new Option("--no-verify-auth", "Skip inbound JWT verification even when the runtime declares a customJwtAuthorizer (local-dev escape hatch). A --bearer-token, if given, is still forwarded.")).addOption(new Option("--sigv4", "Sign each forwarded request with AWS SigV4 (service bedrock-agentcore) when the runtime declares NO customJwtAuthorizer, so the warm container sees the same Authorization / X-Amz-* headers the cloud receives. Mutually exclusive with --bearer-token; ignored (with a warning) when a customJwtAuthorizer is declared.")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64). Defaults to linux/arm64 because the cloud AgentCore Runtime requires arm64.").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).addOption(new Option("--watch", "Re-synth + reload the warm container in place on a CDK source change, keeping the serve up. A per-firing classifier picks rebuild (boot a fresh container, re-point the serve) vs soft-reload (docker cp + docker restart the same container) — the same pattern as start-service / invoke-agentcore --ws --watch.")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for the local-build path")).addOption(new Option("--no-build", "Skip docker build on the local-asset path (use the previously-built tag). No-op for the ECR / registry pull paths.")).addOption(new Option("--container-host <host>", "Host IP used to bind the agent container port. Must be a numeric IP. Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Maximum time in milliseconds to wait for the container to become ready (the GET /ping readiness deadline). Default 120000.").default(12e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the runtime's execution role and forward STS-issued temp credentials to the container. (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's literal RoleArn; (3) `--no-assume-role` opts out. Off by default.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack and substitute Ref / Fn::ImportValue in env vars / image URIs with the deployed physical IDs / exports. Bare form uses the resolved stack name.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
|
|
25229
|
+
}
|
|
25230
|
+
|
|
25231
|
+
//#endregion
|
|
25232
|
+
//#region src/local/web-function-resolver.ts
|
|
25233
|
+
const WEB_FUNCTION_TYPE = "AWS::Lambda::WebFunction";
|
|
25234
|
+
const WEB_FUNCTION_REVISION_TYPE = "AWS::Lambda::WebFunctionRevision";
|
|
25235
|
+
const WEB_FUNCTION_ENDPOINT_TYPE = "AWS::Lambda::WebFunctionEndpoint";
|
|
25236
|
+
const WEB_TYPES = [
|
|
25237
|
+
WEB_FUNCTION_ENDPOINT_TYPE,
|
|
25238
|
+
WEB_FUNCTION_TYPE,
|
|
25239
|
+
WEB_FUNCTION_REVISION_TYPE
|
|
25240
|
+
];
|
|
25241
|
+
var WebFunctionResolutionError = class WebFunctionResolutionError extends Error {
|
|
25242
|
+
constructor(message) {
|
|
25243
|
+
super(message);
|
|
25244
|
+
this.name = "WebFunctionResolutionError";
|
|
25245
|
+
Object.setPrototypeOf(this, WebFunctionResolutionError.prototype);
|
|
25246
|
+
}
|
|
25247
|
+
};
|
|
25248
|
+
function resolveWebFunctionTarget(target, stacks, revisionFlag) {
|
|
25249
|
+
if (stacks.length === 0) throw new WebFunctionResolutionError("No stacks found in the synthesized assembly.");
|
|
25250
|
+
const parsed = parseTarget(target);
|
|
25251
|
+
const stack = pickStack(parsed, stacks, target);
|
|
25252
|
+
const resources = stack.template.Resources ?? {};
|
|
25253
|
+
const { logicalId, resource } = matchWebResource(parsed, target, stack, resources);
|
|
25254
|
+
const functions = logicalIdsOfType(resources, WEB_FUNCTION_TYPE);
|
|
25255
|
+
const linked = (value) => linkedFunctionLogicalId(value, resources, functions);
|
|
25256
|
+
let functionLogicalId;
|
|
25257
|
+
let endpoint;
|
|
25258
|
+
let revisionLogicalId;
|
|
25259
|
+
let selection;
|
|
25260
|
+
if (resource.Type === "AWS::Lambda::WebFunctionRevision") {
|
|
25261
|
+
functionLogicalId = linked(resource.Properties?.["FunctionName"]);
|
|
25262
|
+
revisionLogicalId = logicalId;
|
|
25263
|
+
selection = "target-revision";
|
|
25264
|
+
} else if (resource.Type === "AWS::Lambda::WebFunctionEndpoint") {
|
|
25265
|
+
functionLogicalId = linked(resource.Properties?.["FunctionName"]);
|
|
25266
|
+
endpoint = readEndpoint(logicalId, resource, resources);
|
|
25267
|
+
} else functionLogicalId = logicalId;
|
|
25268
|
+
const revisionsOfFunction = () => functionLogicalId === void 0 ? [] : logicalIdsOfType(resources, WEB_FUNCTION_REVISION_TYPE).filter((id) => linked(resources[id]?.Properties?.["FunctionName"]) === functionLogicalId);
|
|
25269
|
+
const endpointsOfFunction = () => functionLogicalId === void 0 ? [] : logicalIdsOfType(resources, WEB_FUNCTION_ENDPOINT_TYPE).filter((id) => linked(resources[id]?.Properties?.["FunctionName"]) === functionLogicalId);
|
|
25270
|
+
if (revisionFlag !== void 0) {
|
|
25271
|
+
const flagged = resources[revisionFlag];
|
|
25272
|
+
if (!flagged || flagged.Type !== "AWS::Lambda::WebFunctionRevision") throw new WebFunctionResolutionError(`--revision ${displayIdent(revisionFlag)} is not an ${WEB_FUNCTION_REVISION_TYPE} logical id in ${displayStackName(stack.stackName)}. ${describeRevisions(revisionsOfFunction(), stack)}`);
|
|
25273
|
+
const owner = linked(flagged.Properties?.["FunctionName"]);
|
|
25274
|
+
if (functionLogicalId !== void 0 && owner !== functionLogicalId) throw new WebFunctionResolutionError(`--revision ${displayIdent(revisionFlag)} belongs to a different web function than ${displayIdent(logicalId)}. ${describeRevisions(revisionsOfFunction(), stack)}`);
|
|
25275
|
+
functionLogicalId ??= owner;
|
|
25276
|
+
revisionLogicalId = revisionFlag;
|
|
25277
|
+
selection = "revision-flag";
|
|
25278
|
+
}
|
|
25279
|
+
if (revisionLogicalId === void 0 && endpoint !== void 0) {
|
|
25280
|
+
revisionLogicalId = pickHighestWeight(endpoint);
|
|
25281
|
+
if (revisionLogicalId !== void 0) selection = "endpoint-weight";
|
|
25282
|
+
}
|
|
25283
|
+
if (revisionLogicalId === void 0) {
|
|
25284
|
+
const revisions = revisionsOfFunction();
|
|
25285
|
+
if (revisions.length === 1) {
|
|
25286
|
+
revisionLogicalId = revisions[0];
|
|
25287
|
+
selection = "only-revision";
|
|
25288
|
+
} else if (endpoint === void 0 && revisions.length > 1) {
|
|
25289
|
+
const endpoints = endpointsOfFunction();
|
|
25290
|
+
if (endpoints.length === 1) {
|
|
25291
|
+
endpoint = readEndpoint(endpoints[0], resources[endpoints[0]], resources);
|
|
25292
|
+
revisionLogicalId = pickHighestWeight(endpoint);
|
|
25293
|
+
if (revisionLogicalId !== void 0) selection = "endpoint-weight";
|
|
25294
|
+
}
|
|
25295
|
+
}
|
|
25296
|
+
if (revisionLogicalId === void 0) throw new WebFunctionResolutionError(revisions.length === 0 ? `${displayIdent(logicalId)} in ${displayStackName(stack.stackName)} resolves to no ${WEB_FUNCTION_REVISION_TYPE} in this template, so there is no code to serve. Target the revision itself, or pass --revision <logicalId>.` : `${displayIdent(logicalId)} has ${revisions.length} revisions and no single endpoint that picks one. Pass --revision <logicalId>, or target one of the function's endpoints. ${describeRevisions(revisions, stack)}`);
|
|
25297
|
+
}
|
|
25298
|
+
if (endpoint === void 0) {
|
|
25299
|
+
const routing = endpointsOfFunction().map((id) => readEndpoint(id, resources[id], resources)).filter((e) => e.weights.some((w) => w.revisionLogicalId === revisionLogicalId));
|
|
25300
|
+
if (routing.length === 1) endpoint = routing[0];
|
|
25301
|
+
}
|
|
25302
|
+
const fnName = (functionLogicalId !== void 0 ? resources[functionLogicalId] : void 0)?.Properties?.["FunctionName"];
|
|
25303
|
+
return {
|
|
25304
|
+
stack,
|
|
25305
|
+
functionLogicalId,
|
|
25306
|
+
functionName: typeof fnName === "string" && fnName.length > 0 ? fnName : functionLogicalId ?? revisionLogicalId,
|
|
25307
|
+
revisionLogicalId,
|
|
25308
|
+
revision: resources[revisionLogicalId],
|
|
25309
|
+
endpoint,
|
|
25310
|
+
selection: selection ?? "only-revision"
|
|
25311
|
+
};
|
|
25312
|
+
}
|
|
25313
|
+
function pickStack(parsed, stacks, target) {
|
|
25314
|
+
const selection = selectTargetStack(parsed, target, stacks);
|
|
25315
|
+
switch (selection.kind) {
|
|
25316
|
+
case "found": return selection.stack;
|
|
25317
|
+
case "no-prefix": throw new WebFunctionResolutionError(`Multiple stacks in app, target ${displayIdent(parsed.pathOrId)} is missing a stack prefix. Use StackName:<logicalId> or StackName/... (path form). Available stacks: ${describeStacks(stacks)}.`);
|
|
25318
|
+
case "not-found": throw new WebFunctionResolutionError(`Stack ${displayStackName(selection.pattern)} not found. Available stacks: ${describeStacks(stacks)}.`);
|
|
25319
|
+
case "ambiguous": throw new WebFunctionResolutionError(`Stack pattern ${displayStackName(selection.pattern)} matched ${selection.matched.length} stacks: ${describeStacks(selection.matched)}. Use a more specific pattern, or the stack's display path.`);
|
|
25320
|
+
}
|
|
25321
|
+
}
|
|
25322
|
+
function matchWebResource(parsed, target, stack, resources) {
|
|
25323
|
+
if (parsed.isPath) {
|
|
25324
|
+
const matches = resolveCdkPathToLogicalIds(parsed.pathOrId, buildCdkPathIndex(stack.template)).filter(({ logicalId }) => WEB_TYPES.includes(resources[logicalId]?.Type ?? ""));
|
|
25325
|
+
for (const type of WEB_TYPES) {
|
|
25326
|
+
const ofType = matches.filter(({ logicalId }) => resources[logicalId].Type === type);
|
|
25327
|
+
if (ofType.length === 1) {
|
|
25328
|
+
const id = ofType[0].logicalId;
|
|
25329
|
+
return {
|
|
25330
|
+
logicalId: id,
|
|
25331
|
+
resource: resources[id]
|
|
25332
|
+
};
|
|
25333
|
+
}
|
|
25334
|
+
if (ofType.length > 1) throw new WebFunctionResolutionError(`Target ${displayStackName(target)} matches ${ofType.length} ${type} resources: ${ofType.map((m) => displayIdent(m.logicalId)).join(", ")}. Refine the path or use the StackName:<logicalId> form.`);
|
|
25335
|
+
}
|
|
25336
|
+
throw notFoundError(target, stack, resources);
|
|
25337
|
+
}
|
|
25338
|
+
const resource = resources[parsed.pathOrId];
|
|
25339
|
+
if (!resource) throw notFoundError(target, stack, resources);
|
|
25340
|
+
if (!WEB_TYPES.includes(resource.Type)) throw new WebFunctionResolutionError(`Resource ${displayIdent(parsed.pathOrId)} in ${displayStackName(stack.stackName)} is ${displayIdent(resource.Type)}, not a Lambda web function. ${getEmbedConfig().cliName} start-web-function serves ${WEB_TYPES.join(" / ")} targets.`);
|
|
25341
|
+
return {
|
|
25342
|
+
logicalId: parsed.pathOrId,
|
|
25343
|
+
resource
|
|
25344
|
+
};
|
|
25345
|
+
}
|
|
25346
|
+
function notFoundError(target, stack, resources) {
|
|
25347
|
+
const available = WEB_TYPES.flatMap((t) => logicalIdsOfType(resources, t));
|
|
25348
|
+
const hint = available.length > 0 ? `Web function resources in ${displayStackName(stack.stackName)}: ${available.map((id) => displayIdent(id)).join(", ")}.` : `No ${WEB_FUNCTION_TYPE} resources found in ${displayStackName(stack.stackName)}.`;
|
|
25349
|
+
return new WebFunctionResolutionError(`Target ${displayStackName(target)} not found. ${hint}`);
|
|
25350
|
+
}
|
|
25351
|
+
function logicalIdsOfType(resources, type) {
|
|
25352
|
+
return Object.keys(resources).filter((id) => resources[id]?.Type === type);
|
|
25353
|
+
}
|
|
25354
|
+
function linkedFunctionLogicalId(value, resources, functions) {
|
|
25355
|
+
const ref = referencedLogicalId(value);
|
|
25356
|
+
if (ref !== void 0) return functions.includes(ref) ? ref : void 0;
|
|
25357
|
+
if (typeof value !== "string") return void 0;
|
|
25358
|
+
const byName = functions.filter((id) => resources[id]?.Properties?.["FunctionName"] === value);
|
|
25359
|
+
return byName.length === 1 ? byName[0] : void 0;
|
|
25360
|
+
}
|
|
25361
|
+
function referencedLogicalId(value) {
|
|
25362
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
25363
|
+
const obj = value;
|
|
25364
|
+
if (typeof obj["Ref"] === "string") return obj["Ref"];
|
|
25365
|
+
const getAtt = obj["Fn::GetAtt"];
|
|
25366
|
+
if (Array.isArray(getAtt) && typeof getAtt[0] === "string") return getAtt[0];
|
|
25367
|
+
if (typeof getAtt === "string") return getAtt.split(".")[0];
|
|
25368
|
+
}
|
|
25369
|
+
function readEndpoint(logicalId, resource, resources) {
|
|
25370
|
+
const props = resource.Properties ?? {};
|
|
25371
|
+
const rawWeights = props["RevisionWeights"];
|
|
25372
|
+
const weights = [];
|
|
25373
|
+
if (Array.isArray(rawWeights)) for (const entry of rawWeights) {
|
|
25374
|
+
if (!entry || typeof entry !== "object") continue;
|
|
25375
|
+
const e = entry;
|
|
25376
|
+
const ref = referencedLogicalId(e["RevisionId"]);
|
|
25377
|
+
weights.push({
|
|
25378
|
+
revisionLogicalId: ref !== void 0 && resources[ref]?.Type === "AWS::Lambda::WebFunctionRevision" ? ref : void 0,
|
|
25379
|
+
weight: typeof e["Weight"] === "number" ? e["Weight"] : 0
|
|
25380
|
+
});
|
|
25381
|
+
}
|
|
25382
|
+
const str = (v) => typeof v === "string" ? v : void 0;
|
|
25383
|
+
return {
|
|
25384
|
+
logicalId,
|
|
25385
|
+
endpointType: str(props["EndpointType"]),
|
|
25386
|
+
authType: str(props["AuthType"]),
|
|
25387
|
+
weights
|
|
25388
|
+
};
|
|
25389
|
+
}
|
|
25390
|
+
function pickHighestWeight(endpoint) {
|
|
25391
|
+
let best;
|
|
25392
|
+
for (const w of endpoint.weights) {
|
|
25393
|
+
if (w.revisionLogicalId === void 0) continue;
|
|
25394
|
+
if (best === void 0 || w.weight > best.weight) best = w;
|
|
25395
|
+
}
|
|
25396
|
+
return best?.revisionLogicalId;
|
|
25397
|
+
}
|
|
25398
|
+
function describeRevisions(revisions, stack) {
|
|
25399
|
+
return revisions.length > 0 ? `Revisions: ${revisions.map((id) => displayIdent(id)).join(", ")}.` : `No ${WEB_FUNCTION_REVISION_TYPE} linked to it in ${displayStackName(stack.stackName)}.`;
|
|
25400
|
+
}
|
|
25401
|
+
|
|
25402
|
+
//#endregion
|
|
25403
|
+
//#region src/local/web-function-artifact.ts
|
|
25404
|
+
const SUPPORTED_CODE_CONFIG_KEYS = ["S3Object"];
|
|
25405
|
+
function parseWebFunctionBuildConfig(revisionLogicalId, buildConfig) {
|
|
25406
|
+
const subject = `Web function revision ${displayIdent(revisionLogicalId)}`;
|
|
25407
|
+
const build = asObject$2(buildConfig);
|
|
25408
|
+
const runtime = asObject$2(build?.["RuntimeConfig"])?.["Runtime"];
|
|
25409
|
+
if (typeof runtime !== "string" || runtime.length === 0) throw new CdkLocalError(`${subject} has no literal BuildConfig.RuntimeConfig.Runtime.`, "LOCAL_START_WEB_FUNCTION_NO_RUNTIME");
|
|
25410
|
+
const codeConfig = asObject$2(build?.["CodeConfig"]);
|
|
25411
|
+
if (!codeConfig) throw new CdkLocalError(`${subject} has no BuildConfig.CodeConfig object.`, "LOCAL_START_WEB_FUNCTION_NO_CODE_CONFIG");
|
|
25412
|
+
const unknownKeys = Object.keys(codeConfig).filter((k) => !SUPPORTED_CODE_CONFIG_KEYS.includes(k));
|
|
25413
|
+
if (unknownKeys.length > 0) throw new CdkLocalError(`${subject} uses BuildConfig.CodeConfig.${unknownKeys.map((k) => displayIdent(k)).join(", ")}, which ${getEmbedConfig().cliName} start-web-function does not support yet. Supported: ${SUPPORTED_CODE_CONFIG_KEYS.join(", ")}.`, "LOCAL_START_WEB_FUNCTION_UNSUPPORTED_CODE_CONFIG");
|
|
25414
|
+
const s3 = asObject$2(codeConfig["S3Object"]);
|
|
25415
|
+
if (!s3) throw new CdkLocalError(`${subject} has no BuildConfig.CodeConfig.S3Object.`, "LOCAL_START_WEB_FUNCTION_NO_CODE_CONFIG");
|
|
25416
|
+
const key = s3["Key"];
|
|
25417
|
+
if (typeof key !== "string" || key.length === 0) throw new CdkLocalError(`${subject} has a CodeConfig.S3Object.Key that is not a literal string (${describeValue$1(key)}). The code must come from a CDK asset in this app (for example an s3assets.Asset passing asset.s3BucketName / asset.s3ObjectKey), whose key synthesizes as a literal.`, "LOCAL_START_WEB_FUNCTION_CODE_KEY_NOT_LITERAL");
|
|
25418
|
+
const versionId = s3["VersionId"];
|
|
25419
|
+
return {
|
|
25420
|
+
runtime,
|
|
25421
|
+
s3Object: {
|
|
25422
|
+
bucket: s3["Bucket"],
|
|
25423
|
+
key,
|
|
25424
|
+
...typeof versionId === "string" && { versionId }
|
|
25425
|
+
}
|
|
25426
|
+
};
|
|
25427
|
+
}
|
|
25428
|
+
function findFileAssetForObjectKey(manifest, key) {
|
|
25429
|
+
for (const [hash, asset] of Object.entries(manifest.files ?? {})) for (const dest of Object.values(asset.destinations ?? {})) if (dest.objectKey === key || dest.objectKey.endsWith(`/${key}`)) return {
|
|
25430
|
+
hash,
|
|
25431
|
+
asset
|
|
25432
|
+
};
|
|
25433
|
+
const hash = key.replace(/^.*\//, "").replace(/\.zip$/, "");
|
|
25434
|
+
const byHash = Object.hasOwn(manifest.files ?? {}, hash) ? manifest.files[hash] : void 0;
|
|
25435
|
+
return byHash ? {
|
|
25436
|
+
hash,
|
|
25437
|
+
asset: byHash
|
|
25438
|
+
} : void 0;
|
|
25439
|
+
}
|
|
25440
|
+
async function resolveWebFunctionArtifact(stack, revisionLogicalId, code, loader = new AssetManifestLoader()) {
|
|
25441
|
+
const subject = `Web function revision ${displayUntrustedValue(revisionLogicalId)} code bundle`;
|
|
25442
|
+
const notFound = () => new CdkLocalError(`${subject} (S3Object bucket ${describeValue$1(code.s3Object.bucket)}, key ${displayUntrustedValue(code.s3Object.key)}) matches no file asset in this app's cdk.out. ${getEmbedConfig().cliName} start-web-function runs code that comes from a CDK asset in this app (for example an s3assets.Asset of a directory); it does not download deployed code. Re-synthesize the app, or point the revision at an asset.`, "LOCAL_START_WEB_FUNCTION_CODE_ASSET_NOT_FOUND");
|
|
25443
|
+
const manifestPath = stack.assetManifestPath;
|
|
25444
|
+
if (!manifestPath) throw notFound();
|
|
25445
|
+
const manifestDir = dirname$1(manifestPath);
|
|
25446
|
+
const manifest = await loader.loadStackManifest(stack, manifestDir);
|
|
25447
|
+
const match = manifest ? findFileAssetForObjectKey(manifest, code.s3Object.key) : void 0;
|
|
25448
|
+
if (!match) throw notFound();
|
|
25449
|
+
if (typeof match.asset.source.path !== "string" || match.asset.source.path.length === 0) throw new CdkLocalError(`${subject} matches file asset ${displayIdent(match.hash)}, which has no source.path (it is produced by source.executable at publish time), so there is no staged bundle in cdk.out to mount. Give the revision an asset of a directory or a .zip file (for example s3assets.Asset with path: '<dir>').`, "LOCAL_START_WEB_FUNCTION_CODE_ASSET_NO_PATH");
|
|
25450
|
+
const sourcePath = resolveAssetSourcePath({
|
|
25451
|
+
manifestDir,
|
|
25452
|
+
value: match.asset.source.path,
|
|
25453
|
+
assetOutdir: assetPathDirs(stack).assetOutdir,
|
|
25454
|
+
absolute: "honour-warn",
|
|
25455
|
+
field: "source.path",
|
|
25456
|
+
subject,
|
|
25457
|
+
action: "mount it",
|
|
25458
|
+
sink: "bind-mount that directory into the web function container",
|
|
25459
|
+
wrapError: (message) => new CdkLocalError(message, "LOCAL_START_WEB_FUNCTION_CODE_SOURCE_ESCAPES_ASSEMBLY")
|
|
25460
|
+
});
|
|
25461
|
+
if (!existsSync$1(sourcePath)) throw new CdkLocalError(`${subject} source ${displayUntrustedValue(sourcePath)} does not exist. Re-synthesize the app and retry.`, "LOCAL_START_WEB_FUNCTION_CODE_SOURCE_MISSING");
|
|
25462
|
+
if (!statSync$1(sourcePath).isDirectory() && !sourcePath.endsWith(".zip")) throw new CdkLocalError(`${subject} source ${displayUntrustedValue(sourcePath)} is neither a directory nor a .zip archive, so it is not a code bundle.`, "LOCAL_START_WEB_FUNCTION_CODE_SOURCE_NOT_BUNDLE");
|
|
25463
|
+
const materialized = materializeAssetCodeDir(sourcePath);
|
|
25464
|
+
return {
|
|
25465
|
+
kind: "zip",
|
|
25466
|
+
runtime: code.runtime,
|
|
25467
|
+
codeDir: materialized.dir,
|
|
25468
|
+
...materialized.tmpDir !== void 0 && { tmpDir: materialized.tmpDir },
|
|
25469
|
+
assetHash: match.hash
|
|
25470
|
+
};
|
|
25471
|
+
}
|
|
25472
|
+
function disposeWebFunctionArtifact(artifact) {
|
|
25473
|
+
if (artifact?.kind !== "zip" || artifact.tmpDir === void 0) return;
|
|
25474
|
+
try {
|
|
25475
|
+
if (existsSync$1(artifact.tmpDir)) rmSync$1(artifact.tmpDir, {
|
|
25476
|
+
recursive: true,
|
|
25477
|
+
force: true
|
|
25478
|
+
});
|
|
25479
|
+
} catch {}
|
|
25480
|
+
}
|
|
25481
|
+
function asObject$2(value) {
|
|
25482
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
25483
|
+
}
|
|
25484
|
+
function describeValue$1(value) {
|
|
25485
|
+
if (typeof value === "string") return displayUntrustedValue(value);
|
|
25486
|
+
let json;
|
|
25487
|
+
try {
|
|
25488
|
+
json = JSON.stringify(value);
|
|
25489
|
+
} catch {
|
|
25490
|
+
json = void 0;
|
|
25491
|
+
}
|
|
25492
|
+
return displayUntrustedValue(json ?? String(value));
|
|
25493
|
+
}
|
|
25494
|
+
|
|
25495
|
+
//#endregion
|
|
25496
|
+
//#region src/local/web-function-runtime.ts
|
|
25497
|
+
const NODE_CODE_MOUNT = "/var/task";
|
|
25498
|
+
const NODE_ENTRYPOINT_ENV = "AWS_LAMBDA_NODEJS_ENTRYPOINT";
|
|
25499
|
+
const NODE_DEFAULT_ENTRY = "index.js";
|
|
25500
|
+
function buildNodeEntryFileStart(runtime, env, codeDir, fileExists = existsSync$1) {
|
|
25501
|
+
const configured = env[NODE_ENTRYPOINT_ENV];
|
|
25502
|
+
const entryFile = configured !== void 0 && configured.length > 0 ? configured : NODE_DEFAULT_ENTRY;
|
|
25503
|
+
const hostPath = resolve$1(codeDir, entryFile);
|
|
25504
|
+
const rel = relative$1(codeDir, hostPath);
|
|
25505
|
+
if (isAbsolute$1(entryFile) || rel === "" || rel === ".." || rel.startsWith(`..${sep$1}`) || isAbsolute$1(rel)) throw new CdkLocalError(`${NODE_ENTRYPOINT_ENV}=${displayUntrustedValue(entryFile)} must name a file inside the code bundle, relative to its root.`, "LOCAL_START_WEB_FUNCTION_ENTRYPOINT_OUTSIDE_BUNDLE");
|
|
25506
|
+
if (!fileExists(hostPath)) throw new CdkLocalError(`The ${runtime} web function entry file ${displayUntrustedValue(entryFile)} was not found in the code bundle ${displayUntrustedValue(codeDir)}. The runtime loads ${NODE_DEFAULT_ENTRY} from the package root unless ${NODE_ENTRYPOINT_ENV} names another file.`, "LOCAL_START_WEB_FUNCTION_ENTRYPOINT_MISSING");
|
|
25507
|
+
const posixRel = sep$1 === "\\" ? rel.split("\\").join("/") : rel;
|
|
25508
|
+
return {
|
|
25509
|
+
entryPoint: ["/var/lang/bin/node"],
|
|
25510
|
+
cmd: [posix.join(NODE_CODE_MOUNT, posixRel)],
|
|
25511
|
+
label: entryFile
|
|
25512
|
+
};
|
|
25513
|
+
}
|
|
25514
|
+
const WEB_FUNCTION_RUNTIMES = { "nodejs24.x": {
|
|
25515
|
+
runtime: "nodejs24.x",
|
|
25516
|
+
baseImage: "public.ecr.aws/lambda/nodejs:24",
|
|
25517
|
+
codeMountPath: NODE_CODE_MOUNT,
|
|
25518
|
+
architectures: ["arm64"],
|
|
25519
|
+
buildStart: (env, codeDir, deps) => buildNodeEntryFileStart("nodejs24.x", env, codeDir, deps?.fileExists)
|
|
25520
|
+
} };
|
|
25521
|
+
function supportedWebFunctionRuntimes(table = WEB_FUNCTION_RUNTIMES) {
|
|
25522
|
+
return Object.keys(table).sort();
|
|
25523
|
+
}
|
|
25524
|
+
function resolveWebFunctionRuntime(runtime, table = WEB_FUNCTION_RUNTIMES) {
|
|
25525
|
+
const spec = Object.hasOwn(table, runtime) ? table[runtime] : void 0;
|
|
25526
|
+
if (!spec) throw new CdkLocalError(`Web function runtime ${displayIdent(runtime)} is not supported by ${getEmbedConfig().cliName} start-web-function yet. Supported: ${supportedWebFunctionRuntimes(table).join(", ")}.`, "LOCAL_START_WEB_FUNCTION_UNSUPPORTED_RUNTIME");
|
|
25527
|
+
return spec;
|
|
25528
|
+
}
|
|
25529
|
+
function defaultWebFunctionPlatform(spec) {
|
|
25530
|
+
return spec.architectures[0] === "x86_64" ? "linux/amd64" : "linux/arm64";
|
|
25531
|
+
}
|
|
25532
|
+
|
|
25533
|
+
//#endregion
|
|
25534
|
+
//#region src/local/web-function-service-config.ts
|
|
25535
|
+
const WEB_FUNCTION_HTTP_ENDPOINT_ENV = "AWS_LAMBDA_HTTP_ENDPOINT";
|
|
25536
|
+
const APPLICATION_MANAGED_AUTH = "ApplicationManaged";
|
|
25537
|
+
const HOME_REGION_ENDPOINT = "HomeRegion";
|
|
25538
|
+
function readWebFunctionServiceConfig(revision) {
|
|
25539
|
+
const sc = asObject$1(revision.Properties?.["ServiceConfig"]) ?? {};
|
|
25540
|
+
const envRaw = sc["EnvironmentVariables"];
|
|
25541
|
+
const envObj = asObject$1(envRaw);
|
|
25542
|
+
const envKeys = envObj ? Object.keys(envObj) : [];
|
|
25543
|
+
const env = envKeys.length === 1 && (envKeys[0] === "Ref" || envKeys[0].startsWith("Fn::")) ? void 0 : envObj;
|
|
25544
|
+
const logGroup = asObject$1(asObject$1(sc["TelemetryConfig"])?.["LoggingConfig"])?.["LogGroup"];
|
|
25545
|
+
return {
|
|
25546
|
+
environmentVariables: env,
|
|
25547
|
+
environmentUnreadable: envRaw !== void 0 && env === void 0,
|
|
25548
|
+
executionRoleArn: sc["ExecutionRoleArn"],
|
|
25549
|
+
egressNetworkConnectorArn: sc["EgressNetworkConnectorArn"],
|
|
25550
|
+
timeoutSeconds: typeof sc["TimeoutSeconds"] === "number" ? sc["TimeoutSeconds"] : void 0,
|
|
25551
|
+
logGroup: typeof logGroup === "string" && logGroup.length > 0 ? logGroup : void 0
|
|
25552
|
+
};
|
|
25553
|
+
}
|
|
25554
|
+
function buildWebFunctionLambdaView(resolved, config, runtime) {
|
|
25555
|
+
return {
|
|
25556
|
+
kind: "zip",
|
|
25557
|
+
stack: resolved.stack,
|
|
25558
|
+
logicalId: resolved.revisionLogicalId,
|
|
25559
|
+
resource: {
|
|
25560
|
+
Type: resolved.revision.Type,
|
|
25561
|
+
Properties: {
|
|
25562
|
+
...config.environmentVariables !== void 0 && { Environment: { Variables: config.environmentVariables } },
|
|
25563
|
+
...config.executionRoleArn !== void 0 && { Role: config.executionRoleArn }
|
|
25564
|
+
},
|
|
25565
|
+
...resolved.revision.Metadata !== void 0 && { Metadata: resolved.revision.Metadata }
|
|
25566
|
+
},
|
|
25567
|
+
memoryMb: 128,
|
|
25568
|
+
timeoutSec: config.timeoutSeconds ?? 0,
|
|
25569
|
+
layers: [],
|
|
25570
|
+
runtime,
|
|
25571
|
+
handler: "",
|
|
25572
|
+
codePath: null,
|
|
25573
|
+
architecture: "arm64"
|
|
25574
|
+
};
|
|
25575
|
+
}
|
|
25576
|
+
function webFunctionAssumeRole(assumeRole, config) {
|
|
25577
|
+
if (assumeRole === true && isIamRoleArn(config.executionRoleArn)) return config.executionRoleArn;
|
|
25578
|
+
return assumeRole;
|
|
25579
|
+
}
|
|
25580
|
+
function finalizeWebFunctionEnv(lambdaEnv, opts) {
|
|
25581
|
+
const env = { ...lambdaEnv };
|
|
25582
|
+
delete env["AWS_LAMBDA_FUNCTION_MEMORY_SIZE"];
|
|
25583
|
+
delete env["AWS_LAMBDA_FUNCTION_VERSION"];
|
|
25584
|
+
delete env["AWS_LAMBDA_FUNCTION_TIMEOUT"];
|
|
25585
|
+
env["AWS_LAMBDA_FUNCTION_NAME"] = opts.functionName;
|
|
25586
|
+
env["AWS_LAMBDA_LOG_GROUP_NAME"] = opts.logGroup ?? `/aws/lambda/${opts.functionName}`;
|
|
25587
|
+
if (opts.timeoutSeconds !== void 0) env["AWS_LAMBDA_FUNCTION_TIMEOUT"] = String(opts.timeoutSeconds);
|
|
25588
|
+
const replacedHttpEndpoint = Object.hasOwn(lambdaEnv, WEB_FUNCTION_HTTP_ENDPOINT_ENV);
|
|
25589
|
+
env[WEB_FUNCTION_HTTP_ENDPOINT_ENV] = `0.0.0.0:${opts.containerPort}`;
|
|
25590
|
+
return {
|
|
25591
|
+
env,
|
|
25592
|
+
replacedHttpEndpoint
|
|
25593
|
+
};
|
|
25594
|
+
}
|
|
25595
|
+
function webFunctionFidelityWarnings(resolved, config) {
|
|
25596
|
+
const out = [];
|
|
25597
|
+
const rev = displayIdent(resolved.revisionLogicalId);
|
|
25598
|
+
if (config.egressNetworkConnectorArn !== void 0) out.push(`Revision ${rev} sets ServiceConfig.EgressNetworkConnectorArn; the network connector is not emulated, so the container reaches the network through Docker's default bridge.`);
|
|
25599
|
+
if (config.environmentUnreadable) out.push(`Revision ${rev} declares ServiceConfig.EnvironmentVariables as an intrinsic rather than a map; none of its variables reach the container. Supply them with --env-vars.`);
|
|
25600
|
+
const endpoint = resolved.endpoint;
|
|
25601
|
+
if (endpoint) {
|
|
25602
|
+
const ep = displayIdent(endpoint.logicalId);
|
|
25603
|
+
if (endpoint.endpointType !== void 0 && endpoint.endpointType !== "HomeRegion") out.push(`Endpoint ${ep} has EndpointType ${displayIdent(endpoint.endpointType)}; only a single ${HOME_REGION_ENDPOINT} endpoint is reproduced, on one local port.`);
|
|
25604
|
+
if (endpoint.authType !== void 0 && endpoint.authType !== "ApplicationManaged") out.push(`Endpoint ${ep} has AuthType ${displayIdent(endpoint.authType)}; it is not enforced locally — every request reaches the server unauthenticated.`);
|
|
25605
|
+
const routed = endpoint.weights.filter((w) => w.weight > 0);
|
|
25606
|
+
if (routed.length > 1) out.push(`Endpoint ${ep} splits traffic across ${routed.length} revisions; weighted routing is not emulated — every request goes to revision ${rev}.`);
|
|
25607
|
+
}
|
|
25608
|
+
return out;
|
|
25609
|
+
}
|
|
25610
|
+
function asObject$1(value) {
|
|
25611
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
25612
|
+
}
|
|
25613
|
+
|
|
25614
|
+
//#endregion
|
|
25615
|
+
//#region src/local/web-function-runner.ts
|
|
25616
|
+
const execFileAsync$1 = promisify$1(execFile$1);
|
|
25617
|
+
const WEB_FUNCTION_CONTAINER_PORT = 8080;
|
|
25618
|
+
const READY_FAILURE_LOG_LINES = 20;
|
|
25619
|
+
const MAX_PROBE_TIMEOUT_MS = 1e4;
|
|
25620
|
+
function planWebFunctionContainer(artifact, spec, env, deps) {
|
|
25621
|
+
if (artifact.kind !== "zip") throw new CdkLocalError("A container-image web function source is not supported yet; only a zip bundle from CodeConfig.S3Object runs locally.", "LOCAL_START_WEB_FUNCTION_UNSUPPORTED_ARTIFACT");
|
|
25622
|
+
const start = spec.buildStart(env, artifact.codeDir, deps);
|
|
25623
|
+
return {
|
|
25624
|
+
image: spec.baseImage,
|
|
25625
|
+
mounts: [{
|
|
25626
|
+
hostPath: artifact.codeDir,
|
|
25627
|
+
containerPath: spec.codeMountPath,
|
|
25628
|
+
readOnly: true
|
|
25629
|
+
}],
|
|
25630
|
+
entryPoint: start.entryPoint,
|
|
25631
|
+
cmd: start.cmd,
|
|
25632
|
+
workingDir: spec.codeMountPath,
|
|
25633
|
+
label: start.label
|
|
25634
|
+
};
|
|
25635
|
+
}
|
|
25636
|
+
async function startWebFunctionContainer(opts) {
|
|
25637
|
+
const { plan } = opts;
|
|
25638
|
+
return runDetached({
|
|
25639
|
+
image: plan.image,
|
|
25640
|
+
mounts: plan.mounts,
|
|
25641
|
+
env: opts.env,
|
|
25642
|
+
...opts.sensitiveEnvKeys.size > 0 && { sensitiveEnvKeys: opts.sensitiveEnvKeys },
|
|
25643
|
+
entryPoint: plan.entryPoint,
|
|
25644
|
+
cmd: plan.cmd,
|
|
25645
|
+
...plan.workingDir !== void 0 && { workingDir: plan.workingDir },
|
|
25646
|
+
hostPort: opts.hostPort,
|
|
25647
|
+
containerPort: WEB_FUNCTION_CONTAINER_PORT,
|
|
25648
|
+
host: opts.host,
|
|
25649
|
+
platform: opts.platform,
|
|
25650
|
+
name: opts.name,
|
|
25651
|
+
autoRemove: false
|
|
25652
|
+
});
|
|
25653
|
+
}
|
|
25654
|
+
var RecentOutput = class {
|
|
25655
|
+
lines = [];
|
|
25656
|
+
partial = "";
|
|
25657
|
+
max;
|
|
25658
|
+
constructor(max = 20) {
|
|
25659
|
+
this.max = max;
|
|
25660
|
+
}
|
|
25661
|
+
push(chunk) {
|
|
25662
|
+
const parts = (this.partial + chunk.toString()).split("\n");
|
|
25663
|
+
this.partial = parts.pop() ?? "";
|
|
25664
|
+
for (const line of parts) this.add(line);
|
|
25665
|
+
if (this.partial.length > 4096) {
|
|
25666
|
+
this.add(this.partial);
|
|
25667
|
+
this.partial = "";
|
|
25668
|
+
}
|
|
25669
|
+
}
|
|
25670
|
+
tail() {
|
|
25671
|
+
return this.partial.length > 0 ? [...this.lines, this.partial].slice(-this.max) : this.lines;
|
|
25672
|
+
}
|
|
25673
|
+
add(line) {
|
|
25674
|
+
this.lines.push(line.replace(/\r$/, ""));
|
|
25675
|
+
if (this.lines.length > this.max) this.lines.shift();
|
|
25676
|
+
}
|
|
25677
|
+
};
|
|
25678
|
+
async function waitForWebFunctionReady(args, deps = defaultReadyDeps) {
|
|
25679
|
+
const deadline = deps.now() + args.timeoutMs;
|
|
25680
|
+
let attempt = 0;
|
|
25681
|
+
let last = "refused";
|
|
25682
|
+
while (deps.now() < deadline) {
|
|
25683
|
+
const remaining = Math.max(1, deadline - deps.now());
|
|
25684
|
+
last = await deps.probe(args.host, args.port, Math.min(remaining, MAX_PROBE_TIMEOUT_MS));
|
|
25685
|
+
if (last === "answered") return;
|
|
25686
|
+
attempt++;
|
|
25687
|
+
if (attempt % 4 === 0 && !await deps.isRunning(args.containerId)) throw await containerFailure(`The web function container exited before its server listened on AWS_LAMBDA_HTTP_ENDPOINT (started ${args.label}).`, args.containerId, args.recent, deps.fetchLogs);
|
|
25688
|
+
if (last === "refused") await deps.sleep(250);
|
|
25689
|
+
}
|
|
25690
|
+
const authority = formatAuthority(args.host, args.port);
|
|
25691
|
+
throw await containerFailure(last === "timeout" ? `The web function server on ${authority} accepted the connection but did not answer GET / within ${args.timeoutMs}ms — it is listening but slow to respond. Raise --timeout, or make / answer sooner (started ${args.label}).` : `The web function server never accepted an HTTP connection on ${authority} within ${args.timeoutMs}ms. It must listen on the host:port given in AWS_LAMBDA_HTTP_ENDPOINT (started ${args.label}).`, args.containerId, args.recent, deps.fetchLogs);
|
|
25692
|
+
}
|
|
25693
|
+
async function containerFailure(message, containerId, recent, fetchLogs = readContainerLogTail, code = "LOCAL_START_WEB_FUNCTION_NOT_READY") {
|
|
25694
|
+
let lines = [];
|
|
25695
|
+
try {
|
|
25696
|
+
lines = await fetchLogs(containerId, 20);
|
|
25697
|
+
} catch {
|
|
25698
|
+
lines = [];
|
|
25699
|
+
}
|
|
25700
|
+
if (lines.length === 0) lines = recent.tail();
|
|
25701
|
+
return new CdkLocalError(`${message}${lines.length > 0 ? ` Last ${lines.length} line(s) of container output:\n${lines.map((l) => ` ${displayToolOutput(l)}`).join("\n")}` : " The container printed nothing."}`, code);
|
|
25702
|
+
}
|
|
25703
|
+
function watchContainerExit(containerId) {
|
|
25704
|
+
let stopped = false;
|
|
25705
|
+
const proc = spawn$1(getDockerCmd(), ["wait", containerId], { stdio: [
|
|
25706
|
+
"ignore",
|
|
25707
|
+
"pipe",
|
|
25708
|
+
"ignore"
|
|
25709
|
+
] });
|
|
25710
|
+
let out = "";
|
|
25711
|
+
proc.stdout?.on("data", (c) => out += c.toString());
|
|
25712
|
+
return {
|
|
25713
|
+
exited: new Promise((resolveExit) => {
|
|
25714
|
+
const done = () => {
|
|
25715
|
+
if (stopped) return;
|
|
25716
|
+
const code = Number.parseInt(out.trim(), 10);
|
|
25717
|
+
resolveExit(Number.isNaN(code) ? void 0 : code);
|
|
25718
|
+
};
|
|
25719
|
+
proc.on("close", done);
|
|
25720
|
+
proc.on("error", done);
|
|
25721
|
+
}),
|
|
25722
|
+
stop: () => {
|
|
25723
|
+
stopped = true;
|
|
25724
|
+
if (!proc.killed) proc.kill("SIGTERM");
|
|
25725
|
+
}
|
|
25726
|
+
};
|
|
25727
|
+
}
|
|
25728
|
+
function probeHttp(host, port, timeoutMs) {
|
|
25729
|
+
return new Promise((resolveProbe) => {
|
|
25730
|
+
let timedOut = false;
|
|
25731
|
+
let connected = false;
|
|
25732
|
+
const req = request$2({
|
|
25733
|
+
host,
|
|
25734
|
+
port,
|
|
25735
|
+
path: "/",
|
|
25736
|
+
method: "GET",
|
|
25737
|
+
timeout: timeoutMs,
|
|
25738
|
+
headers: { connection: "close" }
|
|
25739
|
+
}, (res) => {
|
|
25740
|
+
res.resume();
|
|
25741
|
+
resolveProbe("answered");
|
|
25742
|
+
});
|
|
25743
|
+
req.on("socket", (socket) => {
|
|
25744
|
+
socket.once("connect", () => {
|
|
25745
|
+
connected = true;
|
|
25746
|
+
});
|
|
25747
|
+
});
|
|
25748
|
+
req.on("timeout", () => {
|
|
25749
|
+
timedOut = true;
|
|
25750
|
+
req.destroy();
|
|
25751
|
+
});
|
|
25752
|
+
req.on("error", () => resolveProbe(timedOut && connected ? "timeout" : "refused"));
|
|
25753
|
+
req.end();
|
|
25754
|
+
});
|
|
25755
|
+
}
|
|
25756
|
+
async function containerIsRunning(containerId) {
|
|
25757
|
+
try {
|
|
25758
|
+
const { stdout } = await execFileAsync$1(getDockerCmd(), [
|
|
25759
|
+
"inspect",
|
|
25760
|
+
"-f",
|
|
25761
|
+
"{{.State.Running}}",
|
|
25762
|
+
containerId
|
|
25763
|
+
]);
|
|
25764
|
+
return stdout.trim() === "true";
|
|
25765
|
+
} catch {
|
|
25766
|
+
return false;
|
|
25767
|
+
}
|
|
25768
|
+
}
|
|
25769
|
+
function readContainerLogTail(containerId, lines, spawnFn = spawn$1) {
|
|
25770
|
+
return new Promise((resolveLogs, rejectLogs) => {
|
|
25771
|
+
const proc = spawnFn(getDockerCmd(), [
|
|
25772
|
+
"logs",
|
|
25773
|
+
"--tail",
|
|
25774
|
+
String(lines),
|
|
25775
|
+
containerId
|
|
25776
|
+
], { stdio: [
|
|
25777
|
+
"ignore",
|
|
25778
|
+
"pipe",
|
|
25779
|
+
"pipe"
|
|
25780
|
+
] });
|
|
25781
|
+
const chunks = [];
|
|
25782
|
+
let size = 0;
|
|
25783
|
+
const take = (c) => {
|
|
25784
|
+
if (size > 4 * 1024 * 1024) return;
|
|
25785
|
+
chunks.push(c);
|
|
25786
|
+
size += c.length;
|
|
25787
|
+
};
|
|
25788
|
+
proc.stdout?.on("data", take);
|
|
25789
|
+
proc.stderr?.on("data", take);
|
|
25790
|
+
proc.on("error", rejectLogs);
|
|
25791
|
+
proc.on("close", (code) => {
|
|
25792
|
+
if (code !== 0) {
|
|
25793
|
+
rejectLogs(/* @__PURE__ */ new Error(`docker logs exited ${code}`));
|
|
25794
|
+
return;
|
|
25795
|
+
}
|
|
25796
|
+
resolveLogs(Buffer.concat(chunks).toString().split("\n").map((l) => l.replace(/\r$/, "")).filter((l) => l.length > 0).slice(-lines));
|
|
25797
|
+
});
|
|
25798
|
+
});
|
|
25799
|
+
}
|
|
25800
|
+
const defaultReadyDeps = {
|
|
25801
|
+
probe: probeHttp,
|
|
25802
|
+
isRunning: containerIsRunning,
|
|
25803
|
+
fetchLogs: readContainerLogTail,
|
|
25804
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
25805
|
+
now: () => Date.now()
|
|
25806
|
+
};
|
|
25807
|
+
|
|
25808
|
+
//#endregion
|
|
25809
|
+
//#region src/cli/commands/local-start-web-function.ts
|
|
25810
|
+
function parsePort$1(raw) {
|
|
25811
|
+
const n = Number(raw);
|
|
25812
|
+
if (!Number.isInteger(n) || n < 0 || n > 65535) throw new InvalidArgumentError(`--port must be an integer 0-65535, got '${raw}'.`);
|
|
25813
|
+
return n;
|
|
25814
|
+
}
|
|
25815
|
+
function connectHost$1(host) {
|
|
25816
|
+
if (host === "0.0.0.0") return "127.0.0.1";
|
|
25817
|
+
if (host === "::" || host === "[::]") return "::1";
|
|
25818
|
+
return host;
|
|
25819
|
+
}
|
|
25820
|
+
async function localStartWebFunctionCommand(target, options, extraStateProviders, hostSynthesizer) {
|
|
25821
|
+
const logger = getLogger();
|
|
25822
|
+
if (options.verbose) logger.setLevel("debug");
|
|
25823
|
+
let containerId;
|
|
25824
|
+
let stopLogs;
|
|
25825
|
+
let exitWatch;
|
|
25826
|
+
let artifact;
|
|
25827
|
+
let runInFlight;
|
|
25828
|
+
let teardownPromise;
|
|
25829
|
+
let shuttingDown = false;
|
|
25830
|
+
let waitingOnRun = false;
|
|
25831
|
+
let containerName;
|
|
25832
|
+
const forever = () => new Promise(() => void 0);
|
|
25833
|
+
const doTeardown = async () => {
|
|
25834
|
+
exitWatch?.stop();
|
|
25835
|
+
if (containerId === void 0 && runInFlight !== void 0) {
|
|
25836
|
+
waitingOnRun = true;
|
|
25837
|
+
try {
|
|
25838
|
+
containerId = await runInFlight;
|
|
25839
|
+
} catch {} finally {
|
|
25840
|
+
waitingOnRun = false;
|
|
25841
|
+
}
|
|
25842
|
+
}
|
|
25843
|
+
if (stopLogs) try {
|
|
25844
|
+
stopLogs();
|
|
25845
|
+
} catch (err) {
|
|
25846
|
+
logger.debug(`streamLogs stop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
25847
|
+
}
|
|
25848
|
+
if (containerId) await removeContainer(containerId);
|
|
25849
|
+
disposeWebFunctionArtifact(artifact);
|
|
25850
|
+
};
|
|
25851
|
+
const teardown = () => teardownPromise ??= doTeardown();
|
|
25852
|
+
await applyRoleArnIfSet({
|
|
25853
|
+
roleArn: options.roleArn,
|
|
25854
|
+
region: options.region,
|
|
25855
|
+
profile: options.profile
|
|
25856
|
+
});
|
|
25857
|
+
await ensureDockerAvailable();
|
|
25858
|
+
const profileCredentials = options.profile ? await resolveProfileCredentials(options.profile) : void 0;
|
|
25859
|
+
const appCmd = resolveApp(options.app);
|
|
25860
|
+
if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
|
|
25861
|
+
logger.info("Synthesizing CDK app...");
|
|
25862
|
+
const synthesizer = hostSynthesizer ?? new Synthesizer();
|
|
25863
|
+
const context = parseContextOptions(options.context);
|
|
25864
|
+
const synthOpts = {
|
|
25865
|
+
app: appCmd,
|
|
25866
|
+
output: options.output,
|
|
25867
|
+
...options.region && { region: options.region },
|
|
25868
|
+
...options.profile && { profile: options.profile },
|
|
25869
|
+
...Object.keys(context).length > 0 && { context }
|
|
25870
|
+
};
|
|
25871
|
+
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
25872
|
+
const resolved = resolveWebFunctionTarget(await resolveSingleTarget(target, {
|
|
25873
|
+
entries: listTargets(stacks).webFunctions,
|
|
25874
|
+
message: "Select a Lambda web function to serve",
|
|
25875
|
+
noun: "Lambda web functions",
|
|
25876
|
+
onMissing: () => new CdkLocalError(`${getEmbedConfig().cliName} start-web-function requires a <target> (a WebFunction, WebFunctionEndpoint or WebFunctionRevision display path or logical ID). Run \`${getEmbedConfig().cliName} list\` to see them, or run it in a TTY to pick interactively.`, "LOCAL_START_WEB_FUNCTION_TARGET_REQUIRED")
|
|
25877
|
+
}), stacks, options.revision);
|
|
25878
|
+
const code = parseWebFunctionBuildConfig(resolved.revisionLogicalId, resolved.revision.Properties?.["BuildConfig"]);
|
|
25879
|
+
const spec = resolveWebFunctionRuntime(code.runtime);
|
|
25880
|
+
const selectionNote = {
|
|
25881
|
+
"target-revision": "the target revision",
|
|
25882
|
+
"revision-flag": "--revision",
|
|
25883
|
+
"endpoint-weight": `highest weight on endpoint ${displayIdent(resolved.endpoint?.logicalId ?? "")}`,
|
|
25884
|
+
"only-revision": "the function's only revision"
|
|
25885
|
+
};
|
|
25886
|
+
logger.info(`Target: ${displayStackName(`${resolved.stack.stackName}/${resolved.revisionLogicalId}`)} (${spec.runtime}; revision chosen by ${selectionNote[resolved.selection]})`);
|
|
25887
|
+
const serviceConfig = readWebFunctionServiceConfig(resolved.revision);
|
|
25888
|
+
for (const line of webFunctionFidelityWarnings(resolved, serviceConfig)) logger.warn(line);
|
|
25889
|
+
artifact = await resolveWebFunctionArtifact(resolved.stack, resolved.revisionLogicalId, code);
|
|
25890
|
+
const shutdown = async (signal, exitCode) => {
|
|
25891
|
+
if (shuttingDown) {
|
|
25892
|
+
if (!waitingOnRun) {
|
|
25893
|
+
logger.info(`Received ${signal} again; still removing the container, please wait...`);
|
|
25894
|
+
return;
|
|
25895
|
+
}
|
|
25896
|
+
disposeWebFunctionArtifact(artifact);
|
|
25897
|
+
logger.warn(`Received ${signal} again; exiting without waiting for docker run to return. The container may still be starting, and its environment holds the forwarded AWS credentials. Remove it by its name (this works even if it is created after this exit): docker rm -f ${containerName ?? "<name>"}`);
|
|
25898
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
25899
|
+
return;
|
|
25900
|
+
}
|
|
25901
|
+
shuttingDown = true;
|
|
25902
|
+
logger.info(`Received ${signal}, shutting down...`);
|
|
25903
|
+
await teardown();
|
|
25904
|
+
process.exit(exitCode);
|
|
25905
|
+
};
|
|
25906
|
+
process.on("SIGINT", () => void shutdown("SIGINT", 130));
|
|
25907
|
+
process.on("SIGTERM", () => void shutdown("SIGTERM", 0));
|
|
25908
|
+
const recent = new RecentOutput();
|
|
25909
|
+
try {
|
|
25910
|
+
const assumeRole = webFunctionAssumeRole(options.assumeRole, serviceConfig);
|
|
25911
|
+
const containerEnv = await resolveLambdaContainerEnv(buildWebFunctionLambdaView(resolved, serviceConfig, spec.runtime), {
|
|
25912
|
+
...options,
|
|
25913
|
+
...assumeRole !== void 0 && { assumeRole },
|
|
25914
|
+
deployedFunctionLookups: false
|
|
25915
|
+
}, profileCredentials, extraStateProviders);
|
|
25916
|
+
const { env, replacedHttpEndpoint } = finalizeWebFunctionEnv(containerEnv.env, {
|
|
25917
|
+
functionName: resolved.functionName,
|
|
25918
|
+
containerPort: WEB_FUNCTION_CONTAINER_PORT,
|
|
25919
|
+
timeoutSeconds: serviceConfig.timeoutSeconds,
|
|
25920
|
+
logGroup: serviceConfig.logGroup
|
|
25921
|
+
});
|
|
25922
|
+
if (replacedHttpEndpoint) logger.warn(`The revision's ${WEB_FUNCTION_HTTP_ENDPOINT_ENV} is replaced with 0.0.0.0:${WEB_FUNCTION_CONTAINER_PORT}: the runtime owns that variable, and the local port is published to it.`);
|
|
25923
|
+
const plan = planWebFunctionContainer(artifact, spec, env);
|
|
25924
|
+
const platform = options.platform ?? defaultWebFunctionPlatform(spec);
|
|
25925
|
+
await pullImage(plan.image, options.pull === false, platform);
|
|
25926
|
+
if (teardownPromise) return await forever();
|
|
25927
|
+
const hostPort = options.port === 0 ? await pickFreePort() : options.port;
|
|
25928
|
+
const name = containerName = `${getEmbedConfig().resourceNamePrefix}-webfn-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
|
|
25929
|
+
logger.info(`Starting web function container (image=${plan.image}, start=${plan.label}, port=${hostPort} -> ${WEB_FUNCTION_CONTAINER_PORT})...`);
|
|
25930
|
+
runInFlight = startWebFunctionContainer({
|
|
25931
|
+
plan,
|
|
25932
|
+
env,
|
|
25933
|
+
sensitiveEnvKeys: new Set(containerEnv.sensitiveEnvKeys),
|
|
25934
|
+
host: options.host,
|
|
25935
|
+
hostPort,
|
|
25936
|
+
platform,
|
|
25937
|
+
name
|
|
25938
|
+
});
|
|
25939
|
+
const id = await runInFlight;
|
|
25940
|
+
if (teardownPromise) return await forever();
|
|
25941
|
+
containerId = id;
|
|
25942
|
+
stopLogs = streamLogs(id, { onOutput: (chunk) => recent.push(chunk) });
|
|
25943
|
+
exitWatch = watchContainerExit(id);
|
|
25944
|
+
const probeHost = connectHost$1(options.host);
|
|
25945
|
+
await waitForWebFunctionReady({
|
|
25946
|
+
containerId: id,
|
|
25947
|
+
host: probeHost,
|
|
25948
|
+
port: hostPort,
|
|
25949
|
+
timeoutMs: options.timeout,
|
|
25950
|
+
recent,
|
|
25951
|
+
label: plan.label
|
|
25952
|
+
});
|
|
25953
|
+
logger.info(`Server listening on http://${formatAuthority(probeHost, hostPort)} (${displayIdent(resolved.functionName)} (Lambda web function))`);
|
|
25954
|
+
logger.info("Press ^C to shut down.");
|
|
25955
|
+
} catch (err) {
|
|
25956
|
+
if (shuttingDown) return await forever();
|
|
25957
|
+
await teardown();
|
|
25958
|
+
throw err;
|
|
25959
|
+
}
|
|
25960
|
+
if (!exitWatch || !containerId) return await forever();
|
|
25961
|
+
const exitCode = await exitWatch.exited;
|
|
25962
|
+
if (shuttingDown || teardownPromise) return await forever();
|
|
25963
|
+
const failure = await containerFailure(`The web function container exited (exit code ${exitCode ?? "unknown"}) while serving.`, containerId, recent, void 0, "LOCAL_START_WEB_FUNCTION_EXITED");
|
|
25964
|
+
await teardown();
|
|
25965
|
+
throw failure;
|
|
25966
|
+
}
|
|
25967
|
+
function createLocalStartWebFunctionCommand(opts = {}) {
|
|
25968
|
+
setEmbedConfig(opts.embedConfig);
|
|
25969
|
+
const cmd = new Command("start-web-function").description("Serve an AWS Lambda Web Function locally. Resolves a WebFunction, WebFunctionEndpoint or WebFunctionRevision target to one revision (an endpoint serves its highest-weight revision; --revision overrides), runs its code bundle from cdk.out in a container whose server listens on AWS_LAMBDA_HTTP_ENDPOINT, and publishes it on a local port with the revision's ServiceConfig environment. Target accepts a CDK display path or stack-qualified logical ID; single-stack apps may omit the prefix. Omit <target> in a TTY to pick from a list. Runs until ^C.").argument("[target]", "CDK display path or stack-qualified logical ID of the WebFunction / WebFunctionEndpoint / WebFunctionRevision (omit to pick interactively in a TTY)").action(withErrorHandling(async (target, options) => {
|
|
25970
|
+
await localStartWebFunctionCommand(target, options, opts.extraStateProviders, opts.synthesizer);
|
|
25971
|
+
}));
|
|
25972
|
+
addStartWebFunctionSpecificOptions(cmd);
|
|
25973
|
+
[
|
|
25974
|
+
...commonOptions(),
|
|
25975
|
+
...appOptions(),
|
|
25976
|
+
...contextOptions
|
|
25977
|
+
].forEach((opt) => cmd.addOption(opt));
|
|
25978
|
+
cmd.addOption(regionOption);
|
|
25979
|
+
return cmd;
|
|
25980
|
+
}
|
|
25981
|
+
function addStartWebFunctionSpecificOptions(cmd) {
|
|
25982
|
+
return cmd.addOption(new Option("--revision <logicalId>", "Serve this WebFunctionRevision (logical ID) instead of the one the endpoint weights pick.")).addOption(new Option("--port <n>", "Local port to serve on. Default 0 (OS-assigned).").default(0).argParser(parsePort$1)).addOption(new Option("--host <ip>", "Address to publish the port on.").default("127.0.0.1")).addOption(new Option("--platform <platform>", "docker --platform for the server container. Defaults to the runtime's cloud architecture (linux/arm64 for nodejs24.x).").choices(["linux/amd64", "linux/arm64"])).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"RevisionLogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--no-pull", "Skip docker pull of the runtime base image (use the cached image)")).addOption(new Option("--timeout <ms>", "Maximum time in milliseconds to wait for the server to answer HTTP. Default 60000.").default(6e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the revision's execution role and forward STS-issued temp credentials to the container. (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the revision's ServiceConfig.ExecutionRoleArn: a literal ARN as written, or the Fn::GetAtt / Ref CDK synthesizes resolved against --from-cfn-stack deployed state; (3) `--no-assume-role` opts out. Off by default.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. Bare form uses the resolved stack name.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
|
|
25983
|
+
}
|
|
25984
|
+
|
|
25985
|
+
//#endregion
|
|
25986
|
+
//#region src/local/microvm-image.ts
|
|
25987
|
+
const MICROVM_IMAGE_TYPE = "AWS::Lambda::MicrovmImage";
|
|
25988
|
+
const MICROVM_DEFAULT_INGRESS_PORT = 8080;
|
|
25989
|
+
var MicrovmResolutionError = class MicrovmResolutionError extends Error {
|
|
25990
|
+
constructor(message) {
|
|
25991
|
+
super(message);
|
|
25992
|
+
this.name = "MicrovmResolutionError";
|
|
25993
|
+
Object.setPrototypeOf(this, MicrovmResolutionError.prototype);
|
|
25994
|
+
}
|
|
25995
|
+
};
|
|
25996
|
+
function resolveMicrovmImageTarget(target, stacks) {
|
|
25997
|
+
if (stacks.length === 0) throw new MicrovmResolutionError("No stacks found in the synthesized assembly.");
|
|
25998
|
+
const parsed = parseTarget(target);
|
|
25999
|
+
const selection = selectTargetStack(parsed, target, stacks);
|
|
26000
|
+
let stack;
|
|
26001
|
+
switch (selection.kind) {
|
|
26002
|
+
case "found":
|
|
26003
|
+
stack = selection.stack;
|
|
26004
|
+
break;
|
|
26005
|
+
case "no-prefix": throw new MicrovmResolutionError(`Multiple stacks in app, target ${displayIdent(parsed.pathOrId)} is missing a stack prefix. Use StackName:<logicalId> or StackName/... (path form). Available stacks: ${describeStacks(stacks)}.`);
|
|
26006
|
+
case "not-found": throw new MicrovmResolutionError(`Stack ${displayStackName(selection.pattern)} not found. Available stacks: ${describeStacks(stacks)}.`);
|
|
26007
|
+
case "ambiguous": throw new MicrovmResolutionError(`Stack pattern ${displayStackName(selection.pattern)} matched ${selection.matched.length} stacks: ${describeStacks(selection.matched)}. Use a more specific pattern, or the stack's display path.`);
|
|
26008
|
+
}
|
|
26009
|
+
const resources = stack.template.Resources ?? {};
|
|
26010
|
+
const images = Object.keys(resources).filter((id) => resources[id]?.Type === MICROVM_IMAGE_TYPE);
|
|
26011
|
+
const notFound = () => new MicrovmResolutionError(`Target ${displayStackName(target)} not found. ` + (images.length > 0 ? `MicroVM images in ${displayStackName(stack.stackName)}: ${images.map((id) => displayIdent(id)).join(", ")}.` : `No ${MICROVM_IMAGE_TYPE} resources found in ${displayStackName(stack.stackName)}.`));
|
|
26012
|
+
if (parsed.isPath) {
|
|
26013
|
+
const matches = resolveCdkPathToLogicalIds(parsed.pathOrId, buildCdkPathIndex(stack.template)).filter(({ logicalId }) => resources[logicalId]?.Type === MICROVM_IMAGE_TYPE);
|
|
26014
|
+
if (matches.length === 1) {
|
|
26015
|
+
const id = matches[0].logicalId;
|
|
26016
|
+
return {
|
|
26017
|
+
stack,
|
|
26018
|
+
logicalId: id,
|
|
26019
|
+
resource: resources[id]
|
|
26020
|
+
};
|
|
26021
|
+
}
|
|
26022
|
+
if (matches.length > 1) throw new MicrovmResolutionError(`Target ${displayStackName(target)} matches ${matches.length} ${MICROVM_IMAGE_TYPE} resources: ${matches.map((m) => displayIdent(m.logicalId)).join(", ")}. Refine the path or use the StackName:<logicalId> form.`);
|
|
26023
|
+
throw notFound();
|
|
26024
|
+
}
|
|
26025
|
+
const resource = resources[parsed.pathOrId];
|
|
26026
|
+
if (!resource) throw notFound();
|
|
26027
|
+
if (resource.Type !== "AWS::Lambda::MicrovmImage") throw new MicrovmResolutionError(`Resource ${displayIdent(parsed.pathOrId)} in ${displayStackName(stack.stackName)} is ${displayIdent(resource.Type)}, not a MicroVM image. ${getEmbedConfig().cliName} start-microvm runs ${MICROVM_IMAGE_TYPE} targets.`);
|
|
26028
|
+
return {
|
|
26029
|
+
stack,
|
|
26030
|
+
logicalId: parsed.pathOrId,
|
|
26031
|
+
resource
|
|
26032
|
+
};
|
|
26033
|
+
}
|
|
26034
|
+
const HOOK_KEYS = {
|
|
26035
|
+
ready: {
|
|
26036
|
+
group: "MicrovmImageHooks",
|
|
26037
|
+
key: "Ready"
|
|
26038
|
+
},
|
|
26039
|
+
validate: {
|
|
26040
|
+
group: "MicrovmImageHooks",
|
|
26041
|
+
key: "Validate"
|
|
26042
|
+
},
|
|
26043
|
+
run: {
|
|
26044
|
+
group: "MicrovmHooks",
|
|
26045
|
+
key: "Run"
|
|
26046
|
+
},
|
|
26047
|
+
suspend: {
|
|
26048
|
+
group: "MicrovmHooks",
|
|
26049
|
+
key: "Suspend"
|
|
26050
|
+
},
|
|
26051
|
+
resume: {
|
|
26052
|
+
group: "MicrovmHooks",
|
|
26053
|
+
key: "Resume"
|
|
26054
|
+
},
|
|
26055
|
+
terminate: {
|
|
26056
|
+
group: "MicrovmHooks",
|
|
26057
|
+
key: "Terminate"
|
|
26058
|
+
}
|
|
26059
|
+
};
|
|
26060
|
+
function readMicrovmImageConfig(logicalId, resource) {
|
|
26061
|
+
const subject = `MicroVM image ${displayIdent(logicalId)}`;
|
|
26062
|
+
const props = resource.Properties ?? {};
|
|
26063
|
+
const name = typeof props["Name"] === "string" && props["Name"].length > 0 ? props["Name"] : logicalId;
|
|
26064
|
+
const uri = asObject(props["CodeArtifact"])?.["Uri"];
|
|
26065
|
+
const codeKey = s3KeyOfUri(uri);
|
|
26066
|
+
if (codeKey === void 0) throw new CdkLocalError(`${subject} has a CodeArtifact.Uri whose object key is not a literal (${describeValue(uri)}). The code must come from a CDK asset in this app (for example an s3assets.Asset of a directory holding the Dockerfile, passing asset.s3ObjectUrl), whose key synthesizes as a literal.`, "LOCAL_START_MICROVM_CODE_URI_NOT_LITERAL");
|
|
26067
|
+
const cpu = props["CpuConfigurations"];
|
|
26068
|
+
const archRaw = Array.isArray(cpu) ? asObject(cpu[0])?.["Architecture"] : void 0;
|
|
26069
|
+
const architecture = typeof archRaw === "string" ? normalizeArchitecture(archRaw) : void 0;
|
|
26070
|
+
const environment = {};
|
|
26071
|
+
const envRaw = props["EnvironmentVariables"];
|
|
26072
|
+
const unemulated = [];
|
|
26073
|
+
if (Array.isArray(envRaw)) for (const entry of envRaw) {
|
|
26074
|
+
const e = asObject(entry);
|
|
26075
|
+
if (e && typeof e["Key"] === "string") environment[e["Key"]] = e["Value"];
|
|
26076
|
+
}
|
|
26077
|
+
else if (envRaw !== void 0) unemulated.push("EnvironmentVariables is an intrinsic rather than a list, so none of its variables reach the container (supply them with --env-vars)");
|
|
26078
|
+
const hooksObj = asObject(props["Hooks"]);
|
|
26079
|
+
const hooks = {};
|
|
26080
|
+
for (const [hook, where] of Object.entries(HOOK_KEYS)) {
|
|
26081
|
+
const group = asObject(hooksObj?.[where.group]);
|
|
26082
|
+
const value = group?.[where.key];
|
|
26083
|
+
if (value === void 0 || value === "DISABLED") continue;
|
|
26084
|
+
if (value !== "ENABLED") throw new CdkLocalError(`${subject} has Hooks.${where.group}.${where.key} ${describeValue(value)}; only the literal ENABLED or DISABLED can run locally.`, "LOCAL_START_MICROVM_HOOK_VALUE");
|
|
26085
|
+
const timeout = group?.[`${where.key}TimeoutInSeconds`];
|
|
26086
|
+
hooks[hook] = { timeoutSeconds: typeof timeout === "number" && timeout > 0 ? timeout : void 0 };
|
|
26087
|
+
}
|
|
26088
|
+
const port = hooksObj?.["Port"];
|
|
26089
|
+
const hooksPort = typeof port === "number" && Number.isInteger(port) && port > 0 && port < 65536 ? port : void 0;
|
|
26090
|
+
if (Object.keys(hooks).length > 0 && hooksPort === void 0) throw new CdkLocalError(`${subject} enables lifecycle hooks (${Object.keys(hooks).join(", ")}) but has no literal Hooks.Port. Lambda requires the hooks port whenever a hook is configured; set Hooks.Port to the port the application serves /aws/lambda-microvms/runtime/v1/ on.`, "LOCAL_START_MICROVM_NO_HOOKS_PORT");
|
|
26091
|
+
const present = (key) => {
|
|
26092
|
+
const v = props[key];
|
|
26093
|
+
if (v === void 0 || v === null) return false;
|
|
26094
|
+
if (Array.isArray(v)) return v.length > 0;
|
|
26095
|
+
if (typeof v === "object") return Object.keys(v).length > 0;
|
|
26096
|
+
return true;
|
|
26097
|
+
};
|
|
26098
|
+
if (present("EgressNetworkConnectors")) unemulated.push("EgressNetworkConnectors (the container uses Docker's default network egress)");
|
|
26099
|
+
if (present("AdditionalOsCapabilities")) unemulated.push("AdditionalOsCapabilities (the container runs with Docker's default capabilities)");
|
|
26100
|
+
if (present("Resources")) unemulated.push("Resources sizing (the container gets no memory limit)");
|
|
26101
|
+
if (present("BaseImageVersion")) unemulated.push("BaseImageVersion (the local build uses whatever the Dockerfile names)");
|
|
26102
|
+
if (present("Logging")) unemulated.push("Logging (container output is printed here instead of sent to CloudWatch Logs)");
|
|
26103
|
+
return {
|
|
26104
|
+
logicalId,
|
|
26105
|
+
name,
|
|
26106
|
+
codeKey,
|
|
26107
|
+
architecture,
|
|
26108
|
+
environment,
|
|
26109
|
+
hooksPort,
|
|
26110
|
+
hooks,
|
|
26111
|
+
unemulated
|
|
26112
|
+
};
|
|
26113
|
+
}
|
|
26114
|
+
function normalizeArchitecture(raw) {
|
|
26115
|
+
const s = raw.toLowerCase().replace(/_/g, "");
|
|
26116
|
+
if (s === "arm64") return "arm64";
|
|
26117
|
+
if (s === "x8664") return "x86_64";
|
|
26118
|
+
}
|
|
26119
|
+
function s3KeyOfUri(value) {
|
|
26120
|
+
const flat = flattenTemplateString(value);
|
|
26121
|
+
if (flat === void 0) return void 0;
|
|
26122
|
+
const m = /^s3:\/\/[^/]*\/(.+)$/.exec(flat);
|
|
26123
|
+
if (!m) return void 0;
|
|
26124
|
+
const key = m[1];
|
|
26125
|
+
return key.includes("\0") || key.includes("${") ? void 0 : key;
|
|
26126
|
+
}
|
|
26127
|
+
function flattenTemplateString(value) {
|
|
26128
|
+
if (typeof value === "string") return value;
|
|
26129
|
+
const obj = asObject(value);
|
|
26130
|
+
if (!obj) return void 0;
|
|
26131
|
+
const join = obj["Fn::Join"];
|
|
26132
|
+
if (Array.isArray(join) && typeof join[0] === "string" && Array.isArray(join[1])) return join[1].map((p) => flattenTemplateString(p) ?? "\0").join(join[0]);
|
|
26133
|
+
const sub = obj["Fn::Sub"];
|
|
26134
|
+
if (typeof sub === "string") return sub.replace(/\$\{[^}]*\}/g, "\0");
|
|
26135
|
+
if (Array.isArray(sub) && typeof sub[0] === "string") return sub[0].replace(/\$\{[^}]*\}/g, "\0");
|
|
26136
|
+
return "\0";
|
|
26137
|
+
}
|
|
26138
|
+
async function resolveMicrovmCodeArtifact(stack, config, loader = new AssetManifestLoader()) {
|
|
26139
|
+
const subject = `MicroVM image ${displayUntrustedValue(config.logicalId)} code artifact`;
|
|
26140
|
+
const notFound = () => new CdkLocalError(`${subject} (key ${displayUntrustedValue(config.codeKey)}) matches no file asset in this app's cdk.out. ${getEmbedConfig().cliName} start-microvm builds code that comes from a CDK asset in this app (for example an s3assets.Asset of a directory holding the Dockerfile); it does not download deployed code. Re-synthesize the app, or point CodeArtifact.Uri at an asset.`, "LOCAL_START_MICROVM_CODE_ASSET_NOT_FOUND");
|
|
26141
|
+
const manifestPath = stack.assetManifestPath;
|
|
26142
|
+
if (!manifestPath) throw notFound();
|
|
26143
|
+
const manifestDir = dirname$1(manifestPath);
|
|
26144
|
+
const manifest = await loader.loadStackManifest(stack, manifestDir);
|
|
26145
|
+
const match = manifest ? findFileAssetForObjectKey(manifest, config.codeKey) : void 0;
|
|
26146
|
+
if (!match) throw notFound();
|
|
26147
|
+
if (typeof match.asset.source.path !== "string" || match.asset.source.path.length === 0) throw new CdkLocalError(`${subject} matches file asset ${displayIdent(match.hash)}, which has no source.path (it is produced by source.executable at publish time), so there is no staged bundle in cdk.out to build. Give the image an asset of a directory or a .zip file.`, "LOCAL_START_MICROVM_CODE_ASSET_NO_PATH");
|
|
26148
|
+
const sourcePath = resolveAssetSourcePath({
|
|
26149
|
+
manifestDir,
|
|
26150
|
+
value: match.asset.source.path,
|
|
26151
|
+
assetOutdir: assetPathDirs(stack).assetOutdir,
|
|
26152
|
+
absolute: "honour-warn",
|
|
26153
|
+
field: "source.path",
|
|
26154
|
+
subject,
|
|
26155
|
+
action: "build it",
|
|
26156
|
+
sink: "send that directory to docker build as the MicroVM image context",
|
|
26157
|
+
wrapError: (message) => new CdkLocalError(message, "LOCAL_START_MICROVM_CODE_SOURCE_ESCAPES_ASSEMBLY")
|
|
26158
|
+
});
|
|
26159
|
+
if (!existsSync$1(sourcePath)) throw new CdkLocalError(`${subject} source ${displayUntrustedValue(sourcePath)} does not exist. Re-synthesize the app and retry.`, "LOCAL_START_MICROVM_CODE_SOURCE_MISSING");
|
|
26160
|
+
if (!statSync$1(sourcePath).isDirectory() && !sourcePath.endsWith(".zip")) throw new CdkLocalError(`${subject} source ${displayUntrustedValue(sourcePath)} is neither a directory nor a .zip archive, so it is not a code bundle.`, "LOCAL_START_MICROVM_CODE_SOURCE_NOT_BUNDLE");
|
|
26161
|
+
const materialized = materializeAssetCodeDir(sourcePath);
|
|
26162
|
+
const artifact = {
|
|
26163
|
+
contextDir: materialized.dir,
|
|
26164
|
+
...materialized.tmpDir !== void 0 && { tmpDir: materialized.tmpDir },
|
|
26165
|
+
assetHash: match.hash
|
|
26166
|
+
};
|
|
26167
|
+
if (!existsSync$1(join$1(materialized.dir, "Dockerfile"))) {
|
|
26168
|
+
disposeMicrovmCodeArtifact(artifact);
|
|
26169
|
+
throw new CdkLocalError(`${subject} has no Dockerfile at its root. A MicroVM image's code artifact is a zip with a Dockerfile at the root plus the application files.`, "LOCAL_START_MICROVM_NO_DOCKERFILE");
|
|
26170
|
+
}
|
|
26171
|
+
return artifact;
|
|
26172
|
+
}
|
|
26173
|
+
function disposeMicrovmCodeArtifact(artifact) {
|
|
26174
|
+
if (artifact?.tmpDir === void 0) return;
|
|
26175
|
+
try {
|
|
26176
|
+
if (existsSync$1(artifact.tmpDir)) rmSync$1(artifact.tmpDir, {
|
|
26177
|
+
recursive: true,
|
|
26178
|
+
force: true
|
|
26179
|
+
});
|
|
26180
|
+
} catch {}
|
|
26181
|
+
}
|
|
26182
|
+
function asObject(value) {
|
|
26183
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
26184
|
+
}
|
|
26185
|
+
function describeValue(value) {
|
|
26186
|
+
if (typeof value === "string") return displayUntrustedValue(value);
|
|
26187
|
+
let json;
|
|
26188
|
+
try {
|
|
26189
|
+
json = JSON.stringify(value);
|
|
26190
|
+
} catch {
|
|
26191
|
+
json = void 0;
|
|
26192
|
+
}
|
|
26193
|
+
return displayUntrustedValue(json ?? String(value));
|
|
26194
|
+
}
|
|
26195
|
+
|
|
26196
|
+
//#endregion
|
|
26197
|
+
//#region src/local/microvm-hooks.ts
|
|
26198
|
+
const MICROVM_HOOK_PATH_PREFIX = "/aws/lambda-microvms/runtime/v1/";
|
|
26199
|
+
const MAX_RUN_HOOK_PAYLOAD_BYTES = 16 * 1024;
|
|
26200
|
+
const HOOK_RETRY_INTERVAL_MS = 250;
|
|
26201
|
+
var MicrovmHookError = class MicrovmHookError extends CdkLocalError {
|
|
26202
|
+
constructor(message, code = "LOCAL_START_MICROVM_HOOK_FAILED") {
|
|
26203
|
+
super(message, code);
|
|
26204
|
+
this.name = "MicrovmHookError";
|
|
26205
|
+
Object.setPrototypeOf(this, MicrovmHookError.prototype);
|
|
26206
|
+
}
|
|
26207
|
+
};
|
|
26208
|
+
function generateMicrovmId() {
|
|
26209
|
+
return `mvm-${randomUUID$1()}`;
|
|
26210
|
+
}
|
|
26211
|
+
function runHookBody(microvmId, runHookPayload) {
|
|
26212
|
+
return JSON.stringify({
|
|
26213
|
+
microvmId,
|
|
26214
|
+
...runHookPayload !== void 0 && { runHookPayload }
|
|
26215
|
+
});
|
|
26216
|
+
}
|
|
26217
|
+
async function pollBuildHook(hook, endpoint, timeoutMs, deps = defaultHookDeps) {
|
|
26218
|
+
const path = `${MICROVM_HOOK_PATH_PREFIX}${hook}`;
|
|
26219
|
+
const deadline = deps.now() + timeoutMs;
|
|
26220
|
+
let attempts = 0;
|
|
26221
|
+
let last = { kind: "refused" };
|
|
26222
|
+
while (deps.now() < deadline) {
|
|
26223
|
+
const remaining = Math.max(1, deadline - deps.now());
|
|
26224
|
+
last = await deps.post(endpoint.host, endpoint.port, path, void 0, remaining);
|
|
26225
|
+
attempts++;
|
|
26226
|
+
if (last.kind === "status" && last.status === 200) return attempts;
|
|
26227
|
+
if (last.kind === "timeout") throw new MicrovmHookError(`The /${hook} hook held the request open for ${timeoutMs}ms without answering. Answer 503 immediately while not ready (the cloud ends the image build when a held request reaches the timeout), or raise ${timeoutField(hook)}.`, "LOCAL_START_MICROVM_HOOK_TIMEOUT");
|
|
26228
|
+
if (last.kind === "status" && last.status !== 503) throw new MicrovmHookError(`The /${hook} hook answered HTTP ${last.status}; it must answer 200 when done or 503 to be retried.${bodySuffix(last.body)}`);
|
|
26229
|
+
if (deps.isAlive && attempts % 4 === 0 && !await deps.isAlive()) throw new MicrovmHookError(`The MicroVM container exited while cdk-local polled its /${hook} hook.`, "LOCAL_START_MICROVM_EXITED");
|
|
26230
|
+
await deps.sleep(250);
|
|
26231
|
+
}
|
|
26232
|
+
const lastSeen = last.kind === "status" ? `its last answer was HTTP ${last.status}` : `nothing answered on ${formatAuthority(endpoint.host, endpoint.port)} (the hooks port)`;
|
|
26233
|
+
throw new MicrovmHookError(`The /${hook} hook did not answer 200 within ${timeoutMs}ms (${timeoutField(hook)}); ${lastSeen}.`, "LOCAL_START_MICROVM_HOOK_TIMEOUT");
|
|
26234
|
+
}
|
|
26235
|
+
async function callRuntimeHook(hook, endpoint, timeoutMs, body, deps = defaultHookDeps) {
|
|
26236
|
+
const path = `${MICROVM_HOOK_PATH_PREFIX}${hook}`;
|
|
26237
|
+
const result = await deps.post(endpoint.host, endpoint.port, path, body, timeoutMs);
|
|
26238
|
+
if (result.kind === "status" && result.status === 200) return;
|
|
26239
|
+
if (result.kind === "timeout") throw new MicrovmHookError(`The /${hook} hook did not answer within ${timeoutMs}ms (${timeoutField(hook)}).`, "LOCAL_START_MICROVM_HOOK_TIMEOUT");
|
|
26240
|
+
if (result.kind === "refused") throw new MicrovmHookError(`The /${hook} hook could not be reached: nothing answered on the hooks port (${formatAuthority(endpoint.host, endpoint.port)}).`);
|
|
26241
|
+
throw new MicrovmHookError(`The /${hook} hook answered HTTP ${result.status}; it must answer 200.${bodySuffix(result.body)}`);
|
|
26242
|
+
}
|
|
26243
|
+
async function waitForAppAnswer(endpoint, timeoutMs, deps = {
|
|
26244
|
+
probe: probeHttp,
|
|
26245
|
+
sleep: defaultHookDeps.sleep,
|
|
26246
|
+
now: defaultHookDeps.now
|
|
26247
|
+
}) {
|
|
26248
|
+
const deadline = deps.now() + timeoutMs;
|
|
26249
|
+
let attempt = 0;
|
|
26250
|
+
while (deps.now() < deadline) {
|
|
26251
|
+
const remaining = Math.max(1, deadline - deps.now());
|
|
26252
|
+
const outcome = await deps.probe(endpoint.host, endpoint.port, Math.min(remaining, 1e4));
|
|
26253
|
+
if (outcome === "answered") return;
|
|
26254
|
+
attempt++;
|
|
26255
|
+
if (deps.isAlive && attempt % 4 === 0 && !await deps.isAlive()) throw new MicrovmHookError("The MicroVM container exited before its application answered HTTP.", "LOCAL_START_MICROVM_EXITED");
|
|
26256
|
+
if (outcome === "refused") await deps.sleep(250);
|
|
26257
|
+
}
|
|
26258
|
+
throw new MicrovmHookError(`The MicroVM application never answered HTTP on ${formatAuthority(endpoint.host, endpoint.port)} within ${timeoutMs}ms. With no /ready hook, cdk-local waits for the app to answer before driving the next hook; raise --timeout, or enable the /ready hook.`, "LOCAL_START_MICROVM_NOT_READY");
|
|
26259
|
+
}
|
|
26260
|
+
function timeoutField(hook) {
|
|
26261
|
+
return `${hook.charAt(0).toUpperCase() + hook.slice(1)}TimeoutInSeconds, or --timeout when it is unset`;
|
|
26262
|
+
}
|
|
26263
|
+
function bodySuffix(body) {
|
|
26264
|
+
const trimmed = body.trim();
|
|
26265
|
+
if (trimmed.length === 0) return "";
|
|
26266
|
+
return ` Body: ${displayToolOutput(trimmed.length > 500 ? `${trimmed.slice(0, 500)}...` : trimmed)}`;
|
|
26267
|
+
}
|
|
26268
|
+
function postHook(host, port, path, body, timeoutMs) {
|
|
26269
|
+
return new Promise((resolvePost) => {
|
|
26270
|
+
let settled = false;
|
|
26271
|
+
const settle = (r) => {
|
|
26272
|
+
if (settled) return;
|
|
26273
|
+
settled = true;
|
|
26274
|
+
clearTimeout(timer);
|
|
26275
|
+
resolvePost(r);
|
|
26276
|
+
};
|
|
26277
|
+
const payload = body ?? "";
|
|
26278
|
+
const req = request$2({
|
|
26279
|
+
host,
|
|
26280
|
+
port,
|
|
26281
|
+
path,
|
|
26282
|
+
method: "POST",
|
|
26283
|
+
headers: {
|
|
26284
|
+
connection: "close",
|
|
26285
|
+
"content-length": Buffer.byteLength(payload),
|
|
26286
|
+
...body !== void 0 && { "content-type": "application/json" }
|
|
26287
|
+
}
|
|
26288
|
+
}, (res) => {
|
|
26289
|
+
const chunks = [];
|
|
26290
|
+
let size = 0;
|
|
26291
|
+
res.on("data", (c) => {
|
|
26292
|
+
if (size < 64 * 1024) chunks.push(c);
|
|
26293
|
+
size += c.length;
|
|
26294
|
+
});
|
|
26295
|
+
res.on("end", () => settle({
|
|
26296
|
+
kind: "status",
|
|
26297
|
+
status: res.statusCode ?? 0,
|
|
26298
|
+
body: Buffer.concat(chunks).toString("utf-8")
|
|
26299
|
+
}));
|
|
26300
|
+
res.on("error", () => settle({
|
|
26301
|
+
kind: "status",
|
|
26302
|
+
status: res.statusCode ?? 0,
|
|
26303
|
+
body: ""
|
|
26304
|
+
}));
|
|
26305
|
+
});
|
|
26306
|
+
const timer = setTimeout(() => {
|
|
26307
|
+
settle({ kind: "timeout" });
|
|
26308
|
+
req.destroy();
|
|
26309
|
+
}, timeoutMs);
|
|
26310
|
+
req.on("error", () => settle({ kind: "refused" }));
|
|
26311
|
+
req.end(payload);
|
|
26312
|
+
});
|
|
26313
|
+
}
|
|
26314
|
+
const defaultHookDeps = {
|
|
26315
|
+
post: postHook,
|
|
26316
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
26317
|
+
now: () => Date.now()
|
|
26318
|
+
};
|
|
26319
|
+
|
|
26320
|
+
//#endregion
|
|
26321
|
+
//#region src/local/microvm-front-door.ts
|
|
26322
|
+
const MICROVM_CONTROL_PATH = "/_cdkl/microvm";
|
|
26323
|
+
const PROXY_PORT_HEADER = "x-aws-proxy-port";
|
|
26324
|
+
const MICROVM_WS_BASE_PROTOCOL = "lambda-microvms";
|
|
26325
|
+
const CLOSE_GRACE_MS = 1e3;
|
|
26326
|
+
function selectTargetPort(headers) {
|
|
26327
|
+
const fromHeader = firstHeader(headers[PROXY_PORT_HEADER]);
|
|
26328
|
+
const fromProtocol = fromHeader === void 0 ? subprotocols(headers).find((p) => p.startsWith(`${MICROVM_WS_BASE_PROTOCOL}.port.`)) : void 0;
|
|
26329
|
+
const raw = fromHeader ?? (fromProtocol !== void 0 ? fromProtocol.slice(`${"lambda-microvms"}.port.`.length) : void 0);
|
|
26330
|
+
if (raw === void 0) return {
|
|
26331
|
+
ok: true,
|
|
26332
|
+
port: MICROVM_DEFAULT_INGRESS_PORT
|
|
26333
|
+
};
|
|
26334
|
+
const trimmed = raw.trim();
|
|
26335
|
+
const n = /^\d{1,5}$/.test(trimmed) ? Number(trimmed) : NaN;
|
|
26336
|
+
if (!Number.isInteger(n) || n < 1 || n > 65535) return {
|
|
26337
|
+
ok: false,
|
|
26338
|
+
status: 400,
|
|
26339
|
+
message: `Invalid ${fromHeader === void 0 ? "WebSocket subprotocol lambda-microvms.port." : "X-aws-proxy-port"} value '${trimmed.slice(0, 32)}': it must be a port number 1-65535.`
|
|
26340
|
+
};
|
|
26341
|
+
return {
|
|
26342
|
+
ok: true,
|
|
26343
|
+
port: n
|
|
26344
|
+
};
|
|
26345
|
+
}
|
|
26346
|
+
function stripMicrovmProxyHeaders(headers) {
|
|
26347
|
+
for (const name of Object.keys(headers)) if (name.toLowerCase().startsWith("x-aws-proxy-")) delete headers[name];
|
|
26348
|
+
const offered = subprotocols(headers);
|
|
26349
|
+
if (offered.length === 0) return false;
|
|
26350
|
+
const kept = offered.filter((p) => !isMicrovmProtocol(p));
|
|
26351
|
+
if (kept.length > 0) headers["sec-websocket-protocol"] = kept.join(", ");
|
|
26352
|
+
else delete headers["sec-websocket-protocol"];
|
|
26353
|
+
return offered.includes(MICROVM_WS_BASE_PROTOCOL);
|
|
26354
|
+
}
|
|
26355
|
+
function isMicrovmProtocol(p) {
|
|
26356
|
+
return p === "lambda-microvms" || p.startsWith(`${"lambda-microvms"}.`);
|
|
26357
|
+
}
|
|
26358
|
+
function subprotocols(headers) {
|
|
26359
|
+
const raw = headers["sec-websocket-protocol"];
|
|
26360
|
+
const joined = Array.isArray(raw) ? raw.join(",") : raw;
|
|
26361
|
+
if (!joined) return [];
|
|
26362
|
+
return joined.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
26363
|
+
}
|
|
26364
|
+
function firstHeader(value) {
|
|
26365
|
+
if (Array.isArray(value)) return value[0];
|
|
26366
|
+
if (typeof value === "string") return value.split(",")[0];
|
|
26367
|
+
}
|
|
26368
|
+
function startMicrovmFrontDoor(opts) {
|
|
26369
|
+
const logger = getLogger().child("microvm");
|
|
26370
|
+
const bound = { port: opts.port };
|
|
26371
|
+
const server = createServer((req, res) => {
|
|
26372
|
+
handleRequest$1(req, res, opts, bound.port).catch((err) => {
|
|
26373
|
+
logger.debug(`microvm front door error: ${err instanceof Error ? err.message : String(err)}`);
|
|
26374
|
+
if (!res.headersSent) writeText(res, 502, "The MicroVM front door failed to forward the request.");
|
|
26375
|
+
else res.destroy();
|
|
26376
|
+
});
|
|
26377
|
+
});
|
|
26378
|
+
const upgraded = /* @__PURE__ */ new Set();
|
|
26379
|
+
const track = (sock) => {
|
|
26380
|
+
upgraded.add(sock);
|
|
26381
|
+
sock.once("close", () => upgraded.delete(sock));
|
|
26382
|
+
};
|
|
26383
|
+
server.on("upgrade", (req, socket, head) => {
|
|
26384
|
+
track(socket);
|
|
26385
|
+
handleUpgrade(req, socket, head, opts, track).catch((err) => {
|
|
26386
|
+
logger.debug(`microvm upgrade error: ${err instanceof Error ? err.message : String(err)}`);
|
|
26387
|
+
writeRawHttpError(socket, 502, "The MicroVM front door failed to forward the upgrade.");
|
|
26388
|
+
});
|
|
26389
|
+
});
|
|
26390
|
+
return new Promise((resolveStart, rejectStart) => {
|
|
26391
|
+
server.once("error", rejectStart);
|
|
26392
|
+
server.listen(opts.port, opts.host, () => {
|
|
26393
|
+
server.off("error", rejectStart);
|
|
26394
|
+
const address = server.address();
|
|
26395
|
+
const port = typeof address === "object" && address !== null ? address.port : opts.port;
|
|
26396
|
+
bound.port = port;
|
|
26397
|
+
resolveStart({
|
|
26398
|
+
port,
|
|
26399
|
+
close: () => new Promise((resolveClose) => {
|
|
26400
|
+
server.close(() => resolveClose());
|
|
26401
|
+
server.closeIdleConnections();
|
|
26402
|
+
for (const sock of upgraded) sock.end();
|
|
26403
|
+
setTimeout(() => {
|
|
26404
|
+
server.closeAllConnections();
|
|
26405
|
+
for (const sock of upgraded) sock.destroy();
|
|
26406
|
+
}, CLOSE_GRACE_MS).unref();
|
|
26407
|
+
})
|
|
26408
|
+
});
|
|
26409
|
+
});
|
|
26410
|
+
});
|
|
26411
|
+
}
|
|
26412
|
+
async function admit(opts) {
|
|
26413
|
+
const id = opts.target.microvmId;
|
|
26414
|
+
let state = opts.target.state();
|
|
26415
|
+
if ((state === "suspended" || state === "suspending" || state === "resuming") && opts.autoResume) {
|
|
26416
|
+
try {
|
|
26417
|
+
await opts.target.resume();
|
|
26418
|
+
} catch (err) {
|
|
26419
|
+
const now = opts.target.state();
|
|
26420
|
+
if (err instanceof MicrovmStateError && (now === "terminating" || now === "terminated")) return {
|
|
26421
|
+
status: 503,
|
|
26422
|
+
message: `MicroVM ${id} is ${now}.`
|
|
26423
|
+
};
|
|
26424
|
+
return {
|
|
26425
|
+
status: 502,
|
|
26426
|
+
message: `MicroVM ${id} could not be resumed for this request: ${err instanceof Error ? err.message : String(err)}`
|
|
26427
|
+
};
|
|
26428
|
+
}
|
|
26429
|
+
state = opts.target.state();
|
|
26430
|
+
}
|
|
26431
|
+
if (state === "running") return void 0;
|
|
26432
|
+
if (state === "terminating" || state === "terminated") return {
|
|
26433
|
+
status: 503,
|
|
26434
|
+
message: `MicroVM ${id} is ${state}.`
|
|
26435
|
+
};
|
|
26436
|
+
return {
|
|
26437
|
+
status: 503,
|
|
26438
|
+
message: `MicroVM ${id} is ${state}. Resume it with POST ${MICROVM_CONTROL_PATH}/resume (or start with --auto-resume to resume on the first request).`
|
|
26439
|
+
};
|
|
26440
|
+
}
|
|
26441
|
+
async function handleRequest$1(req, res, opts, boundPort) {
|
|
26442
|
+
const path = (req.url ?? "/").split("?")[0];
|
|
26443
|
+
if (path === "/_cdkl/microvm" || path.startsWith(`${"/_cdkl/microvm"}/`)) {
|
|
26444
|
+
const refusal = controlRequestRefusal(req.headers, opts.host, boundPort);
|
|
26445
|
+
if (refusal !== void 0) {
|
|
26446
|
+
req.resume();
|
|
26447
|
+
writeText(res, 403, refusal);
|
|
26448
|
+
return;
|
|
26449
|
+
}
|
|
26450
|
+
await handleControl(req, res, path, opts);
|
|
26451
|
+
return;
|
|
26452
|
+
}
|
|
26453
|
+
const selection = selectTargetPort(req.headers);
|
|
26454
|
+
if (!selection.ok) {
|
|
26455
|
+
req.resume();
|
|
26456
|
+
writeText(res, selection.status, selection.message);
|
|
26457
|
+
return;
|
|
26458
|
+
}
|
|
26459
|
+
const upstream = opts.target.upstreamFor(selection.port);
|
|
26460
|
+
if (!upstream) {
|
|
26461
|
+
req.resume();
|
|
26462
|
+
writeText(res, 403, notRoutableMessage(selection.port, opts.target));
|
|
26463
|
+
return;
|
|
26464
|
+
}
|
|
26465
|
+
const refusal = await admit(opts);
|
|
26466
|
+
if (refusal) {
|
|
26467
|
+
req.resume();
|
|
26468
|
+
writeText(res, refusal.status, refusal.message);
|
|
26469
|
+
return;
|
|
26470
|
+
}
|
|
26471
|
+
const headers = { ...req.headers };
|
|
26472
|
+
stripHopByHopHeaders(headers);
|
|
26473
|
+
stripMicrovmProxyHeaders(headers);
|
|
26474
|
+
await new Promise((resolveForward) => {
|
|
26475
|
+
const proxyReq = request({
|
|
26476
|
+
host: upstream.host,
|
|
26477
|
+
port: upstream.port,
|
|
26478
|
+
method: req.method,
|
|
26479
|
+
path: req.url,
|
|
26480
|
+
headers
|
|
26481
|
+
}, (proxyRes) => {
|
|
26482
|
+
const resHeaders = { ...proxyRes.headers };
|
|
26483
|
+
stripHopByHopHeaders(resHeaders);
|
|
26484
|
+
res.writeHead(proxyRes.statusCode ?? 502, resHeaders);
|
|
26485
|
+
proxyRes.pipe(res);
|
|
26486
|
+
proxyRes.on("end", resolveForward);
|
|
26487
|
+
proxyRes.on("error", () => {
|
|
26488
|
+
if (!res.writableEnded) res.destroy();
|
|
26489
|
+
resolveForward();
|
|
26490
|
+
});
|
|
26491
|
+
});
|
|
26492
|
+
proxyReq.on("error", () => {
|
|
26493
|
+
if (!res.headersSent) writeText(res, 502, `The application in MicroVM ${opts.target.microvmId} is not responding on port ${selection.port}.`);
|
|
26494
|
+
else if (!res.writableEnded) res.destroy();
|
|
26495
|
+
resolveForward();
|
|
26496
|
+
});
|
|
26497
|
+
res.on("close", () => {
|
|
26498
|
+
if (!res.writableEnded) proxyReq.destroy();
|
|
26499
|
+
resolveForward();
|
|
26500
|
+
});
|
|
26501
|
+
req.pipe(proxyReq);
|
|
26502
|
+
});
|
|
26503
|
+
}
|
|
26504
|
+
async function handleControl(req, res, path, opts) {
|
|
26505
|
+
req.resume();
|
|
26506
|
+
const target = opts.target;
|
|
26507
|
+
const action = path.slice(14).replace(/^\//, "");
|
|
26508
|
+
if (action === "") {
|
|
26509
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
26510
|
+
writeText(res, 405, `Use GET ${MICROVM_CONTROL_PATH}.`);
|
|
26511
|
+
return;
|
|
26512
|
+
}
|
|
26513
|
+
writeJson(res, 200, {
|
|
26514
|
+
microvmId: target.microvmId,
|
|
26515
|
+
state: target.state(),
|
|
26516
|
+
ports: target.routablePorts()
|
|
26517
|
+
});
|
|
26518
|
+
return;
|
|
26519
|
+
}
|
|
26520
|
+
if (action !== "suspend" && action !== "resume" && action !== "terminate") {
|
|
26521
|
+
writeText(res, 404, `Unknown control action '${action.slice(0, 32)}'. Use POST ${MICROVM_CONTROL_PATH}/suspend, /resume or /terminate.`);
|
|
26522
|
+
return;
|
|
26523
|
+
}
|
|
26524
|
+
if (req.method !== "POST") {
|
|
26525
|
+
writeText(res, 405, `Use POST ${MICROVM_CONTROL_PATH}/${action}.`);
|
|
26526
|
+
return;
|
|
26527
|
+
}
|
|
26528
|
+
try {
|
|
26529
|
+
await (action === "suspend" ? target.suspend() : action === "resume" ? target.resume() : target.terminate());
|
|
26530
|
+
} catch (err) {
|
|
26531
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
26532
|
+
writeText(res, err instanceof MicrovmStateError ? 409 : 502, message);
|
|
26533
|
+
return;
|
|
26534
|
+
}
|
|
26535
|
+
writeJson(res, 200, {
|
|
26536
|
+
microvmId: target.microvmId,
|
|
26537
|
+
state: target.state()
|
|
26538
|
+
});
|
|
26539
|
+
}
|
|
26540
|
+
const LOOPBACK_NAMES = [
|
|
26541
|
+
"localhost",
|
|
26542
|
+
"127.0.0.1",
|
|
26543
|
+
"[::1]"
|
|
26544
|
+
];
|
|
26545
|
+
function isWildcard(host) {
|
|
26546
|
+
return host === "0.0.0.0" || host === "::" || host === "[::]";
|
|
26547
|
+
}
|
|
26548
|
+
function isIpLiteral(name) {
|
|
26549
|
+
return /^\d{1,3}(\.\d{1,3}){3}$/.test(name) || /^\[[0-9a-f:.]+\]$/i.test(name);
|
|
26550
|
+
}
|
|
26551
|
+
function controlRequestRefusal(headers, boundHost, boundPort) {
|
|
26552
|
+
const fetchSite = firstHeader(headers["sec-fetch-site"])?.trim().toLowerCase();
|
|
26553
|
+
if (fetchSite !== void 0 && fetchSite !== "same-origin" && fetchSite !== "none") return `Refusing a ${fetchSite} request to the MicroVM control endpoint: it accepts requests from this origin only (curl, or a page this front door served).`;
|
|
26554
|
+
const host = (Array.isArray(headers.host) ? headers.host[0] : headers.host)?.trim().toLowerCase();
|
|
26555
|
+
const m = host === void 0 ? null : /^(\[[^\]]+\]|[^:]+)(?::(\d+))?$/.exec(host);
|
|
26556
|
+
const name = m?.[1];
|
|
26557
|
+
const port = m ? m[2] !== void 0 ? Number(m[2]) : 80 : NaN;
|
|
26558
|
+
const bindName = boundHost.includes(":") && !boundHost.startsWith("[") ? `[${boundHost}]` : boundHost;
|
|
26559
|
+
const loopbackBind = isWildcard(boundHost) || LOOPBACK_NAMES.includes(bindName.toLowerCase());
|
|
26560
|
+
if (!(name !== void 0 && (name === bindName.toLowerCase() || loopbackBind && LOOPBACK_NAMES.includes(name) || isWildcard(boundHost) && isIpLiteral(name))) || port !== boundPort) return `Refusing a MicroVM control request whose Host header (${(host ?? "<none>").slice(0, 64)}) is not this front door's address.`;
|
|
26561
|
+
const origin = firstHeader(headers.origin)?.trim().toLowerCase();
|
|
26562
|
+
if (origin !== void 0 && origin !== `http://${host}`) return `Refusing a MicroVM control request from origin ${origin.slice(0, 64)}: it accepts requests from this origin only.`;
|
|
26563
|
+
}
|
|
26564
|
+
var MicrovmStateError = class MicrovmStateError extends Error {
|
|
26565
|
+
constructor(message) {
|
|
26566
|
+
super(message);
|
|
26567
|
+
this.name = "MicrovmStateError";
|
|
26568
|
+
Object.setPrototypeOf(this, MicrovmStateError.prototype);
|
|
26569
|
+
}
|
|
26570
|
+
};
|
|
26571
|
+
async function handleUpgrade(req, clientSocket, head, opts, track) {
|
|
26572
|
+
const selection = selectTargetPort(req.headers);
|
|
26573
|
+
if (!selection.ok) {
|
|
26574
|
+
writeRawHttpError(clientSocket, selection.status, selection.message);
|
|
26575
|
+
return;
|
|
26576
|
+
}
|
|
26577
|
+
const upstream = opts.target.upstreamFor(selection.port);
|
|
26578
|
+
if (!upstream) {
|
|
26579
|
+
writeRawHttpError(clientSocket, 403, notRoutableMessage(selection.port, opts.target));
|
|
26580
|
+
return;
|
|
26581
|
+
}
|
|
26582
|
+
const refusal = await admit(opts);
|
|
26583
|
+
if (refusal) {
|
|
26584
|
+
writeRawHttpError(clientSocket, refusal.status, refusal.message);
|
|
26585
|
+
return;
|
|
26586
|
+
}
|
|
26587
|
+
const headers = { ...req.headers };
|
|
26588
|
+
const offeredBase = stripMicrovmProxyHeaders(headers);
|
|
26589
|
+
const appProtocolsLeft = headers["sec-websocket-protocol"] !== void 0;
|
|
26590
|
+
const upstreamSocket = connect({
|
|
26591
|
+
host: upstream.host,
|
|
26592
|
+
port: upstream.port
|
|
26593
|
+
});
|
|
26594
|
+
track(upstreamSocket);
|
|
26595
|
+
upstreamSocket.setNoDelay(true);
|
|
26596
|
+
let connected = false;
|
|
26597
|
+
let responseHead = Buffer.alloc(0);
|
|
26598
|
+
upstreamSocket.on("connect", () => {
|
|
26599
|
+
connected = true;
|
|
26600
|
+
const lines = [`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/${req.httpVersion}`];
|
|
26601
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
26602
|
+
if (value === void 0) continue;
|
|
26603
|
+
for (const v of Array.isArray(value) ? value : [value]) lines.push(`${name}: ${v}`);
|
|
26604
|
+
}
|
|
26605
|
+
upstreamSocket.write(`${lines.join("\r\n")}\r\n\r\n`);
|
|
26606
|
+
if (head.length > 0) upstreamSocket.write(head);
|
|
26607
|
+
clientSocket.pipe(upstreamSocket, { end: false });
|
|
26608
|
+
});
|
|
26609
|
+
const onHead = (chunk) => {
|
|
26610
|
+
responseHead = Buffer.concat([responseHead, chunk]);
|
|
26611
|
+
const end = responseHead.indexOf("\r\n\r\n");
|
|
26612
|
+
if (end === -1 && responseHead.length < 64 * 1024) return;
|
|
26613
|
+
upstreamSocket.pause();
|
|
26614
|
+
upstreamSocket.off("data", onHead);
|
|
26615
|
+
const out = end === -1 ? responseHead : Buffer.concat([Buffer.from(rewriteUpgradeResponseHead(responseHead.subarray(0, end).toString("latin1"), offeredBase && !appProtocolsLeft) + "\r\n\r\n", "latin1"), responseHead.subarray(end + 4)]);
|
|
26616
|
+
responseHead = void 0;
|
|
26617
|
+
clientSocket.write(out);
|
|
26618
|
+
upstreamSocket.pipe(clientSocket);
|
|
26619
|
+
};
|
|
26620
|
+
upstreamSocket.on("data", onHead);
|
|
26621
|
+
const closeBoth = () => {
|
|
26622
|
+
if (!clientSocket.destroyed) clientSocket.destroy();
|
|
26623
|
+
if (!upstreamSocket.destroyed) upstreamSocket.destroy();
|
|
26624
|
+
};
|
|
26625
|
+
let answered = false;
|
|
26626
|
+
const answer502 = (why) => {
|
|
26627
|
+
if (answered || clientSocket.destroyed) return;
|
|
26628
|
+
answered = true;
|
|
26629
|
+
writeRawHttpError(clientSocket, 502, `The application in MicroVM ${opts.target.microvmId} ${why} on port ${selection.port}.`);
|
|
26630
|
+
};
|
|
26631
|
+
upstreamSocket.on("end", () => clientSocket.unpipe(upstreamSocket));
|
|
26632
|
+
upstreamSocket.on("error", () => {
|
|
26633
|
+
if (!connected) return answer502("is not responding");
|
|
26634
|
+
if (responseHead === void 0) closeBoth();
|
|
26635
|
+
});
|
|
26636
|
+
upstreamSocket.on("close", (hadError) => {
|
|
26637
|
+
if (responseHead !== void 0) return answer502("closed the connection before answering the upgrade");
|
|
26638
|
+
if (hadError) closeBoth();
|
|
26639
|
+
else if (!clientSocket.writableEnded) clientSocket.end();
|
|
26640
|
+
});
|
|
26641
|
+
clientSocket.on("error", closeBoth);
|
|
26642
|
+
clientSocket.on("end", () => {
|
|
26643
|
+
if (!upstreamSocket.destroyed && !upstreamSocket.writableEnded) upstreamSocket.end();
|
|
26644
|
+
});
|
|
26645
|
+
clientSocket.on("close", () => {
|
|
26646
|
+
if (!upstreamSocket.destroyed) upstreamSocket.destroy();
|
|
26647
|
+
});
|
|
26648
|
+
}
|
|
26649
|
+
function rewriteUpgradeResponseHead(head, addBase) {
|
|
26650
|
+
if (!addBase) return head;
|
|
26651
|
+
const lines = head.split("\r\n");
|
|
26652
|
+
if (!/^HTTP\/1\.[01] 101\b/.test(lines[0] ?? "")) return head;
|
|
26653
|
+
if (lines.some((l) => /^sec-websocket-protocol\s*:/i.test(l))) return head;
|
|
26654
|
+
return [...lines, `Sec-WebSocket-Protocol: ${MICROVM_WS_BASE_PROTOCOL}`].join("\r\n");
|
|
26655
|
+
}
|
|
26656
|
+
function notRoutableMessage(port, target) {
|
|
26657
|
+
return `Port ${port} of MicroVM ${target.microvmId} is not reachable locally. cdk-local publishes 8080, Hooks.Port, the image's EXPOSE ports and every --proxy-port; published: ${target.routablePorts().join(", ")}.`;
|
|
26658
|
+
}
|
|
26659
|
+
function writeText(res, status, message) {
|
|
26660
|
+
res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
|
|
26661
|
+
res.end(`${message}\n`);
|
|
26662
|
+
}
|
|
26663
|
+
function writeJson(res, status, body) {
|
|
26664
|
+
res.writeHead(status, { "content-type": "application/json" });
|
|
26665
|
+
res.end(JSON.stringify(body));
|
|
26666
|
+
}
|
|
26667
|
+
|
|
26668
|
+
//#endregion
|
|
26669
|
+
//#region src/local/microvm-runner.ts
|
|
26670
|
+
const execFileAsync = promisify$1(execFile$1);
|
|
26671
|
+
const DEFAULT_TERMINATE_HOOK_TIMEOUT_MS = 1e4;
|
|
26672
|
+
function microvmImageTag(artifact) {
|
|
26673
|
+
return `${getEmbedConfig().resourceNamePrefix}-microvm-${artifact.assetHash.slice(0, 16).toLowerCase()}`;
|
|
26674
|
+
}
|
|
26675
|
+
async function buildMicrovmImage(opts) {
|
|
26676
|
+
const tag = microvmImageTag(opts.artifact);
|
|
26677
|
+
const parent = dirname$1(opts.artifact.contextDir);
|
|
26678
|
+
getLogger().info(`Building MicroVM image ${tag}${opts.platform ? ` (platform=${opts.platform})` : ""}...`);
|
|
26679
|
+
await buildDockerImage({ source: {
|
|
26680
|
+
directory: basename$1(opts.artifact.contextDir),
|
|
26681
|
+
...opts.noCache && { cacheDisabled: true }
|
|
26682
|
+
} }, parent, {
|
|
26683
|
+
tag,
|
|
26684
|
+
...opts.platform !== void 0 && { platform: opts.platform },
|
|
26685
|
+
assetOutdir: parent,
|
|
26686
|
+
progressLabel: "Building MicroVM image",
|
|
26687
|
+
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for the MicroVM image's Dockerfile: ${stderr}`)
|
|
26688
|
+
});
|
|
26689
|
+
return tag;
|
|
26690
|
+
}
|
|
26691
|
+
async function readExposedPorts(image) {
|
|
26692
|
+
try {
|
|
26693
|
+
const { stdout } = await execFileAsync(getDockerCmd(), [
|
|
26694
|
+
"image",
|
|
26695
|
+
"inspect",
|
|
26696
|
+
"-f",
|
|
26697
|
+
"{{json .Config.ExposedPorts}}",
|
|
26698
|
+
image
|
|
26699
|
+
]);
|
|
26700
|
+
const parsed = JSON.parse(stdout.trim() || "null");
|
|
26701
|
+
if (!parsed) return [];
|
|
26702
|
+
return Object.keys(parsed).filter((k) => k.endsWith("/tcp") || !k.includes("/")).map((k) => Number(k.split("/")[0])).filter((n) => Number.isInteger(n) && n > 0 && n < 65536);
|
|
26703
|
+
} catch {
|
|
26704
|
+
return [];
|
|
26705
|
+
}
|
|
26706
|
+
}
|
|
26707
|
+
function planMicrovmPorts(hooksPort, exposed, extra) {
|
|
26708
|
+
const out = [MICROVM_DEFAULT_INGRESS_PORT];
|
|
26709
|
+
for (const p of [
|
|
26710
|
+
...hooksPort !== void 0 ? [hooksPort] : [],
|
|
26711
|
+
...exposed,
|
|
26712
|
+
...extra
|
|
26713
|
+
]) if (!out.includes(p)) out.push(p);
|
|
26714
|
+
return out;
|
|
26715
|
+
}
|
|
26716
|
+
async function setContainerPaused(containerId, paused) {
|
|
26717
|
+
try {
|
|
26718
|
+
await execFileAsync(getDockerCmd(), [paused ? "pause" : "unpause", containerId]);
|
|
26719
|
+
} catch (error) {
|
|
26720
|
+
const err = error;
|
|
26721
|
+
throw new CdkLocalError(`docker ${paused ? "pause" : "unpause"} failed: ${displayToolOutput(err.stderr?.trim() || err.message || String(error))}`, "LOCAL_START_MICROVM_PAUSE_FAILED");
|
|
26722
|
+
}
|
|
26723
|
+
}
|
|
26724
|
+
const defaultDeps = {
|
|
26725
|
+
setPaused: setContainerPaused,
|
|
26726
|
+
remove: removeContainer
|
|
26727
|
+
};
|
|
26728
|
+
var LocalMicrovm = class {
|
|
26729
|
+
microvmId;
|
|
26730
|
+
containerId;
|
|
26731
|
+
current = "booting";
|
|
26732
|
+
resuming;
|
|
26733
|
+
suspending;
|
|
26734
|
+
paused = false;
|
|
26735
|
+
terminating;
|
|
26736
|
+
opts;
|
|
26737
|
+
deps;
|
|
26738
|
+
constructor(opts, deps = {}) {
|
|
26739
|
+
this.opts = opts;
|
|
26740
|
+
this.deps = {
|
|
26741
|
+
...defaultDeps,
|
|
26742
|
+
...deps
|
|
26743
|
+
};
|
|
26744
|
+
this.microvmId = opts.microvmId;
|
|
26745
|
+
this.containerId = opts.containerId;
|
|
26746
|
+
}
|
|
26747
|
+
state() {
|
|
26748
|
+
return this.current === "booting" ? "resuming" : this.current;
|
|
26749
|
+
}
|
|
26750
|
+
upstreamFor(containerPort) {
|
|
26751
|
+
return this.opts.ports.get(containerPort);
|
|
26752
|
+
}
|
|
26753
|
+
routablePorts() {
|
|
26754
|
+
return [...this.opts.ports.keys()];
|
|
26755
|
+
}
|
|
26756
|
+
log(message) {
|
|
26757
|
+
getLogger().info(`MicroVM ${this.microvmId}: ${message}`);
|
|
26758
|
+
}
|
|
26759
|
+
hookEndpoint() {
|
|
26760
|
+
const port = this.opts.config.hooksPort;
|
|
26761
|
+
const endpoint = port !== void 0 ? this.opts.ports.get(port) : void 0;
|
|
26762
|
+
if (!endpoint) throw new CdkLocalError("The hooks port is not published.", "LOCAL_START_MICROVM_HOOK_FAILED");
|
|
26763
|
+
return endpoint;
|
|
26764
|
+
}
|
|
26765
|
+
hookTimeoutMs(hook) {
|
|
26766
|
+
const seconds = this.opts.config.hooks[hook]?.timeoutSeconds;
|
|
26767
|
+
if (seconds !== void 0) return seconds * 1e3;
|
|
26768
|
+
return hook === "terminate" ? DEFAULT_TERMINATE_HOOK_TIMEOUT_MS : this.opts.timeoutMs;
|
|
26769
|
+
}
|
|
26770
|
+
hookDeps() {
|
|
26771
|
+
return {
|
|
26772
|
+
...this.deps.hooks ?? defaultHookDeps,
|
|
26773
|
+
...this.deps.isAlive && { isAlive: this.deps.isAlive }
|
|
26774
|
+
};
|
|
26775
|
+
}
|
|
26776
|
+
async runtimeHook(hook, body) {
|
|
26777
|
+
if (!this.opts.config.hooks[hook]) return false;
|
|
26778
|
+
await callRuntimeHook(hook, this.hookEndpoint(), this.hookTimeoutMs(hook), body, this.hookDeps());
|
|
26779
|
+
this.log(`/${hook} hook answered 200`);
|
|
26780
|
+
return true;
|
|
26781
|
+
}
|
|
26782
|
+
async boot(runHookPayload) {
|
|
26783
|
+
const { hooks } = this.opts.config;
|
|
26784
|
+
const deps = this.hookDeps();
|
|
26785
|
+
if (hooks.ready) {
|
|
26786
|
+
const attempts = await pollBuildHook("ready", this.hookEndpoint(), this.hookTimeoutMs("ready"), deps);
|
|
26787
|
+
this.log(`/ready hook answered 200 after ${attempts} attempt(s)`);
|
|
26788
|
+
} else {
|
|
26789
|
+
const port = this.opts.config.hooksPort ?? 8080;
|
|
26790
|
+
const endpoint = this.opts.ports.get(port);
|
|
26791
|
+
await (this.deps.waitForAnswer ?? waitForAppAnswer)(endpoint, this.opts.timeoutMs, {
|
|
26792
|
+
probe: probeHttp,
|
|
26793
|
+
sleep: deps.sleep,
|
|
26794
|
+
now: deps.now,
|
|
26795
|
+
...this.deps.isAlive && { isAlive: this.deps.isAlive }
|
|
26796
|
+
});
|
|
26797
|
+
this.log("application answered HTTP (no /ready hook)");
|
|
26798
|
+
}
|
|
26799
|
+
if (hooks.validate) {
|
|
26800
|
+
const attempts = await pollBuildHook("validate", this.hookEndpoint(), this.hookTimeoutMs("validate"), deps);
|
|
26801
|
+
this.log(`/validate hook answered 200 after ${attempts} attempt(s)`);
|
|
26802
|
+
}
|
|
26803
|
+
await this.runtimeHook("run", runHookBody(this.microvmId, runHookPayload));
|
|
26804
|
+
if (this.current === "booting") this.current = "running";
|
|
26805
|
+
}
|
|
26806
|
+
async setPaused(paused) {
|
|
26807
|
+
await this.deps.setPaused(this.containerId, paused);
|
|
26808
|
+
this.paused = paused;
|
|
26809
|
+
}
|
|
26810
|
+
suspend() {
|
|
26811
|
+
if (this.current !== "running") return Promise.reject(new MicrovmStateError(`MicroVM ${this.microvmId} is ${this.state()}; only a running MicroVM can be suspended.`));
|
|
26812
|
+
this.current = "suspending";
|
|
26813
|
+
this.suspending = (async () => {
|
|
26814
|
+
try {
|
|
26815
|
+
await this.runtimeHook("suspend");
|
|
26816
|
+
await this.setPaused(true);
|
|
26817
|
+
} catch (err) {
|
|
26818
|
+
if (this.current === "suspending") this.current = "running";
|
|
26819
|
+
throw err;
|
|
26820
|
+
} finally {
|
|
26821
|
+
this.suspending = void 0;
|
|
26822
|
+
}
|
|
26823
|
+
if (this.current === "suspending") {
|
|
26824
|
+
this.current = "suspended";
|
|
26825
|
+
this.log("suspended (container paused)");
|
|
26826
|
+
}
|
|
26827
|
+
})();
|
|
26828
|
+
return this.suspending;
|
|
26829
|
+
}
|
|
26830
|
+
resume() {
|
|
26831
|
+
if (this.current === "resuming" && this.resuming) return this.resuming;
|
|
26832
|
+
if (this.current === "suspending" && this.suspending) return this.suspending.then(() => this.resume(), () => void 0);
|
|
26833
|
+
if (this.current !== "suspended") return Promise.reject(new MicrovmStateError(`MicroVM ${this.microvmId} is ${this.state()}; only a suspended MicroVM can be resumed.`));
|
|
26834
|
+
this.current = "resuming";
|
|
26835
|
+
this.resuming = (async () => {
|
|
26836
|
+
try {
|
|
26837
|
+
await this.setPaused(false);
|
|
26838
|
+
try {
|
|
26839
|
+
await this.runtimeHook("resume");
|
|
26840
|
+
} catch (err) {
|
|
26841
|
+
if (this.current === "resuming") await this.setPaused(true).catch(() => void 0);
|
|
26842
|
+
throw err;
|
|
26843
|
+
}
|
|
26844
|
+
} catch (err) {
|
|
26845
|
+
if (this.current === "resuming") this.current = "suspended";
|
|
26846
|
+
throw err;
|
|
26847
|
+
} finally {
|
|
26848
|
+
this.resuming = void 0;
|
|
26849
|
+
}
|
|
26850
|
+
if (this.current === "resuming") {
|
|
26851
|
+
this.current = "running";
|
|
26852
|
+
this.log("resumed");
|
|
26853
|
+
}
|
|
26854
|
+
})();
|
|
26855
|
+
return this.resuming;
|
|
26856
|
+
}
|
|
26857
|
+
terminate() {
|
|
26858
|
+
this.terminating ??= (async () => {
|
|
26859
|
+
const wasBooting = this.current === "booting";
|
|
26860
|
+
this.current = "terminating";
|
|
26861
|
+
await Promise.allSettled([this.suspending, this.resuming].filter((p) => p !== void 0));
|
|
26862
|
+
try {
|
|
26863
|
+
if (this.paused) await this.setPaused(false).catch(() => void 0);
|
|
26864
|
+
if (!wasBooting) await this.runtimeHook("terminate");
|
|
26865
|
+
} catch (err) {
|
|
26866
|
+
getLogger().warn(`MicroVM ${this.microvmId}: ${err instanceof Error ? err.message : String(err)} Removing its container anyway.`);
|
|
26867
|
+
}
|
|
26868
|
+
await this.deps.remove(this.containerId);
|
|
26869
|
+
this.current = "terminated";
|
|
26870
|
+
this.log("terminated (container removed)");
|
|
26871
|
+
this.opts.onTerminated?.(this);
|
|
26872
|
+
})();
|
|
26873
|
+
return this.terminating;
|
|
26874
|
+
}
|
|
26875
|
+
};
|
|
26876
|
+
|
|
26877
|
+
//#endregion
|
|
26878
|
+
//#region src/cli/commands/local-start-microvm.ts
|
|
26879
|
+
function parsePort(raw) {
|
|
26880
|
+
const n = Number(raw);
|
|
26881
|
+
if (!Number.isInteger(n) || n < 0 || n > 65535) throw new InvalidArgumentError(`--port must be an integer 0-65535, got '${raw}'.`);
|
|
26882
|
+
return n;
|
|
26883
|
+
}
|
|
26884
|
+
function parseCount(raw) {
|
|
26885
|
+
const n = Number(raw);
|
|
26886
|
+
if (!Number.isInteger(n) || n < 1 || n > 64) throw new InvalidArgumentError(`--count must be an integer 1-64, got '${raw}'.`);
|
|
26887
|
+
return n;
|
|
26888
|
+
}
|
|
26889
|
+
function parseProxyPort(raw, previous) {
|
|
26890
|
+
const n = Number(raw);
|
|
26891
|
+
if (!Number.isInteger(n) || n < 1 || n > 65535) throw new InvalidArgumentError(`--proxy-port must be an integer 1-65535, got '${raw}'.`);
|
|
26892
|
+
return [...previous ?? [], n];
|
|
26893
|
+
}
|
|
26894
|
+
function connectHost(host) {
|
|
26895
|
+
if (host === "0.0.0.0") return "127.0.0.1";
|
|
26896
|
+
if (host === "::" || host === "[::]") return "::1";
|
|
26897
|
+
return host;
|
|
26898
|
+
}
|
|
26899
|
+
function readRunHookPayload(options, readFile = (p) => readFileSync$1(p, "utf-8")) {
|
|
26900
|
+
if (options.runHookPayload !== void 0 && options.runHookPayloadFile !== void 0) throw new CdkLocalError("--run-hook-payload and --run-hook-payload-file are mutually exclusive.", "LOCAL_START_MICROVM_PAYLOAD_CONFLICT");
|
|
26901
|
+
let payload = options.runHookPayload;
|
|
26902
|
+
if (options.runHookPayloadFile !== void 0) try {
|
|
26903
|
+
payload = readFile(options.runHookPayloadFile);
|
|
26904
|
+
} catch (err) {
|
|
26905
|
+
throw new CdkLocalError(`Cannot read --run-hook-payload-file ${displayUntrustedValue(options.runHookPayloadFile)}: ${err instanceof Error ? err.message : String(err)}`, "LOCAL_START_MICROVM_PAYLOAD_UNREADABLE");
|
|
26906
|
+
}
|
|
26907
|
+
if (payload !== void 0 && Buffer.byteLength(payload) > 16384) throw new CdkLocalError(`The run hook payload is ${Buffer.byteLength(payload)} bytes; run-microvm accepts at most ${MAX_RUN_HOOK_PAYLOAD_BYTES} (16 KB).`, "LOCAL_START_MICROVM_PAYLOAD_TOO_LARGE");
|
|
26908
|
+
return payload;
|
|
26909
|
+
}
|
|
26910
|
+
function buildMicrovmLambdaView(resolved, config) {
|
|
26911
|
+
return {
|
|
26912
|
+
kind: "zip",
|
|
26913
|
+
stack: resolved.stack,
|
|
26914
|
+
logicalId: resolved.logicalId,
|
|
26915
|
+
resource: {
|
|
26916
|
+
Type: resolved.resource.Type,
|
|
26917
|
+
Properties: { Environment: { Variables: config.environment } },
|
|
26918
|
+
...resolved.resource.Metadata !== void 0 && { Metadata: resolved.resource.Metadata }
|
|
26919
|
+
},
|
|
26920
|
+
memoryMb: 128,
|
|
26921
|
+
timeoutSec: 0,
|
|
26922
|
+
layers: [],
|
|
26923
|
+
runtime: "",
|
|
26924
|
+
handler: "",
|
|
26925
|
+
codePath: null,
|
|
26926
|
+
architecture: config.architecture ?? "arm64"
|
|
26927
|
+
};
|
|
26928
|
+
}
|
|
26929
|
+
const LAMBDA_RUNTIME_ENV_KEYS = [
|
|
26930
|
+
"AWS_LAMBDA_FUNCTION_NAME",
|
|
26931
|
+
"AWS_LAMBDA_FUNCTION_MEMORY_SIZE",
|
|
26932
|
+
"AWS_LAMBDA_FUNCTION_TIMEOUT",
|
|
26933
|
+
"AWS_LAMBDA_FUNCTION_VERSION",
|
|
26934
|
+
"AWS_LAMBDA_LOG_GROUP_NAME",
|
|
26935
|
+
"AWS_LAMBDA_LOG_STREAM_NAME"
|
|
26936
|
+
];
|
|
26937
|
+
function finalizeMicrovmEnv(lambdaEnv, declared) {
|
|
26938
|
+
const env = { ...lambdaEnv };
|
|
26939
|
+
for (const key of LAMBDA_RUNTIME_ENV_KEYS) if (!Object.hasOwn(declared, key)) delete env[key];
|
|
26940
|
+
return env;
|
|
26941
|
+
}
|
|
26942
|
+
function microvmFidelityWarnings(config) {
|
|
26943
|
+
const image = displayIdent(config.logicalId);
|
|
26944
|
+
const out = [
|
|
26945
|
+
`MicroVM image ${image}: each MicroVM is a Docker container started fresh from the built image, not a MicroVM restored from a snapshot — state the app initializes before /ready answers 200 is NOT shared across MicroVMs (each one runs its own startup), and there is no VM isolation or memory checkpoint (suspend is docker pause).`,
|
|
26946
|
+
`MicroVM image ${image}: requests are accepted without X-aws-proxy-auth — the JWE token the cloud endpoint requires is not checked locally (the header is still stripped).`,
|
|
26947
|
+
`MicroVM image ${image}: the idle policy is not emulated — a MicroVM suspends, resumes and terminates only through ${MICROVM_CONTROL_PATH}/suspend|resume|terminate (and --auto-resume).`,
|
|
26948
|
+
`MicroVM image ${image}: the Dockerfile is built by the local Docker engine, not on the Lambda-managed MicroVM base OS (BaseImageArn).`
|
|
26949
|
+
];
|
|
26950
|
+
if (config.hooks.validate) out.push(`MicroVM image ${image}: /validate is called on the same container as /ready (the cloud calls it on a fresh MicroVM started from the built snapshot).`);
|
|
26951
|
+
for (const what of config.unemulated) out.push(`MicroVM image ${image}: ${what} is not emulated.`);
|
|
26952
|
+
return out;
|
|
26953
|
+
}
|
|
26954
|
+
async function localStartMicrovmCommand(target, options, extraStateProviders, hostSynthesizer) {
|
|
26955
|
+
const logger = getLogger();
|
|
26956
|
+
if (options.verbose) logger.setLevel("debug");
|
|
26957
|
+
const runHookPayload = readRunHookPayload(options);
|
|
26958
|
+
const vms = [];
|
|
26959
|
+
const frontDoors = /* @__PURE__ */ new Map();
|
|
26960
|
+
const logStops = [];
|
|
26961
|
+
const exitWatches = [];
|
|
26962
|
+
const bareContainers = /* @__PURE__ */ new Set();
|
|
26963
|
+
let runInFlight;
|
|
26964
|
+
let containerName;
|
|
26965
|
+
let artifact;
|
|
26966
|
+
let teardownPromise;
|
|
26967
|
+
let shuttingDown = false;
|
|
26968
|
+
let waitingOnRun = false;
|
|
26969
|
+
const forever = () => new Promise(() => void 0);
|
|
26970
|
+
const doTeardown = async () => {
|
|
26971
|
+
for (const w of exitWatches) w.stop();
|
|
26972
|
+
if (runInFlight !== void 0) {
|
|
26973
|
+
waitingOnRun = true;
|
|
26974
|
+
try {
|
|
26975
|
+
bareContainers.add(await runInFlight);
|
|
26976
|
+
} catch {} finally {
|
|
26977
|
+
waitingOnRun = false;
|
|
26978
|
+
}
|
|
26979
|
+
}
|
|
26980
|
+
await Promise.allSettled(vms.map((vm) => vm.terminate()));
|
|
26981
|
+
for (const vm of vms) bareContainers.delete(vm.containerId);
|
|
26982
|
+
await Promise.allSettled([...bareContainers].map((id) => removeContainer(id)));
|
|
26983
|
+
await Promise.allSettled([...frontDoors.values()].map((fd) => fd.close()));
|
|
26984
|
+
for (const stop of logStops) try {
|
|
26985
|
+
stop();
|
|
26986
|
+
} catch (err) {
|
|
26987
|
+
logger.debug(`streamLogs stop failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
26988
|
+
}
|
|
26989
|
+
disposeMicrovmCodeArtifact(artifact);
|
|
26990
|
+
};
|
|
26991
|
+
const teardown = () => teardownPromise ??= doTeardown();
|
|
26992
|
+
await applyRoleArnIfSet({
|
|
26993
|
+
roleArn: options.roleArn,
|
|
26994
|
+
region: options.region,
|
|
26995
|
+
profile: options.profile
|
|
26996
|
+
});
|
|
26997
|
+
await ensureDockerAvailable();
|
|
26998
|
+
const profileCredentials = options.profile ? await resolveProfileCredentials(options.profile) : void 0;
|
|
26999
|
+
const appCmd = resolveApp(options.app);
|
|
27000
|
+
if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
|
|
27001
|
+
logger.info("Synthesizing CDK app...");
|
|
27002
|
+
const synthesizer = hostSynthesizer ?? new Synthesizer();
|
|
27003
|
+
const context = parseContextOptions(options.context);
|
|
27004
|
+
const synthOpts = {
|
|
27005
|
+
app: appCmd,
|
|
27006
|
+
output: options.output,
|
|
27007
|
+
...options.region && { region: options.region },
|
|
27008
|
+
...options.profile && { profile: options.profile },
|
|
27009
|
+
...Object.keys(context).length > 0 && { context }
|
|
27010
|
+
};
|
|
27011
|
+
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
27012
|
+
const resolved = resolveMicrovmImageTarget(await resolveSingleTarget(target, {
|
|
27013
|
+
entries: listTargets(stacks).microvmImages,
|
|
27014
|
+
message: "Select a Lambda MicroVM image to run",
|
|
27015
|
+
noun: "Lambda MicroVM images",
|
|
27016
|
+
onMissing: () => new CdkLocalError(`${getEmbedConfig().cliName} start-microvm requires a <target> (a MicrovmImage display path or logical ID). Run \`${getEmbedConfig().cliName} list\` to see them, or run it in a TTY to pick interactively.`, "LOCAL_START_MICROVM_TARGET_REQUIRED")
|
|
27017
|
+
}), stacks);
|
|
27018
|
+
const config = readMicrovmImageConfig(resolved.logicalId, resolved.resource);
|
|
27019
|
+
const platform = options.platform ?? (config.architecture !== void 0 ? architectureToPlatform(config.architecture) : void 0);
|
|
27020
|
+
logger.info(`Target: ${displayStackName(`${resolved.stack.stackName}/${resolved.logicalId}`)} (MicroVM image ${displayIdent(config.name)}; hooks: ${Object.keys(config.hooks).join(", ") || "none"}${platform ? `; ${platform}` : ""})`);
|
|
27021
|
+
for (const line of microvmFidelityWarnings(config)) logger.warn(line);
|
|
27022
|
+
artifact = await resolveMicrovmCodeArtifact(resolved.stack, config);
|
|
27023
|
+
const shutdown = async (signal, exitCode) => {
|
|
27024
|
+
if (shuttingDown) {
|
|
27025
|
+
if (!waitingOnRun) {
|
|
27026
|
+
logger.info(`Received ${signal} again; still terminating the MicroVMs, please wait...`);
|
|
27027
|
+
return;
|
|
27028
|
+
}
|
|
27029
|
+
disposeMicrovmCodeArtifact(artifact);
|
|
27030
|
+
logger.warn(`Received ${signal} again; exiting without waiting for docker run to return. The container may still be starting, and its environment holds the forwarded AWS credentials. Remove it by its name (this works even if it is created after this exit): docker rm -f ${containerName ?? "<name>"}`);
|
|
27031
|
+
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
27032
|
+
return;
|
|
27033
|
+
}
|
|
27034
|
+
shuttingDown = true;
|
|
27035
|
+
logger.info(`Received ${signal}, terminating ${vms.length} MicroVM(s)...`);
|
|
27036
|
+
await teardown();
|
|
27037
|
+
process.exit(exitCode);
|
|
27038
|
+
};
|
|
27039
|
+
process.on("SIGINT", () => void shutdown("SIGINT", 130));
|
|
27040
|
+
process.on("SIGTERM", () => void shutdown("SIGTERM", 0));
|
|
27041
|
+
const recents = /* @__PURE__ */ new Map();
|
|
27042
|
+
let allTerminated;
|
|
27043
|
+
const everyVmTerminated = new Promise((r) => allTerminated = r);
|
|
27044
|
+
const checkAllTerminated = () => {
|
|
27045
|
+
if (vms.length === options.count && vms.every((v) => v.state() === "terminated")) allTerminated();
|
|
27046
|
+
};
|
|
27047
|
+
let unexpectedExit;
|
|
27048
|
+
const anyUnexpectedExit = new Promise((r) => unexpectedExit = r);
|
|
27049
|
+
const probeHost = connectHost(options.host);
|
|
27050
|
+
try {
|
|
27051
|
+
const containerEnv = await resolveLambdaContainerEnv(buildMicrovmLambdaView(resolved, config), {
|
|
27052
|
+
...options,
|
|
27053
|
+
deployedFunctionLookups: false
|
|
27054
|
+
}, profileCredentials, extraStateProviders);
|
|
27055
|
+
const env = finalizeMicrovmEnv(containerEnv.env, config.environment);
|
|
27056
|
+
const image = await buildMicrovmImage({
|
|
27057
|
+
artifact,
|
|
27058
|
+
platform,
|
|
27059
|
+
noCache: options.buildCache === false
|
|
27060
|
+
});
|
|
27061
|
+
if (teardownPromise) return await forever();
|
|
27062
|
+
const containerPorts = planMicrovmPorts(config.hooksPort, await readExposedPorts(image), options.proxyPort ?? []);
|
|
27063
|
+
logger.info(`Publishing container ports ${containerPorts.join(", ")} to the front door(s).`);
|
|
27064
|
+
for (let i = 0; i < options.count; i++) {
|
|
27065
|
+
const microvmId = generateMicrovmId();
|
|
27066
|
+
const hostPorts = /* @__PURE__ */ new Map();
|
|
27067
|
+
for (const cp of containerPorts) hostPorts.set(cp, {
|
|
27068
|
+
host: "127.0.0.1",
|
|
27069
|
+
port: await pickFreePort()
|
|
27070
|
+
});
|
|
27071
|
+
const [first, ...rest] = containerPorts;
|
|
27072
|
+
const name = containerName = `${getEmbedConfig().resourceNamePrefix}-microvm-${process.pid}-${i}-${Math.random().toString(36).slice(2, 8)}`;
|
|
27073
|
+
logger.info(`Starting MicroVM ${microvmId} (container ${name})...`);
|
|
27074
|
+
runInFlight = runDetached({
|
|
27075
|
+
image,
|
|
27076
|
+
mounts: [],
|
|
27077
|
+
env,
|
|
27078
|
+
...containerEnv.sensitiveEnvKeys.length > 0 && { sensitiveEnvKeys: new Set(containerEnv.sensitiveEnvKeys) },
|
|
27079
|
+
cmd: [],
|
|
27080
|
+
hostPort: hostPorts.get(first).port,
|
|
27081
|
+
containerPort: first,
|
|
27082
|
+
extraPorts: rest.map((cp) => ({
|
|
27083
|
+
hostPort: hostPorts.get(cp).port,
|
|
27084
|
+
containerPort: cp
|
|
27085
|
+
})),
|
|
27086
|
+
host: "127.0.0.1",
|
|
27087
|
+
...platform !== void 0 && { platform },
|
|
27088
|
+
name,
|
|
27089
|
+
autoRemove: false
|
|
27090
|
+
});
|
|
27091
|
+
const containerId = await runInFlight;
|
|
27092
|
+
runInFlight = void 0;
|
|
27093
|
+
bareContainers.add(containerId);
|
|
27094
|
+
if (teardownPromise) return await forever();
|
|
27095
|
+
const recent = new RecentOutput();
|
|
27096
|
+
recents.set(containerId, recent);
|
|
27097
|
+
logStops.push(streamLogs(containerId, {
|
|
27098
|
+
onOutput: (chunk) => recent.push(chunk),
|
|
27099
|
+
...options.count > 1 && { prefix: `[${microvmId.slice(0, 12)}] ` }
|
|
27100
|
+
}));
|
|
27101
|
+
const watch = watchContainerExit(containerId);
|
|
27102
|
+
exitWatches.push(watch);
|
|
27103
|
+
let exited = false;
|
|
27104
|
+
const vm = new LocalMicrovm({
|
|
27105
|
+
microvmId,
|
|
27106
|
+
containerId,
|
|
27107
|
+
ports: hostPorts,
|
|
27108
|
+
config,
|
|
27109
|
+
timeoutMs: options.timeout,
|
|
27110
|
+
onTerminated: checkAllTerminated
|
|
27111
|
+
}, { isAlive: async () => !exited });
|
|
27112
|
+
vms.push(vm);
|
|
27113
|
+
bareContainers.delete(containerId);
|
|
27114
|
+
watch.exited.then((code) => {
|
|
27115
|
+
exited = true;
|
|
27116
|
+
const s = vm.state();
|
|
27117
|
+
if (s !== "terminating" && s !== "terminated") unexpectedExit({
|
|
27118
|
+
vm,
|
|
27119
|
+
code
|
|
27120
|
+
});
|
|
27121
|
+
});
|
|
27122
|
+
await vm.boot(runHookPayload);
|
|
27123
|
+
if (teardownPromise) return await forever();
|
|
27124
|
+
const fd = await startMicrovmFrontDoor({
|
|
27125
|
+
host: options.host,
|
|
27126
|
+
port: options.port === 0 ? 0 : options.port + i,
|
|
27127
|
+
target: vm,
|
|
27128
|
+
autoResume: options.autoResume === true
|
|
27129
|
+
});
|
|
27130
|
+
frontDoors.set(vm, fd);
|
|
27131
|
+
const base = `http://${formatAuthority(probeHost, fd.port)}`;
|
|
27132
|
+
logger.info(`MicroVM ${microvmId} listening on ${base} (control: ${base}${MICROVM_CONTROL_PATH})`);
|
|
27133
|
+
}
|
|
27134
|
+
logger.info("Press ^C to terminate the MicroVM(s).");
|
|
27135
|
+
checkAllTerminated();
|
|
27136
|
+
} catch (err) {
|
|
27137
|
+
if (shuttingDown) return await forever();
|
|
27138
|
+
const failed = vms.at(-1);
|
|
27139
|
+
const wrapped = err instanceof MicrovmHookError && failed ? await containerFailure(err.message, failed.containerId, recents.get(failed.containerId) ?? new RecentOutput()).then((f) => new CdkLocalError(f.message, err.code)) : err;
|
|
27140
|
+
await teardown();
|
|
27141
|
+
throw wrapped;
|
|
27142
|
+
}
|
|
27143
|
+
const outcome = await Promise.race([everyVmTerminated.then(() => void 0), anyUnexpectedExit]);
|
|
27144
|
+
if (shuttingDown) return await forever();
|
|
27145
|
+
if (outcome === void 0) {
|
|
27146
|
+
logger.info("Every MicroVM is terminated; shutting down.");
|
|
27147
|
+
await teardown();
|
|
27148
|
+
return;
|
|
27149
|
+
}
|
|
27150
|
+
if (teardownPromise) return await forever();
|
|
27151
|
+
const failure = await containerFailure(`MicroVM ${outcome.vm.microvmId}'s container exited (exit code ${outcome.code ?? "unknown"}) while running.`, outcome.vm.containerId, recents.get(outcome.vm.containerId) ?? new RecentOutput());
|
|
27152
|
+
await teardown();
|
|
27153
|
+
throw new CdkLocalError(failure.message, "LOCAL_START_MICROVM_EXITED");
|
|
27154
|
+
}
|
|
27155
|
+
function createLocalStartMicrovmCommand(opts = {}) {
|
|
27156
|
+
setEmbedConfig(opts.embedConfig);
|
|
27157
|
+
const cmd = new Command("start-microvm").description("Run AWS Lambda MicroVMs locally. Builds a MicrovmImage code artifact (a Dockerfile plus the app, from cdk.out) once, starts --count containers with its EnvironmentVariables, drives the /ready, /validate and /run lifecycle hooks in the cloud order, and serves each MicroVM on its own local URL: X-aws-proxy-port / lambda-microvms.port.N routing (default 8080), X-aws-proxy-* stripping, and a control endpoint for suspend / resume / terminate. Target accepts a CDK display path or stack-qualified logical ID; single-stack apps may omit the prefix. Omit <target> in a TTY to pick from a list. Runs until ^C, which calls /terminate.").argument("[target]", "CDK display path or stack-qualified logical ID of the MicrovmImage (omit to pick interactively in a TTY)").action(withErrorHandling(async (target, options) => {
|
|
27158
|
+
await localStartMicrovmCommand(target, options, opts.extraStateProviders, opts.synthesizer);
|
|
27159
|
+
}));
|
|
27160
|
+
addStartMicrovmSpecificOptions(cmd);
|
|
27161
|
+
[
|
|
27162
|
+
...commonOptions(),
|
|
27163
|
+
...appOptions(),
|
|
27164
|
+
...contextOptions
|
|
27165
|
+
].forEach((opt) => cmd.addOption(opt));
|
|
27166
|
+
cmd.addOption(regionOption);
|
|
27167
|
+
return cmd;
|
|
27168
|
+
}
|
|
27169
|
+
function addStartMicrovmSpecificOptions(cmd) {
|
|
27170
|
+
return cmd.addOption(new Option("--count <n>", "Number of independent MicroVMs to run, each with its own URL.").default(1).argParser(parseCount)).addOption(new Option("--run-hook-payload <string>", "The runHookPayload string delivered to each MicroVM's /run hook (max 16 KB).")).addOption(new Option("--run-hook-payload-file <path>", "Read the runHookPayload from a file (mutually exclusive with --run-hook-payload).")).addOption(new Option("--port <n>", "Local port of the first MicroVM; MicroVM i listens on <n>+i. Default 0 (OS-assigned).").default(0).argParser(parsePort)).addOption(new Option("--host <ip>", "Address the MicroVM URLs listen on.").default("127.0.0.1")).addOption(new Option("--platform <platform>", "docker --platform for the build and the containers. Defaults to the image's CpuConfigurations architecture.").choices(["linux/amd64", "linux/arm64"])).addOption(new Option("--proxy-port <port>", "An extra container port X-aws-proxy-port may route to (repeatable). 8080, Hooks.Port and the image's EXPOSE ports are always routable.").argParser(parseProxyPort)).addOption(new Option("--auto-resume", "Resume a suspended MicroVM on its next request instead of answering 503 (autoResumeEnabled).")).addOption(new Option("--no-build-cache", "Build the image with docker build --no-cache.")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"MicrovmImageLogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--timeout <ms>", "Default deadline in milliseconds for each hook without its own <Hook>TimeoutInSeconds (/terminate defaults to 10000), and for the app to answer HTTP when there is no /ready hook. Default 60000.").default(6e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role <arn>", "Assume this role (the MicroVM's execution role, a run-microvm parameter rather than part of the template) and forward STS-issued temp credentials to the containers. Off by default: the shell credentials are forwarded.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. Bare form uses the resolved stack name.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
|
|
25190
27171
|
}
|
|
25191
27172
|
|
|
25192
27173
|
//#endregion
|
|
@@ -25215,7 +27196,7 @@ async function localListCommand(options) {
|
|
|
25215
27196
|
process.stdout.write(`${formatTargetListing(listing, getEmbedConfig().cliName, { long: options.long })}\n`);
|
|
25216
27197
|
}
|
|
25217
27198
|
function formatTargetListing(listing, cliName, options = {}) {
|
|
25218
|
-
if (countTargets(listing) === 0) return `No runnable targets (Lambda functions, APIs, ECS services / tasks, AgentCore Runtimes, load balancers, CloudFront distributions) found in this CDK app.`;
|
|
27199
|
+
if (countTargets(listing) === 0) return `No runnable targets (Lambda functions, APIs, ECS services / tasks, AgentCore Runtimes, load balancers, CloudFront distributions, Lambda web functions, Lambda MicroVM images) found in this CDK app.`;
|
|
25219
27200
|
const long = options.long ?? false;
|
|
25220
27201
|
return "\n" + [
|
|
25221
27202
|
formatSection("Lambda Functions", `${cliName} invoke <target>`, listing.lambdas, long),
|
|
@@ -25224,7 +27205,9 @@ function formatTargetListing(listing, cliName, options = {}) {
|
|
|
25224
27205
|
formatSection("ECS Task Definitions", `${cliName} run-task <target>`, listing.ecsTaskDefinitions, long),
|
|
25225
27206
|
formatSection("AgentCore Runtimes", `${cliName} invoke-agentcore <target>`, listing.agentCoreRuntimes, long),
|
|
25226
27207
|
formatSection("Application Load Balancers", `${cliName} start-alb <target...>`, listing.loadBalancers, long),
|
|
25227
|
-
formatSection("CloudFront Distributions", `${cliName} start-cloudfront <target>`, listing.cloudFrontDistributions, long)
|
|
27208
|
+
formatSection("CloudFront Distributions", `${cliName} start-cloudfront <target>`, listing.cloudFrontDistributions, long),
|
|
27209
|
+
formatSection("Lambda Web Functions", `${cliName} start-web-function <target>`, listing.webFunctions, long),
|
|
27210
|
+
formatSection("Lambda MicroVM Images", `${cliName} start-microvm <target>`, listing.microvmImages, long)
|
|
25228
27211
|
].filter((lines) => lines.length > 0).map((lines) => lines.join("\n")).join("\n\n");
|
|
25229
27212
|
}
|
|
25230
27213
|
function formatSection(title, command, entries, long) {
|
|
@@ -30500,5 +32483,5 @@ function addStudioSpecificOptions(cmd) {
|
|
|
30500
32483
|
}
|
|
30501
32484
|
|
|
30502
32485
|
//#endregion
|
|
30503
|
-
export { applyEdgeResponseResult as $, defaultCredentialsLoader as $n, resolveCfnFallbackRegion as $r, isLocalCdkAssetImage as $t, startAgentCoreHttpServer as A, LocalInvokeBuildError as Ai, renderCodeDockerfile as An, resolveHostGatewayExtraHosts as Ar, addRunTaskSpecificOptions as At, idFromArn as B, withoutKeys as Bi, createFileWatcher as Bn, buildContainerImage as Br, resolveEcsAssumeRoleOption as Bt, addListSpecificOptions as C, AgentCoreResolutionError as Ci, invokeAgentCore as Cn, pickResponseTemplate as Cr, parseLbPortOverrides as Ct, createLocalStartAgentCoreCommand as D, formatStateRemedy as Di, SUPPORTED_CODE_RUNTIMES as Dn, HOST_DOCKER_INTERNAL_GATEWAY as Dr, addStartServiceSpecificOptions as Dt, addStartAgentCoreSpecificOptions as E, derivePseudoParametersFromRegion as Ei, downloadAndExtractS3Bundle as En, VtlEvaluationError as Er, resolveAlbFrontDoor as Et, createLocalStartCloudFrontCommand as F, firstUsableRegion as Fi, addStartApiSpecificOptions as Fn, parseConnectionsPath as Fr, addImageOverrideOptions as Ft, classifyS3Error as G, recordAssumedRole as Gi, availableApiIdentifiers as Gn, substituteAgainstState as Gr, canonicalizeImageOverrideKeys as Gt, createDeployedKvsDataSource as H, assumedRoleRecorded as Hi, buildStageMap as Hn, resolveRuntimeFileExtension as Hr, runEcsServiceEmulator as Ht, normalizeKvsFileKeys as I, keysNotFromTemplate as Ii, createLocalStartApiCommand as In, buildConnectEvent as Ir, buildEcsImageResolutionContext$1 as It, startCloudFrontServer as J, workloadStsClientConfig as Ji, groupRoutesByServer as Jn, substituteEnvVarsFromStateAsync as Jr, parseImageOverrideFlags as Jt, createS3OriginReader as K, resetWorkloadIdentity as Ki, filterRoutesByApiIdentifier as Kn, substituteAgainstStateAsync as Kr, enforceImageOverrideOrphans as Kt, parseKvsFileOverrides as L, keysOverriddenBy as Li, createWatchPredicates as Ln, buildDisconnectEvent as Lr, ecsClusterOption as Lt, startAgentCoreWsBridge as M, DynamicReferenceResolver as Mi, classifySourceChange as Mn, ConnectionRegistry as Mr, MAX_TASKS_SUBNET_RANGE_CAP as Mt, LocalStartCloudFrontError as N, containsDynamicReference as Ni, addInvokeSpecificOptions as Nn, buildMgmtEndpointEnvUrl as Nr, addCommonEcsServiceOptions as Nt, buildAgentCoreServeAuthCheck as O, substituteImagePlaceholders as Oi, buildAgentCoreCodeImage as On, HOST_GATEWAY_MIN_VERSION as Or, createLocalStartServiceCommand as Ot, addStartCloudFrontSpecificOptions as P, deployedFillMayBeSecret as Pi, createLocalInvokeCommand as Pn, handleConnectionsRequest as Pr, addEcsAssumeRoleOptions as Pt, applyEdgeRequestResult as Q, isProxyEnvConfigured as Qi, resolveServiceIntegrationParameters as Qn, rejectExplicitCfnStackWithMultipleStacks as Qr, imageOverrideKeyAliases as Qt, parseOriginOverrides as R, parseDynamicReference as Ri, resolveApiTargetSubset as Rn, buildMessageEvent as Rr, parseMaxTasks as Rt, StudioEventBus as S, AGENTCORE_RUNTIME_TYPE as Si, AGENTCORE_SESSION_ID_HEADER as Sn, evaluateResponseParameters as Sr, createLocalStartAlbCommand as St, formatTargetListing as T, resolveAgentCoreTarget as Ti, waitForAgentCorePing as Tn, tryParseStatus as Tr, isApplicationLoadBalancer as Tt, resolveDeployedKvsArnByName as U, deployAccountStsClientConfig as Ui, materializeLayerFromArn as Un, resolveRuntimeImage as Ur, ImageOverrideError as Ut, resolveKvsModulesForDistribution as V, SECOND_ASSUME_MESSAGE as Vi, attachStageContext as Vn, resolveRuntimeCodeMountPath as Vr, resolveSharedSidecarCredentials as Vt, resolveDeployedOriginBucket as W, readEnvCallerCredentials as Wi, resolveEnvVars$1 as Wn, EcsTaskResolutionError as Wr, buildImageOverrideTag as Wt, serveFromStaticOrigin as X, resolveProfileCredentials as Xi, startApiServer as Xn, createLocalStateProvider as Xr, runImageOverrideBuilds as Xt, resolveErrorResponseCandidates as Y, buildStsClientConfig as Yi, readMtlsMaterialsFromDisk as Yn, LocalStateSourceError as Yr, resolveImageOverrides as Yt, serveLambdaUrlOrigin as Z, buildProxyClientConfig as Zi, resolveSelectionExpression as Zn, isCfnFlagPresent as Zr, describePinnedImageUri as Zt, filterStudioTargetGroups as _, resolveLambdaArnIntrinsic as _i, MCP_PROTOCOL_VERSION as _n, matchRoute as _r, createCloudFrontModule as _t, createLocalStudioCommand as a, resolveWatchConfig as ai, setShadowReadyTimeoutMs as an, verifyJwtViaDiscovery as ar, describeS3OriginDomain as at, renderStudioHtml as b, AGENTCORE_HTTP_PROTOCOL as bi, AGENTCORE_SIGV4_SERVICE as bn, buildHttpApiV2Event as br, addAlbSpecificOptions as bt, startStudioProxy as c, listTargets as ci, addInvokeAgentCoreSpecificOptions as cn, evaluateCachedLambdaPolicy as cr, pickFunctionUrlLogicalIdFromOrigin as ct, createStudioDispatcher as d, discoverWebSocketApisOrThrow as di, invokeAgentCoreWs as dn, attachAuthorizers as dr, pickTargetFunctionLogicalId as dt, resolveCfnRegion as ei, listPinnedTargets as en, buildCognitoJwksUrl as er, buildEdgeRequestEvent as et, filterStudioCustomResources as f, filterWebSocketApisByIdentifiers as fi, A2A_CONTAINER_PORT as fn, applyCorsResponseHeaders as fr, resolveCloudFrontDistribution as ft, annotatePinnedEcsTargets as g, pickRefLogicalId as gi, MCP_PATH as gn, matchPreflight as gr, stripCloudFrontImport as gt, annotateEcsTaskPinnedTargets as h, discoverRoutes as hi, MCP_CONTAINER_PORT as hn, isFunctionUrlOacFronted as hr, runViewerResponse as ht, coerceStopRequest as i, resolveSsmParameters as ii, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as in, verifyJwtAuthorizer as ir, CLOUDFRONT_DISTRIBUTION_TYPE as it, attachAgentCoreWsBridge as j, DynamicReferenceResolutionError as ji, toCmdArgv as jn, bufferToBody as jr, createLocalRunTaskCommand as jt, selectServeInboundAuth as k, tryResolveImageFnJoin as ki, computeCodeImageTag as kn, probeHostGatewaySupport as kr, serviceStrategy as kt, relayServeRequest as l, availableWebSocketApiIdentifiers as li, createLocalInvokeAgentCoreCommand as ln, invokeRequestAuthorizer as lr, pickKvsLogicalIdFromArn as lt, annotateAlbPinnedBackingServices as m, webSocketApiMatchesIdentifier as mi, a2aInvokeOnce as mn, buildCorsConfigFromCloudFrontChain as mr, runViewerRequest as mt, coerceRunRequest as n, CfnLocalStateProvider as ni, CloudMapRegistry as nn, createJwksCache as nr, edgeHeadersToHttp as nt, resolveServeBaseUrl as o, resolveSingleTarget as oi, getContainerNetworkIp as on, buildMethodArn as or, extractKvsAssociations as ot, isCustomResourceLambdaTarget as p, parseSelectionExpressionPath as pi, A2A_PATH as pn, buildCorsConfigByApiId as pr, compileCloudFrontFunction as pt, matchBehavior as q, workloadClientConfig as qi, filterRoutesByApiIdentifiers as qn, substituteEnvVarsFromState as qr, mergeForService as qt, coerceServeRequest as r, collectSsmParameterRefs as ri, DEFAULT_SHADOW_READY_TIMEOUT_MS as rn, verifyCognitoJwt as rr, httpHeadersToEdge as rt, createStudioServeManager as s, countTargets as si, attachContainerLogStreamer as sn, computeRequestIdentityHash as sr, isCloudFrontDistribution as st, addStudioSpecificOptions as t, resolveCfnStackName as ti, buildCloudMapIndex as tn, buildJwksUrlFromIssuer as tr, buildEdgeResponseEvent as tt, reinvoke as u, discoverWebSocketApis as ui, bridgeAgentCoreWs as un, invokeTokenAuthorizer as ur, pickLambdaEdgeFunctionLogicalId as ut, startStudioServer as v, AGENTCORE_A2A_PROTOCOL as vi, mcpInvokeOnce as vn, translateLambdaResponse as vr, createLocalFileKvsDataSource as vt, createLocalListCommand as w, pickAgentCoreCandidateStack as wi, waitForAgentCoreHttpReady as wn, selectIntegrationResponse as wr, resolveAlbTarget as wt, createStudioStore as x, AGENTCORE_MCP_PROTOCOL as xi, signAgentCoreInvocation as xn, buildRestV1Event as xr, albStrategy as xt, toStudioTargetGroups as y, AGENTCORE_AGUI_PROTOCOL as yi, parseSseForJsonRpc as yn, applyAuthorizerOverlay as yr, createUnboundCloudFrontModule as yt, resolveCloudFrontTarget as z, resolveDynamicReferencesInEnv as zi, createAuthorizerCache as zn, architectureToPlatform as zr, parseRestartPolicy as zt };
|
|
30504
|
-
//# sourceMappingURL=local-studio-
|
|
32486
|
+
export { postHook as $, resolveDynamicReferencesInEnv as $a, architectureToPlatform as $i, parseRestartPolicy as $n, createAuthorizerCache as $r, resolveCloudFrontTarget as $t, microvmFidelityWarnings as A, pickRefLogicalId as Aa, matchPreflight as Ai, stripCloudFrontImport as An, MCP_PATH as Ar, supportedWebFunctionRuntimes as At, MICROVM_WS_BASE_PROTOCOL as B, derivePseudoParametersFromRegion as Ba, VtlEvaluationError as Bi, resolveAlbFrontDoor as Bn, downloadAndExtractS3Bundle as Br, addStartAgentCoreSpecificOptions as Bt, addListSpecificOptions as C, availableWebSocketApiIdentifiers as Ca, invokeRequestAuthorizer as Ci, pickKvsLogicalIdFromArn as Cn, createLocalInvokeAgentCoreCommand as Cr, readWebFunctionServiceConfig as Ct, buildMicrovmLambdaView as D, parseSelectionExpressionPath as Da, buildCorsConfigByApiId as Di, compileCloudFrontFunction as Dn, A2A_PATH as Dr, buildNodeEntryFileStart as Dt, addStartMicrovmSpecificOptions as E, filterWebSocketApisByIdentifiers as Ea, applyCorsResponseHeaders as Ei, resolveCloudFrontDistribution as En, A2A_CONTAINER_PORT as Er, WEB_FUNCTION_RUNTIMES as Et, microvmImageTag as F, AGENTCORE_MCP_PROTOCOL as Fa, buildRestV1Event as Fi, albStrategy as Fn, signAgentCoreInvocation as Fr, WEB_FUNCTION_ENDPOINT_TYPE as Ft, startMicrovmFrontDoor as G, DynamicReferenceResolutionError as Ga, bufferToBody as Gi, createLocalRunTaskCommand as Gn, toCmdArgv as Gr, attachAgentCoreWsBridge as Gt, PROXY_PORT_HEADER as H, substituteImagePlaceholders as Ha, HOST_GATEWAY_MIN_VERSION as Hi, createLocalStartServiceCommand as Hn, buildAgentCoreCodeImage as Hr, buildAgentCoreServeAuthCheck as Ht, planMicrovmPorts as I, AGENTCORE_RUNTIME_TYPE as Ia, evaluateResponseParameters as Ii, createLocalStartAlbCommand as In, AGENTCORE_SESSION_ID_HEADER as Ir, WEB_FUNCTION_REVISION_TYPE as It, MICROVM_HOOK_PATH_PREFIX as J, deployedFillMayBeSecret as Ja, handleConnectionsRequest as Ji, addEcsAssumeRoleOptions as Jn, createLocalInvokeCommand as Jr, addStartCloudFrontSpecificOptions as Jt, stripMicrovmProxyHeaders as K, DynamicReferenceResolver as Ka, ConnectionRegistry as Ki, MAX_TASKS_SUBNET_RANGE_CAP as Kn, classifySourceChange as Kr, startAgentCoreWsBridge as Kt, readExposedPorts as L, AgentCoreResolutionError as La, pickResponseTemplate as Li, parseLbPortOverrides as Ln, invokeAgentCore as Lr, WEB_FUNCTION_TYPE as Lt, DEFAULT_TERMINATE_HOOK_TIMEOUT_MS as M, AGENTCORE_A2A_PROTOCOL as Ma, translateLambdaResponse as Mi, createLocalFileKvsDataSource as Mn, mcpInvokeOnce as Mr, findFileAssetForObjectKey as Mt, LocalMicrovm as N, AGENTCORE_AGUI_PROTOCOL as Na, applyAuthorizerOverlay as Ni, createUnboundCloudFrontModule as Nn, parseSseForJsonRpc as Nr, parseWebFunctionBuildConfig as Nt, createLocalStartMicrovmCommand as O, webSocketApiMatchesIdentifier as Oa, buildCorsConfigFromCloudFrontChain as Oi, runViewerRequest as On, a2aInvokeOnce as Or, defaultWebFunctionPlatform as Ot, buildMicrovmImage as P, AGENTCORE_HTTP_PROTOCOL as Pa, buildHttpApiV2Event as Pi, addAlbSpecificOptions as Pn, AGENTCORE_SIGV4_SERVICE as Pr, resolveWebFunctionArtifact as Pt, pollBuildHook as Q, parseDynamicReference as Qa, buildMessageEvent as Qi, parseMaxTasks as Qn, resolveApiTargetSubset as Qr, parseOriginOverrides as Qt, setContainerPaused as R, pickAgentCoreCandidateStack as Ra, selectIntegrationResponse as Ri, resolveAlbTarget as Rn, waitForAgentCoreHttpReady as Rr, WebFunctionResolutionError as Rt, StudioEventBus as S, listTargets as Sa, evaluateCachedLambdaPolicy as Si, pickFunctionUrlLogicalIdFromOrigin as Sn, addInvokeAgentCoreSpecificOptions as Sr, finalizeWebFunctionEnv as St, formatTargetListing as T, discoverWebSocketApisOrThrow as Ta, attachAuthorizers as Ti, pickTargetFunctionLogicalId as Tn, invokeAgentCoreWs as Tr, webFunctionFidelityWarnings as Tt, rewriteUpgradeResponseHead as U, tryResolveImageFnJoin as Ua, probeHostGatewaySupport as Ui, serviceStrategy as Un, computeCodeImageTag as Ur, selectServeInboundAuth as Ut, MicrovmStateError as V, formatStateRemedy as Va, HOST_DOCKER_INTERNAL_GATEWAY as Vi, addStartServiceSpecificOptions as Vn, SUPPORTED_CODE_RUNTIMES as Vr, createLocalStartAgentCoreCommand as Vt, selectTargetPort as W, LocalInvokeBuildError as Wa, resolveHostGatewayExtraHosts as Wi, addRunTaskSpecificOptions as Wn, renderCodeDockerfile as Wr, startAgentCoreHttpServer as Wt, callRuntimeHook as X, keysNotFromTemplate as Xa, buildConnectEvent as Xi, buildEcsImageResolutionContext$1 as Xn, createLocalStartApiCommand as Xr, normalizeKvsFileKeys as Xt, MicrovmHookError as Y, firstUsableRegion as Ya, parseConnectionsPath as Yi, addImageOverrideOptions as Yn, addStartApiSpecificOptions as Yr, createLocalStartCloudFrontCommand as Yt, generateMicrovmId as Z, keysOverriddenBy as Za, buildDisconnectEvent as Zi, ecsClusterOption as Zn, createWatchPredicates as Zr, parseKvsFileOverrides as Zt, filterStudioTargetGroups as _, collectSsmParameterRefs as _a, verifyCognitoJwt as _i, httpHeadersToEdge as _n, DEFAULT_SHADOW_READY_TIMEOUT_MS as _r, startWebFunctionContainer as _t, createLocalStudioCommand as a, substituteAgainstState as aa, availableApiIdentifiers as ai, classifyS3Error as an, recordAssumedRole as ao, canonicalizeImageOverrideKeys as ar, disposeMicrovmCodeArtifact as at, renderStudioHtml as b, resolveSingleTarget as ba, buildMethodArn as bi, extractKvsAssociations as bn, getContainerNetworkIp as br, WEB_FUNCTION_HTTP_ENDPOINT_ENV as bt, startStudioProxy as c, substituteEnvVarsFromStateAsync as ca, groupRoutesByServer as ci, startCloudFrontServer as cn, workloadStsClientConfig as co, parseImageOverrideFlags as cr, resolveMicrovmImageTarget as ct, createStudioDispatcher as d, isCfnFlagPresent as da, resolveSelectionExpression as di, serveLambdaUrlOrigin as dn, buildProxyClientConfig as do, describePinnedImageUri as dr, createLocalStartWebFunctionCommand as dt, buildContainerImage as ea, createFileWatcher as ei, idFromArn as en, withoutKeys as eo, resolveEcsAssumeRoleOption as er, runHookBody as et, filterStudioCustomResources as f, rejectExplicitCfnStackWithMultipleStacks as fa, resolveServiceIntegrationParameters as fi, applyEdgeRequestResult as fn, isProxyEnvConfigured as fo, imageOverrideKeyAliases as fr, RecentOutput as ft, annotatePinnedEcsTargets as g, CfnLocalStateProvider as ga, createJwksCache as gi, edgeHeadersToHttp as gn, CloudMapRegistry as gr, readContainerLogTail as gt, annotateEcsTaskPinnedTargets as h, resolveCfnStackName as ha, buildJwksUrlFromIssuer as hi, buildEdgeResponseEvent as hn, buildCloudMapIndex as hr, planWebFunctionContainer as ht, coerceStopRequest as i, EcsTaskResolutionError as ia, resolveEnvVars$1 as ii, resolveDeployedOriginBucket as in, readEnvCallerCredentials as io, buildImageOverrideTag as ir, MicrovmResolutionError as it, readRunHookPayload as j, resolveLambdaArnIntrinsic as ja, matchRoute as ji, createCloudFrontModule as jn, MCP_PROTOCOL_VERSION as jr, disposeWebFunctionArtifact as jt, finalizeMicrovmEnv as k, discoverRoutes as ka, isFunctionUrlOacFronted as ki, runViewerResponse as kn, MCP_CONTAINER_PORT as kr, resolveWebFunctionRuntime as kt, relayServeRequest as l, LocalStateSourceError as la, readMtlsMaterialsFromDisk as li, resolveErrorResponseCandidates as ln, buildStsClientConfig as lo, resolveImageOverrides as lr, s3KeyOfUri as lt, annotateAlbPinnedBackingServices as m, resolveCfnRegion as ma, buildCognitoJwksUrl as mi, buildEdgeRequestEvent as mn, listPinnedTargets as mr, containerFailure as mt, coerceRunRequest as n, resolveRuntimeFileExtension as na, buildStageMap as ni, createDeployedKvsDataSource as nn, assumedRoleRecorded as no, runEcsServiceEmulator as nr, MICROVM_DEFAULT_INGRESS_PORT as nt, resolveServeBaseUrl as o, substituteAgainstStateAsync as oa, filterRoutesByApiIdentifier as oi, createS3OriginReader as on, resetWorkloadIdentity as oo, enforceImageOverrideOrphans as or, readMicrovmImageConfig as ot, isCustomResourceLambdaTarget as p, resolveCfnFallbackRegion as pa, defaultCredentialsLoader as pi, applyEdgeResponseResult as pn, isLocalCdkAssetImage as pr, WEB_FUNCTION_CONTAINER_PORT as pt, MAX_RUN_HOOK_PAYLOAD_BYTES as q, containsDynamicReference as qa, buildMgmtEndpointEnvUrl as qi, addCommonEcsServiceOptions as qn, addInvokeSpecificOptions as qr, LocalStartCloudFrontError as qt, coerceServeRequest as r, resolveRuntimeImage as ra, materializeLayerFromArn as ri, resolveDeployedKvsArnByName as rn, deployAccountStsClientConfig as ro, ImageOverrideError as rr, MICROVM_IMAGE_TYPE as rt, createStudioServeManager as s, substituteEnvVarsFromState as sa, filterRoutesByApiIdentifiers as si, matchBehavior as sn, workloadClientConfig as so, mergeForService as sr, resolveMicrovmCodeArtifact as st, addStudioSpecificOptions as t, resolveRuntimeCodeMountPath as ta, attachStageContext as ti, resolveKvsModulesForDistribution as tn, SECOND_ASSUME_MESSAGE as to, resolveSharedSidecarCredentials as tr, waitForAppAnswer as tt, reinvoke as u, createLocalStateProvider as ua, startApiServer as ui, serveFromStaticOrigin as un, resolveProfileCredentials as uo, runImageOverrideBuilds as ur, addStartWebFunctionSpecificOptions as ut, startStudioServer as v, resolveSsmParameters as va, verifyJwtAuthorizer as vi, CLOUDFRONT_DISTRIBUTION_TYPE as vn, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as vr, waitForWebFunctionReady as vt, createLocalListCommand as w, discoverWebSocketApis as wa, invokeTokenAuthorizer as wi, pickLambdaEdgeFunctionLogicalId as wn, bridgeAgentCoreWs as wr, webFunctionAssumeRole as wt, createStudioStore as x, countTargets as xa, computeRequestIdentityHash as xi, isCloudFrontDistribution as xn, attachContainerLogStreamer as xr, buildWebFunctionLambdaView as xt, toStudioTargetGroups as y, resolveWatchConfig as ya, verifyJwtViaDiscovery as yi, describeS3OriginDomain as yn, setShadowReadyTimeoutMs as yr, watchContainerExit as yt, MICROVM_CONTROL_PATH as z, resolveAgentCoreTarget as za, tryParseStatus as zi, isApplicationLoadBalancer as zn, waitForAgentCorePing as zr, resolveWebFunctionTarget as zt };
|
|
32487
|
+
//# sourceMappingURL=local-studio-BhFxp-jM.js.map
|