cdk-local 0.54.0 → 0.56.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 +2 -2
- package/dist/cli.js +3 -3
- package/dist/{cloud-map-resolver-CbSdXQjx.js → cloud-map-resolver-BGl1bgxF.js} +48 -10
- package/dist/cloud-map-resolver-BGl1bgxF.js.map +1 -0
- package/dist/{error-handler-BPGt9Xqd.d.ts → error-handler-CwzKivb8.d.ts} +2 -1
- package/dist/error-handler-CwzKivb8.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +1 -1
- package/dist/{local-list-e7cz-0OZ.js → local-list-6ZNQh_PK.js} +215 -43
- package/dist/local-list-6ZNQh_PK.js.map +1 -0
- package/package.json +1 -1
- package/dist/cloud-map-resolver-CbSdXQjx.js.map +0 -1
- package/dist/error-handler-BPGt9Xqd.d.ts.map +0 -1
- package/dist/local-list-e7cz-0OZ.js.map +0 -1
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { a as runDockerStreaming, c as getEmbedConfig, r as getDockerCmd, s as getLogger, u as setEmbedConfig } from "./docker-cmd-voNPrcRh.js";
|
|
2
|
-
import { $n as parseContextOptions, $t as resolveEcsTaskTarget, At as parseEcrUri, Bn as matchStacks, Bt as removeContainer, Cn as listTargets, Ct as singleFlight, Dt as waitForRieReady, Et as invokeRie, Fn as resolveAgentCoreTarget, Ft as appendEnvFlags, Gn as LocalInvokeBuildError, Gt as resolveRuntimeImage, Hn as readCdkPathOrUndefined, Ht as streamLogs, In as resolveLambdaTarget, It as ensureDockerAvailable, Jn as applyRoleArnIfSet, Jt as applyCrossStackResolverToTask, Kn as LocalStartServiceError, Kt as EcsTaskResolutionError, Ln as derivePseudoParametersFromRegion, Lt as execEnvForSecrets, Mt as buildDockerImage, Nt as DockerRunnerError, Ot as architectureToPlatform, Pn as pickAgentCoreCandidateStack, Pt as SENSITIVE_ENV_KEYS, Q as verifyJwtViaDiscovery, Qn as deprecatedRegionOption, Qt as parseEcsTarget, Rt as pickFreePort, Sn as countTargets, St as writeProfileCredentialsFile, Tt as getDockerImageBySourceHash, Un as resolveCdkPathToLogicalIds, Ut as resolveRuntimeCodeMountPath, Vn as buildCdkPathIndex, Vt as runDetached, Wn as CdkLocalError, Wt as resolveRuntimeFileExtension, Xn as commonOptions, Xt as derivePartitionAndUrlSuffix, Y as createJwksCache, Yn as appOptions, Yt as checkVolumeHostPath, Zn as contextOptions, Zt as detectEcsImageResolutionNeeds, _n as resolveApp, an as resolveEnvVars, at as invokeAgentCoreWs, bn as resolveMultiTarget, c as resolveProfileCredentials$1, cn as createLocalStateProvider, dn as resolveCfnFallbackRegion, en as applyDeployedEnvFallback, er as warnIfDeprecatedRegion, ft as signAgentCoreInvocation, gt as downloadAndExtractS3Bundle, ht as waitForAgentCorePing, i as getPublishedHostPort, in as substituteEnvVarsFromStateAsync, jn as AGENTCORE_MCP_PROTOCOL, jt as pullEcrImage, kt as buildContainerImage, lt as mcpInvokeOnce, mt as invokeAgentCore, n as CloudMapRegistry, nn as substituteAgainstStateAsync, on as materializeLayerFromArn, ot as MCP_CONTAINER_PORT, qn as withErrorHandling, qt as TASK_ROLE_ACCOUNT_PLACEHOLDER, r as getContainerNetworkIp, st as MCP_PATH, t as buildCloudMapIndex, un as rejectExplicitCfnStackWithMultipleStacks, vt as buildAgentCoreCodeImage, wt as AssetManifestLoader, xn as resolveSingleTarget, yn as Synthesizer, zt as pullImage } from "./cloud-map-resolver-
|
|
3
|
-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
2
|
+
import { $n as parseContextOptions, $t as resolveEcsTaskTarget, At as parseEcrUri, Bn as matchStacks, Bt as removeContainer, Cn as listTargets, Ct as singleFlight, Dt as waitForRieReady, Et as invokeRie, Fn as resolveAgentCoreTarget, Ft as appendEnvFlags, Gn as LocalInvokeBuildError, Gt as resolveRuntimeImage, Hn as readCdkPathOrUndefined, Ht as streamLogs, In as resolveLambdaTarget, It as ensureDockerAvailable, Jn as applyRoleArnIfSet, Jt as applyCrossStackResolverToTask, Kn as LocalStartServiceError, Kt as EcsTaskResolutionError, Ln as derivePseudoParametersFromRegion, Lt as execEnvForSecrets, Mt as buildDockerImage, Nt as DockerRunnerError, Ot as architectureToPlatform, Pn as pickAgentCoreCandidateStack, Pt as SENSITIVE_ENV_KEYS, Q as verifyJwtViaDiscovery, Qn as deprecatedRegionOption, Qt as parseEcsTarget, Rt as pickFreePort, Sn as countTargets, St as writeProfileCredentialsFile, Tt as getDockerImageBySourceHash, Un as resolveCdkPathToLogicalIds, Ut as resolveRuntimeCodeMountPath, Vn as buildCdkPathIndex, Vt as runDetached, Wn as CdkLocalError, Wt as resolveRuntimeFileExtension, Xn as commonOptions, Xt as derivePartitionAndUrlSuffix, Y as createJwksCache, Yn as appOptions, Yt as checkVolumeHostPath, Zn as contextOptions, Zt as detectEcsImageResolutionNeeds, _n as resolveApp, an as resolveEnvVars, at as invokeAgentCoreWs, bn as resolveMultiTarget, c as resolveProfileCredentials$1, cn as createLocalStateProvider, dn as resolveCfnFallbackRegion, en as applyDeployedEnvFallback, er as warnIfDeprecatedRegion, ft as signAgentCoreInvocation, gt as downloadAndExtractS3Bundle, ht as waitForAgentCorePing, i as getPublishedHostPort, in as substituteEnvVarsFromStateAsync, jn as AGENTCORE_MCP_PROTOCOL, jt as pullEcrImage, kt as buildContainerImage, lt as mcpInvokeOnce, mt as invokeAgentCore, n as CloudMapRegistry, nn as substituteAgainstStateAsync, on as materializeLayerFromArn, ot as MCP_CONTAINER_PORT, qn as withErrorHandling, qt as TASK_ROLE_ACCOUNT_PLACEHOLDER, r as getContainerNetworkIp, st as MCP_PATH, t as buildCloudMapIndex, un as rejectExplicitCfnStackWithMultipleStacks, vt as buildAgentCoreCodeImage, wt as AssetManifestLoader, xn as resolveSingleTarget, yn as Synthesizer, zt as pullImage } from "./cloud-map-resolver-BGl1bgxF.js";
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir, tmpdir } from "node:os";
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
-
import { dirname } from "node:path";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
7
|
import { Command, Option } from "commander";
|
|
8
8
|
import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
9
9
|
import { execFile, spawn } from "node:child_process";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
|
-
import { randomBytes, randomUUID } from "node:crypto";
|
|
11
|
+
import { X509Certificate, randomBytes, randomUUID } from "node:crypto";
|
|
12
12
|
import { createServer, request } from "node:http";
|
|
13
|
+
import { createServer as createServer$1 } from "node:https";
|
|
13
14
|
import graphlib from "graphlib";
|
|
14
15
|
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
|
|
15
16
|
|
|
@@ -681,6 +682,7 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
|
|
|
681
682
|
logger.info(`Target: ${resolved.stack.stackName}/${resolved.logicalId} (${resolved.protocol})`);
|
|
682
683
|
const isMcp = resolved.protocol === "MCP";
|
|
683
684
|
if (isMcp && options.ws) logger.warn("--ws applies only to the HTTP protocol; ignoring it for this MCP runtime.");
|
|
685
|
+
if (options.wsInteractive && !options.ws) logger.warn("--ws-interactive is meaningful only with --ws; ignoring.");
|
|
684
686
|
if (options.sigv4 && (isMcp || options.ws)) logger.warn("--sigv4 signs the HTTP /invocations request only; ignoring it for the " + (isMcp ? "MCP" : "/ws WebSocket") + " path.");
|
|
685
687
|
const sessionId = options.sessionId ?? randomUUID();
|
|
686
688
|
const event = await readEvent(options);
|
|
@@ -721,12 +723,14 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
|
|
|
721
723
|
emitMcpResult(mcp);
|
|
722
724
|
} else if (options.ws) {
|
|
723
725
|
await waitForAgentCorePing(containerHost, hostPort);
|
|
724
|
-
|
|
726
|
+
const frameSource = options.wsInteractive ? readStdinLines() : void 0;
|
|
727
|
+
logger.info(options.wsInteractive ? "Opening the agent /ws WebSocket (interactive — stdin lines = follow-up frames; Ctrl-D to end)..." : "Opening the agent /ws WebSocket and streaming frames...");
|
|
725
728
|
const wsResult = await invokeAgentCoreWs(containerHost, hostPort, event, {
|
|
726
729
|
sessionId,
|
|
727
730
|
timeoutMs: options.timeout,
|
|
728
731
|
onMessage: (text) => process.stdout.write(text),
|
|
729
|
-
...authorization && { authorization }
|
|
732
|
+
...authorization && { authorization },
|
|
733
|
+
...frameSource && { frameSource }
|
|
730
734
|
});
|
|
731
735
|
await new Promise((r) => setTimeout(r, 250));
|
|
732
736
|
emitWsResult(wsResult);
|
|
@@ -1296,6 +1300,27 @@ async function readStdin() {
|
|
|
1296
1300
|
for await (const chunk of process.stdin) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
1297
1301
|
return Buffer.concat(chunks).toString("utf-8");
|
|
1298
1302
|
}
|
|
1303
|
+
/**
|
|
1304
|
+
* Read `process.stdin` line-buffered and yield each line as a string (trailing
|
|
1305
|
+
* `\r?\n` stripped). The async iterable completes when stdin EOFs (Ctrl-D /
|
|
1306
|
+
* end-of-pipe), and surrenders the underlying stream when its `return()` is
|
|
1307
|
+
* called — so the WS client can close down the source when the server closes
|
|
1308
|
+
* first without leaving stdin held open.
|
|
1309
|
+
*
|
|
1310
|
+
* Exported so a unit test can drive the iterable shape directly.
|
|
1311
|
+
*/
|
|
1312
|
+
async function* readStdinLines() {
|
|
1313
|
+
const { createInterface } = await import("node:readline");
|
|
1314
|
+
const rl = createInterface({
|
|
1315
|
+
input: process.stdin,
|
|
1316
|
+
crlfDelay: Infinity
|
|
1317
|
+
});
|
|
1318
|
+
try {
|
|
1319
|
+
for await (const line of rl) yield line;
|
|
1320
|
+
} finally {
|
|
1321
|
+
rl.close();
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1299
1324
|
function readEnvOverridesFile$2(filePath) {
|
|
1300
1325
|
if (!filePath) return void 0;
|
|
1301
1326
|
let raw;
|
|
@@ -1315,7 +1340,7 @@ function readEnvOverridesFile$2(filePath) {
|
|
|
1315
1340
|
}
|
|
1316
1341
|
function createLocalInvokeAgentCoreCommand(opts = {}) {
|
|
1317
1342
|
setEmbedConfig(opts.embedConfig);
|
|
1318
|
-
const cmd = new Command("invoke-agentcore").description("Run a Bedrock AgentCore Runtime container locally and invoke it once over its protocol contract: HTTP (POST /invocations + GET /ping on 8080) or MCP (POST /mcp Streamable HTTP on 8000). Resolves the AWS::BedrockAgentCore::Runtime, pulls/builds its container, injects env vars + AWS credentials, and prints the response. For an MCP runtime, runs the session handshake then sends one JSON-RPC request (tools/list by default, or the method/params from --event). Target accepts a CDK display path (MyStack/MyAgent) or stack-qualified logical ID (MyStack:MyAgentRuntime1234). Single-stack apps may omit the stack prefix. Omit <target> in an interactive terminal to pick from a list. Supports the container artifact and the CodeConfiguration managed-runtime artifact (fromCodeAsset, built from source) on the HTTP + MCP protocols; the agent calls real AWS for managed services.").argument("[target]", "CDK display path or stack-qualified logical ID of the AgentCore Runtime to invoke (omit to pick interactively in a TTY)").addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}})")).addOption(new Option("--session-id <id>", "AgentCore runtime session id header value (default: a random UUID)")).addOption(new Option("--ws", "Stream over the HTTP-protocol agent's bidirectional /ws WebSocket endpoint (on 8080) instead of POST /invocations: send --event as the first frame and print every received frame to stdout until the agent closes. Ignored for an MCP runtime.").default(false)).addOption(new Option("--bearer-token <jwt>", "Bearer JWT to present when the runtime declares a customJwtAuthorizer. Verified against the runtime OIDC discovery URL (signature / issuer / expiry / audience) before the container starts, then forwarded to /invocations as Authorization: Bearer <jwt>.")).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 the /invocations POST with AWS SigV4 (service bedrock-agentcore) using the resolved credentials, matching the cloud default when the runtime declares no customJwtAuthorizer. Opt-in: default unsigned. Mutually exclusive with --bearer-token; ignored on a JWT-protected runtime.").default(false)).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64)").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).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 to bind the agent port to").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Per-request timeout in milliseconds. Applied to POST /invocations, POST /mcp, and the /ws open-to-close window. Raise this for long-running agent calls that exceed the default.").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 so the agent runs with the deployed role. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's RoleArn when it is a literal ARN; (3) `--no-assume-role` opts out. Off by default — the developer's shell credentials are forwarded unchanged.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. Bare form uses the resolved stack name; pass an explicit value when the CFn stack name differs.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).action(withErrorHandling(async (target, options) => {
|
|
1343
|
+
const cmd = new Command("invoke-agentcore").description("Run a Bedrock AgentCore Runtime container locally and invoke it once over its protocol contract: HTTP (POST /invocations + GET /ping on 8080) or MCP (POST /mcp Streamable HTTP on 8000). Resolves the AWS::BedrockAgentCore::Runtime, pulls/builds its container, injects env vars + AWS credentials, and prints the response. For an MCP runtime, runs the session handshake then sends one JSON-RPC request (tools/list by default, or the method/params from --event). Target accepts a CDK display path (MyStack/MyAgent) or stack-qualified logical ID (MyStack:MyAgentRuntime1234). Single-stack apps may omit the stack prefix. Omit <target> in an interactive terminal to pick from a list. Supports the container artifact and the CodeConfiguration managed-runtime artifact (fromCodeAsset, built from source) on the HTTP + MCP protocols; the agent calls real AWS for managed services.").argument("[target]", "CDK display path or stack-qualified logical ID of the AgentCore Runtime to invoke (omit to pick interactively in a TTY)").addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}})")).addOption(new Option("--session-id <id>", "AgentCore runtime session id header value (default: a random UUID)")).addOption(new Option("--ws", "Stream over the HTTP-protocol agent's bidirectional /ws WebSocket endpoint (on 8080) instead of POST /invocations: send --event as the first frame and print every received frame to stdout until the agent closes. Ignored for an MCP runtime.").default(false)).addOption(new Option("--ws-interactive", "REPL mode for --ws: after the initial --event frame, read additional frames from stdin (one frame per line, trailing newline stripped) and send each as a text frame until stdin EOFs (Ctrl-D) or the agent closes. Only meaningful with --ws.").default(false)).addOption(new Option("--bearer-token <jwt>", "Bearer JWT to present when the runtime declares a customJwtAuthorizer. Verified against the runtime OIDC discovery URL (signature / issuer / expiry / audience) before the container starts, then forwarded to /invocations as Authorization: Bearer <jwt>.")).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 the /invocations POST with AWS SigV4 (service bedrock-agentcore) using the resolved credentials, matching the cloud default when the runtime declares no customJwtAuthorizer. Opt-in: default unsigned. Mutually exclusive with --bearer-token; ignored on a JWT-protected runtime.").default(false)).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64)").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).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 to bind the agent port to").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Per-request timeout in milliseconds. Applied to POST /invocations, POST /mcp, and the /ws open-to-close window. Raise this for long-running agent calls that exceed the default.").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 so the agent runs with the deployed role. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's RoleArn when it is a literal ARN; (3) `--no-assume-role` opts out. Off by default — the developer's shell credentials are forwarded unchanged.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. Bare form uses the resolved stack name; pass an explicit value when the CFn stack name differs.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).action(withErrorHandling(async (target, options) => {
|
|
1319
1344
|
await localInvokeAgentCoreCommand(target, options, opts.extraStateProviders);
|
|
1320
1345
|
}));
|
|
1321
1346
|
[
|
|
@@ -1329,7 +1354,7 @@ function createLocalInvokeAgentCoreCommand(opts = {}) {
|
|
|
1329
1354
|
|
|
1330
1355
|
//#endregion
|
|
1331
1356
|
//#region src/local/ecs-network.ts
|
|
1332
|
-
const execFileAsync$
|
|
1357
|
+
const execFileAsync$2 = promisify(execFile);
|
|
1333
1358
|
/**
|
|
1334
1359
|
* Docker network + AWS-published metadata-endpoints sidecar lifecycle for
|
|
1335
1360
|
* `cdkl run-task`. The sidecar (a small Go binary maintained by
|
|
@@ -1453,7 +1478,7 @@ async function sweepOrphanedSvcNetworks(prefix) {
|
|
|
1453
1478
|
const filter = `${prefix}-svc-`;
|
|
1454
1479
|
let names;
|
|
1455
1480
|
try {
|
|
1456
|
-
const { stdout } = await execFileAsync$
|
|
1481
|
+
const { stdout } = await execFileAsync$2(getDockerCmd(), [
|
|
1457
1482
|
"network",
|
|
1458
1483
|
"ls",
|
|
1459
1484
|
"--filter",
|
|
@@ -1471,7 +1496,7 @@ async function sweepOrphanedSvcNetworks(prefix) {
|
|
|
1471
1496
|
for (const name of names) {
|
|
1472
1497
|
let attached;
|
|
1473
1498
|
try {
|
|
1474
|
-
const { stdout } = await execFileAsync$
|
|
1499
|
+
const { stdout } = await execFileAsync$2(getDockerCmd(), [
|
|
1475
1500
|
"network",
|
|
1476
1501
|
"inspect",
|
|
1477
1502
|
name,
|
|
@@ -1506,7 +1531,7 @@ async function createNetworkAndSidecar(args) {
|
|
|
1506
1531
|
await pullImage(METADATA_ENDPOINT_IMAGE, skipPull);
|
|
1507
1532
|
logger.info(`Creating docker network ${networkName} (subnet ${cidr})...`);
|
|
1508
1533
|
try {
|
|
1509
|
-
await execFileAsync$
|
|
1534
|
+
await execFileAsync$2(getDockerCmd(), [
|
|
1510
1535
|
"network",
|
|
1511
1536
|
"create",
|
|
1512
1537
|
"--driver",
|
|
@@ -1541,7 +1566,7 @@ async function createNetworkAndSidecar(args) {
|
|
|
1541
1566
|
sidecarArgs.push(METADATA_ENDPOINT_IMAGE);
|
|
1542
1567
|
logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
|
|
1543
1568
|
try {
|
|
1544
|
-
const { stdout } = await execFileAsync$
|
|
1569
|
+
const { stdout } = await execFileAsync$2(getDockerCmd(), sidecarArgs, {
|
|
1545
1570
|
maxBuffer: 10 * 1024 * 1024,
|
|
1546
1571
|
...execEnvForSecrets(sidecarPassthroughEnv)
|
|
1547
1572
|
});
|
|
@@ -1612,7 +1637,7 @@ async function destroyNetworkOnly(networkName) {
|
|
|
1612
1637
|
if (!networkName) return;
|
|
1613
1638
|
const logger = getLogger().child("ecs-network");
|
|
1614
1639
|
try {
|
|
1615
|
-
await execFileAsync$
|
|
1640
|
+
await execFileAsync$2(getDockerCmd(), [
|
|
1616
1641
|
"network",
|
|
1617
1642
|
"rm",
|
|
1618
1643
|
networkName
|
|
@@ -1751,7 +1776,7 @@ async function resolveSsm(entry, shape, client) {
|
|
|
1751
1776
|
|
|
1752
1777
|
//#endregion
|
|
1753
1778
|
//#region src/local/ecs-task-runner.ts
|
|
1754
|
-
const execFileAsync = promisify(execFile);
|
|
1779
|
+
const execFileAsync$1 = promisify(execFile);
|
|
1755
1780
|
/**
|
|
1756
1781
|
* Top-level orchestrator for `cdkl run-task`. Coordinates image
|
|
1757
1782
|
* preparation, secret resolution, docker-network bring-up, container
|
|
@@ -1811,7 +1836,7 @@ async function cleanupEcsRun(state, options) {
|
|
|
1811
1836
|
if (state.network && !state.network.ownedByCaller) await destroyTaskNetwork(state.network);
|
|
1812
1837
|
state.network = void 0;
|
|
1813
1838
|
for (const v of state.dockerVolumeNames) try {
|
|
1814
|
-
await execFileAsync(getDockerCmd(), [
|
|
1839
|
+
await execFileAsync$1(getDockerCmd(), [
|
|
1815
1840
|
"volume",
|
|
1816
1841
|
"rm",
|
|
1817
1842
|
v
|
|
@@ -1892,7 +1917,7 @@ async function runEcsTask(task, options, state) {
|
|
|
1892
1917
|
logger.info(`Starting container '${container.name}' (image=${imagePlan.get(container.name)})`);
|
|
1893
1918
|
let id;
|
|
1894
1919
|
try {
|
|
1895
|
-
const { stdout } = await execFileAsync(getDockerCmd(), args, {
|
|
1920
|
+
const { stdout } = await execFileAsync$1(getDockerCmd(), args, {
|
|
1896
1921
|
maxBuffer: 10 * 1024 * 1024,
|
|
1897
1922
|
...execEnvForSecrets(sensitiveEnv)
|
|
1898
1923
|
});
|
|
@@ -2004,7 +2029,7 @@ async function waitForContainerHealthy(containerId, displayName) {
|
|
|
2004
2029
|
let lastStatus = "";
|
|
2005
2030
|
while (Date.now() < deadline) {
|
|
2006
2031
|
try {
|
|
2007
|
-
const { stdout } = await execFileAsync(getDockerCmd(), [
|
|
2032
|
+
const { stdout } = await execFileAsync$1(getDockerCmd(), [
|
|
2008
2033
|
"inspect",
|
|
2009
2034
|
"--format",
|
|
2010
2035
|
"{{.State.Health.Status}}",
|
|
@@ -2027,7 +2052,7 @@ async function waitForContainerHealthy(containerId, displayName) {
|
|
|
2027
2052
|
}
|
|
2028
2053
|
async function waitForContainerExit(containerId) {
|
|
2029
2054
|
try {
|
|
2030
|
-
const { stdout } = await execFileAsync(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
|
|
2055
|
+
const { stdout } = await execFileAsync$1(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
|
|
2031
2056
|
const code = Number.parseInt(stdout.trim(), 10);
|
|
2032
2057
|
return Number.isFinite(code) ? code : 1;
|
|
2033
2058
|
} catch (err) {
|
|
@@ -2037,7 +2062,7 @@ async function waitForContainerExit(containerId) {
|
|
|
2037
2062
|
}
|
|
2038
2063
|
async function stopContainer(containerId, graceSeconds) {
|
|
2039
2064
|
try {
|
|
2040
|
-
await execFileAsync(getDockerCmd(), [
|
|
2065
|
+
await execFileAsync$1(getDockerCmd(), [
|
|
2041
2066
|
"stop",
|
|
2042
2067
|
"-t",
|
|
2043
2068
|
String(graceSeconds),
|
|
@@ -2163,7 +2188,7 @@ async function realizeDockerVolumes(volumes, state) {
|
|
|
2163
2188
|
const dockerVolumeName = `${getEmbedConfig().resourceNamePrefix}-${v.name}-${randHex(4)}`;
|
|
2164
2189
|
args.push(dockerVolumeName);
|
|
2165
2190
|
try {
|
|
2166
|
-
await execFileAsync(getDockerCmd(), args);
|
|
2191
|
+
await execFileAsync$1(getDockerCmd(), args);
|
|
2167
2192
|
state.dockerVolumeNames.push(dockerVolumeName);
|
|
2168
2193
|
logger.debug(`Created docker volume ${dockerVolumeName} for task volume '${v.name}'`);
|
|
2169
2194
|
} catch (err) {
|
|
@@ -3707,12 +3732,17 @@ const DEFAULT_UPSTREAM_TIMEOUT_MS = 3e4;
|
|
|
3707
3732
|
async function startFrontDoorServer(opts) {
|
|
3708
3733
|
const logger = getLogger().child("front-door");
|
|
3709
3734
|
const host = opts.host ?? "127.0.0.1";
|
|
3710
|
-
const
|
|
3735
|
+
const requestHandler = (req, res) => {
|
|
3711
3736
|
handleProxyRequest(req, res, opts).catch((err) => {
|
|
3712
3737
|
logger.debug(`front-door request error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3713
3738
|
if (!res.headersSent) writeError(res, 502, "Bad Gateway");
|
|
3714
3739
|
});
|
|
3715
|
-
}
|
|
3740
|
+
};
|
|
3741
|
+
const server = opts.tls ? createServer$1({
|
|
3742
|
+
cert: opts.tls.certPem,
|
|
3743
|
+
key: opts.tls.keyPem
|
|
3744
|
+
}, requestHandler) : createServer(requestHandler);
|
|
3745
|
+
const scheme = opts.tls ? "https" : "http";
|
|
3716
3746
|
server.on("connection", (socket) => socket.setNoDelay(true));
|
|
3717
3747
|
const boundPort = await new Promise((resolve, reject) => {
|
|
3718
3748
|
server.once("error", reject);
|
|
@@ -3729,6 +3759,7 @@ async function startFrontDoorServer(opts) {
|
|
|
3729
3759
|
return {
|
|
3730
3760
|
port: boundPort,
|
|
3731
3761
|
host,
|
|
3762
|
+
scheme,
|
|
3732
3763
|
server,
|
|
3733
3764
|
close: async () => {
|
|
3734
3765
|
if (closed) return;
|
|
@@ -3769,7 +3800,7 @@ function handleProxyRequest(req, res, opts) {
|
|
|
3769
3800
|
if (!action) return reply404(req, res, opts);
|
|
3770
3801
|
if (action.kind === "redirect" || action.kind === "fixed-response") {
|
|
3771
3802
|
req.resume();
|
|
3772
|
-
if (action.kind === "redirect") writeRedirect(res, action, req, opts.listenerPort);
|
|
3803
|
+
if (action.kind === "redirect") writeRedirect(res, action, req, opts.listenerPort, opts.tls ? "https" : "http");
|
|
3773
3804
|
else writeFixedResponse(res, action);
|
|
3774
3805
|
return Promise.resolve();
|
|
3775
3806
|
}
|
|
@@ -3807,7 +3838,7 @@ function handlePoolRequest(req, res, pool, opts) {
|
|
|
3807
3838
|
};
|
|
3808
3839
|
const headers = { ...req.headers };
|
|
3809
3840
|
stripHopByHopHeaders(headers);
|
|
3810
|
-
appendForwardedHeaders(headers, req, opts.listenerPort);
|
|
3841
|
+
appendForwardedHeaders(headers, req, opts.listenerPort, opts.tls ? "https" : "http");
|
|
3811
3842
|
const proxyReq = request({
|
|
3812
3843
|
host: endpoint.host,
|
|
3813
3844
|
port: endpoint.port,
|
|
@@ -3874,8 +3905,8 @@ function pickWeightedTarget(targets) {
|
|
|
3874
3905
|
* `#{query}` placeholders filled from the incoming request. We resolve those
|
|
3875
3906
|
* placeholders against the request the front-door received.
|
|
3876
3907
|
*/
|
|
3877
|
-
function writeRedirect(res, action, req, listenerPort) {
|
|
3878
|
-
const location = buildRedirectLocation(action, req, listenerPort);
|
|
3908
|
+
function writeRedirect(res, action, req, listenerPort, scheme) {
|
|
3909
|
+
const location = buildRedirectLocation(action, req, listenerPort, scheme);
|
|
3879
3910
|
res.writeHead(action.statusCode, {
|
|
3880
3911
|
location,
|
|
3881
3912
|
"content-type": "text/plain; charset=utf-8",
|
|
@@ -3883,15 +3914,20 @@ function writeRedirect(res, action, req, listenerPort) {
|
|
|
3883
3914
|
});
|
|
3884
3915
|
res.end();
|
|
3885
3916
|
}
|
|
3886
|
-
/**
|
|
3887
|
-
|
|
3917
|
+
/**
|
|
3918
|
+
* Build the `Location` URL for a redirect action, resolving ALB `#{...}`
|
|
3919
|
+
* placeholders. `scheme` is the receiving listener's protocol — it sets the
|
|
3920
|
+
* default for `#{protocol}` so an HTTPS listener redirects to `https://...`
|
|
3921
|
+
* by default, matching what a real ALB does.
|
|
3922
|
+
*/
|
|
3923
|
+
function buildRedirectLocation(action, req, listenerPort, scheme = "http") {
|
|
3888
3924
|
const url = req.url ?? "/";
|
|
3889
3925
|
const qIndex = url.indexOf("?");
|
|
3890
3926
|
const reqPath = qIndex === -1 ? url : url.slice(0, qIndex);
|
|
3891
3927
|
const reqQuery = qIndex === -1 ? "" : url.slice(qIndex + 1);
|
|
3892
3928
|
const rawHost = req.headers["host"];
|
|
3893
3929
|
const placeholders = {
|
|
3894
|
-
protocol:
|
|
3930
|
+
protocol: scheme,
|
|
3895
3931
|
host: ((Array.isArray(rawHost) ? rawHost[0] : rawHost) ?? "").split(":")[0] ?? "",
|
|
3896
3932
|
port: String(listenerPort),
|
|
3897
3933
|
path: reqPath.replace(/^\//, ""),
|
|
@@ -3958,7 +3994,7 @@ function handleLambdaRequest(req, res, lambda, opts) {
|
|
|
3958
3994
|
const body = Buffer.concat(chunks);
|
|
3959
3995
|
const forwardHeaders = { ...req.headers };
|
|
3960
3996
|
stripHopByHopHeaders(forwardHeaders);
|
|
3961
|
-
appendForwardedHeaders(forwardHeaders, req, opts.listenerPort);
|
|
3997
|
+
appendForwardedHeaders(forwardHeaders, req, opts.listenerPort, opts.tls ? "https" : "http");
|
|
3962
3998
|
const snapshot = snapshotFromIncoming(req, body);
|
|
3963
3999
|
for (const [name, value] of Object.entries(forwardHeaders)) {
|
|
3964
4000
|
if (value === void 0) continue;
|
|
@@ -4020,14 +4056,15 @@ function stripHopByHopHeaders(headers) {
|
|
|
4020
4056
|
/**
|
|
4021
4057
|
* Inject the ALB-style forwarding headers a downstream app may read. Appends
|
|
4022
4058
|
* the client IP to any existing `X-Forwarded-For` chain (ALB appends rather
|
|
4023
|
-
* than replaces) and stamps the scheme / listener port.
|
|
4059
|
+
* than replaces) and stamps the scheme / listener port. `scheme` follows the
|
|
4060
|
+
* listener's protocol so an HTTPS listener stamps `x-forwarded-proto: https`.
|
|
4024
4061
|
*/
|
|
4025
|
-
function appendForwardedHeaders(headers, req, listenerPort) {
|
|
4062
|
+
function appendForwardedHeaders(headers, req, listenerPort, scheme) {
|
|
4026
4063
|
const clientIp = req.socket.remoteAddress ?? "";
|
|
4027
4064
|
const existing = headers["x-forwarded-for"];
|
|
4028
4065
|
const chain = Array.isArray(existing) ? existing.join(", ") : existing;
|
|
4029
4066
|
headers["x-forwarded-for"] = chain ? `${chain}, ${clientIp}` : clientIp;
|
|
4030
|
-
headers["x-forwarded-proto"] =
|
|
4067
|
+
headers["x-forwarded-proto"] = scheme;
|
|
4031
4068
|
headers["x-forwarded-port"] = String(listenerPort);
|
|
4032
4069
|
}
|
|
4033
4070
|
function writeError(res, statusCode, message) {
|
|
@@ -4345,6 +4382,129 @@ function matchBitPrefix(a, b, prefixLength) {
|
|
|
4345
4382
|
return (a[fullBytes] & mask) === (b[fullBytes] & mask);
|
|
4346
4383
|
}
|
|
4347
4384
|
|
|
4385
|
+
//#endregion
|
|
4386
|
+
//#region src/local/front-door-tls.ts
|
|
4387
|
+
const execFileAsync = promisify(execFile);
|
|
4388
|
+
/**
|
|
4389
|
+
* Resolve a single global cert/key pair for HTTPS front-door listeners. When
|
|
4390
|
+
* BOTH `certPath` and `keyPath` are supplied, those PEM files are read from
|
|
4391
|
+
* disk. When neither is supplied, a long-lived self-signed cert is cached
|
|
4392
|
+
* under `$XDG_CACHE_HOME/cdk-local/alb-https/` (defaulting to
|
|
4393
|
+
* `~/.cache/cdk-local/alb-https/`) and reused across boots; it is regenerated
|
|
4394
|
+
* when missing or within `regenerateWithinDays` of expiry. Pairing is enforced
|
|
4395
|
+
* — supplying exactly one of `--tls-cert` / `--tls-key` is rejected with a
|
|
4396
|
+
* clear error before the server starts.
|
|
4397
|
+
*
|
|
4398
|
+
* The self-signed cert path subprocesses `openssl req -x509 ...`. `openssl`
|
|
4399
|
+
* is on macOS / Linux dev boxes and Docker base images by default; absence
|
|
4400
|
+
* surfaces as an actionable error pointing at the BYO recipe.
|
|
4401
|
+
*/
|
|
4402
|
+
async function resolveFrontDoorTlsMaterials(opts) {
|
|
4403
|
+
const hasCert = opts.certPath !== void 0 && opts.certPath !== "";
|
|
4404
|
+
const hasKey = opts.keyPath !== void 0 && opts.keyPath !== "";
|
|
4405
|
+
if (hasCert !== hasKey) throw new Error(`${hasCert ? "--tls-cert" : "--tls-key"} is set but ${hasCert ? "--tls-key" : "--tls-cert"} is missing. Both --tls-cert and --tls-key must be set together, or both left unset to use an auto-generated self-signed cert.`);
|
|
4406
|
+
if (hasCert && hasKey) return {
|
|
4407
|
+
certPem: readPemOrThrow(opts.certPath, "--tls-cert"),
|
|
4408
|
+
keyPem: readPemOrThrow(opts.keyPath, "--tls-key")
|
|
4409
|
+
};
|
|
4410
|
+
return ensureSelfSignedCert({
|
|
4411
|
+
cacheDir: opts.cacheDir ?? defaultCacheDir(),
|
|
4412
|
+
regenerateWithinDays: opts.regenerateWithinDays ?? 30,
|
|
4413
|
+
validityDays: opts.validityDays ?? 825
|
|
4414
|
+
});
|
|
4415
|
+
}
|
|
4416
|
+
/** Default cache dir for the auto-generated self-signed cert. */
|
|
4417
|
+
function defaultCacheDir() {
|
|
4418
|
+
const xdg = process.env["XDG_CACHE_HOME"];
|
|
4419
|
+
return join(xdg && xdg !== "" ? xdg : join(homedir(), ".cache"), "cdk-local", "alb-https");
|
|
4420
|
+
}
|
|
4421
|
+
const CERT_FILENAME = "cert.pem";
|
|
4422
|
+
const KEY_FILENAME = "key.pem";
|
|
4423
|
+
async function ensureSelfSignedCert(opts) {
|
|
4424
|
+
const logger = getLogger().child("front-door-tls");
|
|
4425
|
+
const certPath = join(opts.cacheDir, CERT_FILENAME);
|
|
4426
|
+
const keyPath = join(opts.cacheDir, KEY_FILENAME);
|
|
4427
|
+
if (cachedPairIsFresh(certPath, keyPath, opts.regenerateWithinDays)) return {
|
|
4428
|
+
certPem: readFileSync(certPath),
|
|
4429
|
+
keyPem: readFileSync(keyPath)
|
|
4430
|
+
};
|
|
4431
|
+
mkdirSync(opts.cacheDir, { recursive: true });
|
|
4432
|
+
try {
|
|
4433
|
+
unlinkSync(certPath);
|
|
4434
|
+
} catch {}
|
|
4435
|
+
try {
|
|
4436
|
+
unlinkSync(keyPath);
|
|
4437
|
+
} catch {}
|
|
4438
|
+
try {
|
|
4439
|
+
await runOpenssl([
|
|
4440
|
+
"req",
|
|
4441
|
+
"-x509",
|
|
4442
|
+
"-newkey",
|
|
4443
|
+
"rsa:2048",
|
|
4444
|
+
"-nodes",
|
|
4445
|
+
"-keyout",
|
|
4446
|
+
keyPath,
|
|
4447
|
+
"-out",
|
|
4448
|
+
certPath,
|
|
4449
|
+
"-subj",
|
|
4450
|
+
"/CN=localhost",
|
|
4451
|
+
"-days",
|
|
4452
|
+
String(opts.validityDays),
|
|
4453
|
+
"-addext",
|
|
4454
|
+
"subjectAltName=DNS:localhost,IP:127.0.0.1"
|
|
4455
|
+
]);
|
|
4456
|
+
} catch (err) {
|
|
4457
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4458
|
+
throw new Error(`Failed to auto-generate a self-signed cert via openssl: ${msg}. Install openssl, or supply --tls-cert <path> + --tls-key <path> with your own PEM files. Recipe: openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -subj "/CN=localhost" -days 365.`);
|
|
4459
|
+
}
|
|
4460
|
+
logger.info(`ALB front-door: generated self-signed cert at ${certPath} (valid ${opts.validityDays} days)`);
|
|
4461
|
+
return {
|
|
4462
|
+
certPem: readFileSync(certPath),
|
|
4463
|
+
keyPem: readFileSync(keyPath)
|
|
4464
|
+
};
|
|
4465
|
+
}
|
|
4466
|
+
function cachedPairIsFresh(certPath, keyPath, regenWithinDays) {
|
|
4467
|
+
try {
|
|
4468
|
+
statSync(certPath);
|
|
4469
|
+
statSync(keyPath);
|
|
4470
|
+
} catch {
|
|
4471
|
+
return false;
|
|
4472
|
+
}
|
|
4473
|
+
try {
|
|
4474
|
+
const notAfter = readCertNotAfter(certPath);
|
|
4475
|
+
if (notAfter === void 0) return false;
|
|
4476
|
+
const renewAt = notAfter.getTime() - regenWithinDays * 864e5;
|
|
4477
|
+
return Date.now() < renewAt;
|
|
4478
|
+
} catch {
|
|
4479
|
+
return false;
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
/**
|
|
4483
|
+
* Read a cert's `notAfter` expiry timestamp. Used to decide whether to
|
|
4484
|
+
* regenerate the cached self-signed cert proactively. Returns `undefined`
|
|
4485
|
+
* when the cert is unreadable (caller then regenerates).
|
|
4486
|
+
*/
|
|
4487
|
+
function readCertNotAfter(certPath) {
|
|
4488
|
+
try {
|
|
4489
|
+
const cert = new X509Certificate(readFileSync(certPath));
|
|
4490
|
+
const parsed = new Date(cert.validTo);
|
|
4491
|
+
return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
|
|
4492
|
+
} catch {
|
|
4493
|
+
return;
|
|
4494
|
+
}
|
|
4495
|
+
}
|
|
4496
|
+
async function runOpenssl(args) {
|
|
4497
|
+
await execFileAsync("openssl", args, { timeout: 3e4 });
|
|
4498
|
+
}
|
|
4499
|
+
function readPemOrThrow(path, flagName) {
|
|
4500
|
+
try {
|
|
4501
|
+
return readFileSync(path);
|
|
4502
|
+
} catch (err) {
|
|
4503
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4504
|
+
throw new Error(`${flagName}: cannot read PEM file at '${path}': ${msg}`);
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4348
4508
|
//#endregion
|
|
4349
4509
|
//#region src/local/front-door-lambda-runner.ts
|
|
4350
4510
|
/** Forward the dev shell's AWS credential / region env into the Lambda container. */
|
|
@@ -4829,6 +4989,10 @@ async function buildFrontDoor(plan, options, logger) {
|
|
|
4829
4989
|
...action.messageBody !== void 0 && { messageBody: action.messageBody }
|
|
4830
4990
|
};
|
|
4831
4991
|
};
|
|
4992
|
+
const tlsMaterials = plan.listeners.some((l) => l.protocol === "HTTPS") ? await resolveFrontDoorTlsMaterials({
|
|
4993
|
+
certPath: options.tlsCert,
|
|
4994
|
+
keyPath: options.tlsKey
|
|
4995
|
+
}) : void 0;
|
|
4832
4996
|
try {
|
|
4833
4997
|
for (const listener of plan.listeners) {
|
|
4834
4998
|
const defaultRoute = listener.defaultAction ? toRouteAction(listener.defaultAction) : void 0;
|
|
@@ -4843,15 +5007,17 @@ async function buildFrontDoor(plan, options, logger) {
|
|
|
4843
5007
|
target: toRouteAction(r.action)
|
|
4844
5008
|
}));
|
|
4845
5009
|
const route = (req) => matchAlbPathRule(req, ruleRoutes) ?? defaultRoute;
|
|
5010
|
+
const tls = listener.protocol === "HTTPS" ? tlsMaterials : void 0;
|
|
4846
5011
|
const server = await startFrontDoorServer({
|
|
4847
5012
|
route,
|
|
4848
5013
|
port: listener.hostPort,
|
|
4849
5014
|
host: containerHost,
|
|
4850
5015
|
listenerPort: listener.listenerPort,
|
|
4851
|
-
label: `listener port ${listener.listenerPort}
|
|
5016
|
+
label: `listener port ${listener.listenerPort}`,
|
|
5017
|
+
...tls ? { tls } : {}
|
|
4852
5018
|
});
|
|
4853
5019
|
servers.push(server);
|
|
4854
|
-
logger.info(`ALB front-door:
|
|
5020
|
+
logger.info(`ALB front-door: ${server.scheme}://${server.host}:${server.port} (listener port ${listener.listenerPort})`);
|
|
4855
5021
|
if (listener.defaultAction) logger.info(` default -> ${describeAction(listener.defaultAction)}`);
|
|
4856
5022
|
for (const r of [...listener.rules].sort((a, b) => a.priority - b.priority)) logger.info(` ${describeConditions(r)} (priority ${r.priority}) -> ${describeAction(r.action)}`);
|
|
4857
5023
|
if (!listener.defaultAction) logger.info(" (no default action: unmatched requests return 404)");
|
|
@@ -5153,8 +5319,12 @@ function createLocalStartServiceCommand(opts = {}) {
|
|
|
5153
5319
|
* functions (`TargetType:"lambda"` target groups — #123: the TG -> backing
|
|
5154
5320
|
* `AWS::Lambda::Function` is resolved and the front-door invokes it locally per
|
|
5155
5321
|
* request); `redirect` / `fixed-response` actions. A single weighted forward may
|
|
5156
|
-
* mix ECS and Lambda targets.
|
|
5157
|
-
*
|
|
5322
|
+
* mix ECS and Lambda targets. HTTPS listeners are served — local TLS
|
|
5323
|
+
* termination uses a user-supplied or auto-generated self-signed cert/key
|
|
5324
|
+
* pair (the deployed `Listener.Certificates[]` ACM ARNs are not fetched,
|
|
5325
|
+
* because ACM private keys are not retrievable by design). Skipped with a
|
|
5326
|
+
* warning: TLS listeners (NLB-style, not ALB) and `authenticate-cognito` /
|
|
5327
|
+
* `authenticate-oidc` actions.
|
|
5158
5328
|
*/
|
|
5159
5329
|
const ALB_TYPE = "AWS::ElasticLoadBalancingV2::LoadBalancer";
|
|
5160
5330
|
const LISTENER_TYPE = "AWS::ElasticLoadBalancingV2::Listener";
|
|
@@ -5181,10 +5351,11 @@ function resolveAlbFrontDoor(stack, albLogicalId) {
|
|
|
5181
5351
|
const port = parsePort(props["Port"]);
|
|
5182
5352
|
if (port === void 0) continue;
|
|
5183
5353
|
const protocol = typeof props["Protocol"] === "string" ? props["Protocol"] : "HTTP";
|
|
5184
|
-
if (protocol !== "HTTP") {
|
|
5185
|
-
warnings.push(`Listener '${listenerLogicalId}' on port ${port} uses protocol ${protocol}; the local ALB front-door supports HTTP listeners only (TLS
|
|
5354
|
+
if (protocol !== "HTTP" && protocol !== "HTTPS") {
|
|
5355
|
+
warnings.push(`Listener '${listenerLogicalId}' on port ${port} uses protocol ${protocol}; the local ALB front-door supports HTTP and HTTPS listeners only (TLS / NLB-style listeners are not served). Skipping it.`);
|
|
5186
5356
|
continue;
|
|
5187
5357
|
}
|
|
5358
|
+
if (protocol === "HTTPS" && Array.isArray(props["Certificates"]) && props["Certificates"].length > 0) warnings.push(`Listener '${listenerLogicalId}' on port ${port} declares ACM Certificates which are not retrievable locally. The front-door terminates TLS with --tls-cert/--tls-key or an auto-generated self-signed cert.`);
|
|
5188
5359
|
const defaultAction = resolveAction(props["DefaultActions"], resources, tgToService, stackName, `Listener '${listenerLogicalId}' (port ${port}) default action`, warnings);
|
|
5189
5360
|
const rules = [];
|
|
5190
5361
|
for (const { ruleLogicalId, ruleProps } of rulesByListener.get(listenerLogicalId) ?? []) {
|
|
@@ -5210,7 +5381,7 @@ function resolveAlbFrontDoor(stack, albLogicalId) {
|
|
|
5210
5381
|
if (!defaultAction && rules.length === 0) continue;
|
|
5211
5382
|
listeners.push({
|
|
5212
5383
|
listenerPort: port,
|
|
5213
|
-
listenerProtocol:
|
|
5384
|
+
listenerProtocol: protocol,
|
|
5214
5385
|
listenerLogicalId,
|
|
5215
5386
|
...defaultAction ? { defaultAction } : {},
|
|
5216
5387
|
rules
|
|
@@ -5800,6 +5971,7 @@ function albStrategy(options) {
|
|
|
5800
5971
|
listeners.push({
|
|
5801
5972
|
listenerPort: listener.listenerPort,
|
|
5802
5973
|
hostPort,
|
|
5974
|
+
protocol: listener.listenerProtocol,
|
|
5803
5975
|
...listener.defaultAction ? { defaultAction: qualify(listener.defaultAction) } : {},
|
|
5804
5976
|
rules: listener.rules.map((r) => ({
|
|
5805
5977
|
priority: r.priority,
|
|
@@ -5838,7 +6010,7 @@ function albStrategy(options) {
|
|
|
5838
6010
|
*/
|
|
5839
6011
|
function createLocalStartAlbCommand(opts = {}) {
|
|
5840
6012
|
setEmbedConfig(opts.embedConfig);
|
|
5841
|
-
return addCommonEcsServiceOptions(new Command("start-alb").description("Run an Application Load Balancer locally: name the ALB, and cdk-local boots the ECS service(s) behind its
|
|
6013
|
+
return addCommonEcsServiceOptions(new Command("start-alb").description("Run an Application Load Balancer locally: name the ALB, and cdk-local boots the ECS service(s) behind its listeners and stands up a local front-door on each listener port that round-robins across the running replicas and routes its listener rules across the backing services — a stable host endpoint, like behind a real load balancer. The symmetric ALB counterpart of `start-api`. Each <target> accepts a CDK display path (MyStack/MyAlb) or stack-qualified logical ID; single-stack apps may omit the stack prefix. Supports HTTP and HTTPS listeners (TLS terminated locally with --tls-cert/--tls-key or an auto-generated self-signed cert); all six ALB rule-condition fields (path-pattern / host-header / http-header / http-request-method / query-string / source-ip); forward (single and weighted), redirect, and fixed-response actions; and ECS or Lambda targets (a Lambda target group is invoked locally via the Lambda RIE). authenticate-cognito / authenticate-oidc actions are skipped with a warning. Omit <targets> in an interactive terminal to multi-select the load balancers from a list.").argument("[targets...]", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ElasticLoadBalancingV2::LoadBalancer resources to run (omit to multi-select interactively in a TTY)").addOption(new Option("--lb-port <listenerPort=hostPort...>", "Bind the local front-door on a specific host port (e.g. 80=8080); repeatable. Default: host port == ALB listener port. Use this on macOS to remap a privileged listener port (< 1024) to a non-privileged host port.")).addOption(new Option("--tls-cert <path>", "PEM-encoded server certificate for HTTPS front-door listeners. Must be set together with --tls-key. Omit both flags to auto-generate a self-signed cert (cached under $XDG_CACHE_HOME/cdk-local/alb-https/, default ~/.cache/cdk-local/alb-https/); requires openssl on PATH. The deployed Listener Certificates[] are NOT fetched (ACM private keys are not retrievable by design). The auto-generated cert lists DNS:localhost,IP:127.0.0.1 as SubjectAltName, so a client validating a non-loopback --container-host will fail the SAN check — pass --tls-cert / --tls-key with a SAN covering that host instead.")).addOption(new Option("--tls-key <path>", "PEM-encoded server private key matching --tls-cert. Must be set together with --tls-cert.")).action(withErrorHandling(async (targets, options) => {
|
|
5842
6014
|
await runEcsServiceEmulator(targets, options, albStrategy(options), opts.extraStateProviders);
|
|
5843
6015
|
})));
|
|
5844
6016
|
}
|
|
@@ -5915,4 +6087,4 @@ function createLocalListCommand(opts = {}) {
|
|
|
5915
6087
|
|
|
5916
6088
|
//#endregion
|
|
5917
6089
|
export { createLocalRunTaskCommand as a, createLocalStartServiceCommand as i, formatTargetListing as n, createLocalInvokeAgentCoreCommand as o, createLocalStartAlbCommand as r, createLocalInvokeCommand as s, createLocalListCommand as t };
|
|
5918
|
-
//# sourceMappingURL=local-list-
|
|
6090
|
+
//# sourceMappingURL=local-list-6ZNQh_PK.js.map
|