cdk-local 0.53.0 → 0.55.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 +19 -43
- package/dist/cli.js +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{local-list-B1RsKINr.js → local-list-DT8qRbKy.js} +200 -43
- package/dist/local-list-DT8qRbKy.js.map +1 -0
- package/package.json +1 -1
- package/dist/local-list-B1RsKINr.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
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-CbSdXQjx.js";
|
|
3
|
-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
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
|
|
|
@@ -593,6 +594,15 @@ function createLocalInvokeCommand(opts = {}) {
|
|
|
593
594
|
//#endregion
|
|
594
595
|
//#region src/cli/commands/local-invoke-agentcore.ts
|
|
595
596
|
/**
|
|
597
|
+
* Parser for `--timeout <ms>`. Accepts a positive integer; rejects 0,
|
|
598
|
+
* negatives, fractions, and non-numeric input.
|
|
599
|
+
*/
|
|
600
|
+
function parseTimeoutMs(raw) {
|
|
601
|
+
const parsed = Number(raw);
|
|
602
|
+
if (!Number.isInteger(parsed) || parsed <= 0) throw new CdkLocalError(`--timeout must be a positive integer number of milliseconds (got '${raw}').`, "LOCAL_INVOKE_AGENTCORE_TIMEOUT_INVALID");
|
|
603
|
+
return parsed;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
596
606
|
* `cdkl invoke-agentcore <target>` — run a Bedrock AgentCore Runtime container
|
|
597
607
|
* locally and invoke it once over the AgentCore HTTP contract. Resolves
|
|
598
608
|
* the `AWS::BedrockAgentCore::Runtime`, pulls / builds its container,
|
|
@@ -707,7 +717,7 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
|
|
|
707
717
|
process.on("SIGINT", sigintHandler);
|
|
708
718
|
if (isMcp && mcpRequest) {
|
|
709
719
|
logger.info(`MCP request: ${mcpRequest.method}`);
|
|
710
|
-
const mcp = await mcpInvokeOnce(containerHost, hostPort, mcpRequest);
|
|
720
|
+
const mcp = await mcpInvokeOnce(containerHost, hostPort, mcpRequest, { requestTimeoutMs: options.timeout });
|
|
711
721
|
await new Promise((r) => setTimeout(r, 250));
|
|
712
722
|
emitMcpResult(mcp);
|
|
713
723
|
} else if (options.ws) {
|
|
@@ -715,7 +725,7 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
|
|
|
715
725
|
logger.info("Opening the agent /ws WebSocket and streaming frames...");
|
|
716
726
|
const wsResult = await invokeAgentCoreWs(containerHost, hostPort, event, {
|
|
717
727
|
sessionId,
|
|
718
|
-
timeoutMs:
|
|
728
|
+
timeoutMs: options.timeout,
|
|
719
729
|
onMessage: (text) => process.stdout.write(text),
|
|
720
730
|
...authorization && { authorization }
|
|
721
731
|
});
|
|
@@ -726,7 +736,7 @@ async function localInvokeAgentCoreCommand(target, options, extraStateProviders)
|
|
|
726
736
|
const additionalHeaders = await buildSigV4HeadersIfRequested(options, resolved, loadedState, containerHost, hostPort, event, sessionId);
|
|
727
737
|
const result = await invokeAgentCore(containerHost, hostPort, event, {
|
|
728
738
|
sessionId,
|
|
729
|
-
timeoutMs:
|
|
739
|
+
timeoutMs: options.timeout,
|
|
730
740
|
onChunk: (text) => process.stdout.write(text),
|
|
731
741
|
...authorization && { authorization },
|
|
732
742
|
...additionalHeaders && { additionalHeaders }
|
|
@@ -1306,7 +1316,7 @@ function readEnvOverridesFile$2(filePath) {
|
|
|
1306
1316
|
}
|
|
1307
1317
|
function createLocalInvokeAgentCoreCommand(opts = {}) {
|
|
1308
1318
|
setEmbedConfig(opts.embedConfig);
|
|
1309
|
-
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("--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
|
+
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) => {
|
|
1310
1320
|
await localInvokeAgentCoreCommand(target, options, opts.extraStateProviders);
|
|
1311
1321
|
}));
|
|
1312
1322
|
[
|
|
@@ -1320,7 +1330,7 @@ function createLocalInvokeAgentCoreCommand(opts = {}) {
|
|
|
1320
1330
|
|
|
1321
1331
|
//#endregion
|
|
1322
1332
|
//#region src/local/ecs-network.ts
|
|
1323
|
-
const execFileAsync$
|
|
1333
|
+
const execFileAsync$2 = promisify(execFile);
|
|
1324
1334
|
/**
|
|
1325
1335
|
* Docker network + AWS-published metadata-endpoints sidecar lifecycle for
|
|
1326
1336
|
* `cdkl run-task`. The sidecar (a small Go binary maintained by
|
|
@@ -1444,7 +1454,7 @@ async function sweepOrphanedSvcNetworks(prefix) {
|
|
|
1444
1454
|
const filter = `${prefix}-svc-`;
|
|
1445
1455
|
let names;
|
|
1446
1456
|
try {
|
|
1447
|
-
const { stdout } = await execFileAsync$
|
|
1457
|
+
const { stdout } = await execFileAsync$2(getDockerCmd(), [
|
|
1448
1458
|
"network",
|
|
1449
1459
|
"ls",
|
|
1450
1460
|
"--filter",
|
|
@@ -1462,7 +1472,7 @@ async function sweepOrphanedSvcNetworks(prefix) {
|
|
|
1462
1472
|
for (const name of names) {
|
|
1463
1473
|
let attached;
|
|
1464
1474
|
try {
|
|
1465
|
-
const { stdout } = await execFileAsync$
|
|
1475
|
+
const { stdout } = await execFileAsync$2(getDockerCmd(), [
|
|
1466
1476
|
"network",
|
|
1467
1477
|
"inspect",
|
|
1468
1478
|
name,
|
|
@@ -1497,7 +1507,7 @@ async function createNetworkAndSidecar(args) {
|
|
|
1497
1507
|
await pullImage(METADATA_ENDPOINT_IMAGE, skipPull);
|
|
1498
1508
|
logger.info(`Creating docker network ${networkName} (subnet ${cidr})...`);
|
|
1499
1509
|
try {
|
|
1500
|
-
await execFileAsync$
|
|
1510
|
+
await execFileAsync$2(getDockerCmd(), [
|
|
1501
1511
|
"network",
|
|
1502
1512
|
"create",
|
|
1503
1513
|
"--driver",
|
|
@@ -1532,7 +1542,7 @@ async function createNetworkAndSidecar(args) {
|
|
|
1532
1542
|
sidecarArgs.push(METADATA_ENDPOINT_IMAGE);
|
|
1533
1543
|
logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
|
|
1534
1544
|
try {
|
|
1535
|
-
const { stdout } = await execFileAsync$
|
|
1545
|
+
const { stdout } = await execFileAsync$2(getDockerCmd(), sidecarArgs, {
|
|
1536
1546
|
maxBuffer: 10 * 1024 * 1024,
|
|
1537
1547
|
...execEnvForSecrets(sidecarPassthroughEnv)
|
|
1538
1548
|
});
|
|
@@ -1603,7 +1613,7 @@ async function destroyNetworkOnly(networkName) {
|
|
|
1603
1613
|
if (!networkName) return;
|
|
1604
1614
|
const logger = getLogger().child("ecs-network");
|
|
1605
1615
|
try {
|
|
1606
|
-
await execFileAsync$
|
|
1616
|
+
await execFileAsync$2(getDockerCmd(), [
|
|
1607
1617
|
"network",
|
|
1608
1618
|
"rm",
|
|
1609
1619
|
networkName
|
|
@@ -1742,7 +1752,7 @@ async function resolveSsm(entry, shape, client) {
|
|
|
1742
1752
|
|
|
1743
1753
|
//#endregion
|
|
1744
1754
|
//#region src/local/ecs-task-runner.ts
|
|
1745
|
-
const execFileAsync = promisify(execFile);
|
|
1755
|
+
const execFileAsync$1 = promisify(execFile);
|
|
1746
1756
|
/**
|
|
1747
1757
|
* Top-level orchestrator for `cdkl run-task`. Coordinates image
|
|
1748
1758
|
* preparation, secret resolution, docker-network bring-up, container
|
|
@@ -1802,7 +1812,7 @@ async function cleanupEcsRun(state, options) {
|
|
|
1802
1812
|
if (state.network && !state.network.ownedByCaller) await destroyTaskNetwork(state.network);
|
|
1803
1813
|
state.network = void 0;
|
|
1804
1814
|
for (const v of state.dockerVolumeNames) try {
|
|
1805
|
-
await execFileAsync(getDockerCmd(), [
|
|
1815
|
+
await execFileAsync$1(getDockerCmd(), [
|
|
1806
1816
|
"volume",
|
|
1807
1817
|
"rm",
|
|
1808
1818
|
v
|
|
@@ -1883,7 +1893,7 @@ async function runEcsTask(task, options, state) {
|
|
|
1883
1893
|
logger.info(`Starting container '${container.name}' (image=${imagePlan.get(container.name)})`);
|
|
1884
1894
|
let id;
|
|
1885
1895
|
try {
|
|
1886
|
-
const { stdout } = await execFileAsync(getDockerCmd(), args, {
|
|
1896
|
+
const { stdout } = await execFileAsync$1(getDockerCmd(), args, {
|
|
1887
1897
|
maxBuffer: 10 * 1024 * 1024,
|
|
1888
1898
|
...execEnvForSecrets(sensitiveEnv)
|
|
1889
1899
|
});
|
|
@@ -1995,7 +2005,7 @@ async function waitForContainerHealthy(containerId, displayName) {
|
|
|
1995
2005
|
let lastStatus = "";
|
|
1996
2006
|
while (Date.now() < deadline) {
|
|
1997
2007
|
try {
|
|
1998
|
-
const { stdout } = await execFileAsync(getDockerCmd(), [
|
|
2008
|
+
const { stdout } = await execFileAsync$1(getDockerCmd(), [
|
|
1999
2009
|
"inspect",
|
|
2000
2010
|
"--format",
|
|
2001
2011
|
"{{.State.Health.Status}}",
|
|
@@ -2018,7 +2028,7 @@ async function waitForContainerHealthy(containerId, displayName) {
|
|
|
2018
2028
|
}
|
|
2019
2029
|
async function waitForContainerExit(containerId) {
|
|
2020
2030
|
try {
|
|
2021
|
-
const { stdout } = await execFileAsync(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
|
|
2031
|
+
const { stdout } = await execFileAsync$1(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
|
|
2022
2032
|
const code = Number.parseInt(stdout.trim(), 10);
|
|
2023
2033
|
return Number.isFinite(code) ? code : 1;
|
|
2024
2034
|
} catch (err) {
|
|
@@ -2028,7 +2038,7 @@ async function waitForContainerExit(containerId) {
|
|
|
2028
2038
|
}
|
|
2029
2039
|
async function stopContainer(containerId, graceSeconds) {
|
|
2030
2040
|
try {
|
|
2031
|
-
await execFileAsync(getDockerCmd(), [
|
|
2041
|
+
await execFileAsync$1(getDockerCmd(), [
|
|
2032
2042
|
"stop",
|
|
2033
2043
|
"-t",
|
|
2034
2044
|
String(graceSeconds),
|
|
@@ -2154,7 +2164,7 @@ async function realizeDockerVolumes(volumes, state) {
|
|
|
2154
2164
|
const dockerVolumeName = `${getEmbedConfig().resourceNamePrefix}-${v.name}-${randHex(4)}`;
|
|
2155
2165
|
args.push(dockerVolumeName);
|
|
2156
2166
|
try {
|
|
2157
|
-
await execFileAsync(getDockerCmd(), args);
|
|
2167
|
+
await execFileAsync$1(getDockerCmd(), args);
|
|
2158
2168
|
state.dockerVolumeNames.push(dockerVolumeName);
|
|
2159
2169
|
logger.debug(`Created docker volume ${dockerVolumeName} for task volume '${v.name}'`);
|
|
2160
2170
|
} catch (err) {
|
|
@@ -3698,12 +3708,17 @@ const DEFAULT_UPSTREAM_TIMEOUT_MS = 3e4;
|
|
|
3698
3708
|
async function startFrontDoorServer(opts) {
|
|
3699
3709
|
const logger = getLogger().child("front-door");
|
|
3700
3710
|
const host = opts.host ?? "127.0.0.1";
|
|
3701
|
-
const
|
|
3711
|
+
const requestHandler = (req, res) => {
|
|
3702
3712
|
handleProxyRequest(req, res, opts).catch((err) => {
|
|
3703
3713
|
logger.debug(`front-door request error: ${err instanceof Error ? err.message : String(err)}`);
|
|
3704
3714
|
if (!res.headersSent) writeError(res, 502, "Bad Gateway");
|
|
3705
3715
|
});
|
|
3706
|
-
}
|
|
3716
|
+
};
|
|
3717
|
+
const server = opts.tls ? createServer$1({
|
|
3718
|
+
cert: opts.tls.certPem,
|
|
3719
|
+
key: opts.tls.keyPem
|
|
3720
|
+
}, requestHandler) : createServer(requestHandler);
|
|
3721
|
+
const scheme = opts.tls ? "https" : "http";
|
|
3707
3722
|
server.on("connection", (socket) => socket.setNoDelay(true));
|
|
3708
3723
|
const boundPort = await new Promise((resolve, reject) => {
|
|
3709
3724
|
server.once("error", reject);
|
|
@@ -3720,6 +3735,7 @@ async function startFrontDoorServer(opts) {
|
|
|
3720
3735
|
return {
|
|
3721
3736
|
port: boundPort,
|
|
3722
3737
|
host,
|
|
3738
|
+
scheme,
|
|
3723
3739
|
server,
|
|
3724
3740
|
close: async () => {
|
|
3725
3741
|
if (closed) return;
|
|
@@ -3760,7 +3776,7 @@ function handleProxyRequest(req, res, opts) {
|
|
|
3760
3776
|
if (!action) return reply404(req, res, opts);
|
|
3761
3777
|
if (action.kind === "redirect" || action.kind === "fixed-response") {
|
|
3762
3778
|
req.resume();
|
|
3763
|
-
if (action.kind === "redirect") writeRedirect(res, action, req, opts.listenerPort);
|
|
3779
|
+
if (action.kind === "redirect") writeRedirect(res, action, req, opts.listenerPort, opts.tls ? "https" : "http");
|
|
3764
3780
|
else writeFixedResponse(res, action);
|
|
3765
3781
|
return Promise.resolve();
|
|
3766
3782
|
}
|
|
@@ -3798,7 +3814,7 @@ function handlePoolRequest(req, res, pool, opts) {
|
|
|
3798
3814
|
};
|
|
3799
3815
|
const headers = { ...req.headers };
|
|
3800
3816
|
stripHopByHopHeaders(headers);
|
|
3801
|
-
appendForwardedHeaders(headers, req, opts.listenerPort);
|
|
3817
|
+
appendForwardedHeaders(headers, req, opts.listenerPort, opts.tls ? "https" : "http");
|
|
3802
3818
|
const proxyReq = request({
|
|
3803
3819
|
host: endpoint.host,
|
|
3804
3820
|
port: endpoint.port,
|
|
@@ -3865,8 +3881,8 @@ function pickWeightedTarget(targets) {
|
|
|
3865
3881
|
* `#{query}` placeholders filled from the incoming request. We resolve those
|
|
3866
3882
|
* placeholders against the request the front-door received.
|
|
3867
3883
|
*/
|
|
3868
|
-
function writeRedirect(res, action, req, listenerPort) {
|
|
3869
|
-
const location = buildRedirectLocation(action, req, listenerPort);
|
|
3884
|
+
function writeRedirect(res, action, req, listenerPort, scheme) {
|
|
3885
|
+
const location = buildRedirectLocation(action, req, listenerPort, scheme);
|
|
3870
3886
|
res.writeHead(action.statusCode, {
|
|
3871
3887
|
location,
|
|
3872
3888
|
"content-type": "text/plain; charset=utf-8",
|
|
@@ -3874,15 +3890,20 @@ function writeRedirect(res, action, req, listenerPort) {
|
|
|
3874
3890
|
});
|
|
3875
3891
|
res.end();
|
|
3876
3892
|
}
|
|
3877
|
-
/**
|
|
3878
|
-
|
|
3893
|
+
/**
|
|
3894
|
+
* Build the `Location` URL for a redirect action, resolving ALB `#{...}`
|
|
3895
|
+
* placeholders. `scheme` is the receiving listener's protocol — it sets the
|
|
3896
|
+
* default for `#{protocol}` so an HTTPS listener redirects to `https://...`
|
|
3897
|
+
* by default, matching what a real ALB does.
|
|
3898
|
+
*/
|
|
3899
|
+
function buildRedirectLocation(action, req, listenerPort, scheme = "http") {
|
|
3879
3900
|
const url = req.url ?? "/";
|
|
3880
3901
|
const qIndex = url.indexOf("?");
|
|
3881
3902
|
const reqPath = qIndex === -1 ? url : url.slice(0, qIndex);
|
|
3882
3903
|
const reqQuery = qIndex === -1 ? "" : url.slice(qIndex + 1);
|
|
3883
3904
|
const rawHost = req.headers["host"];
|
|
3884
3905
|
const placeholders = {
|
|
3885
|
-
protocol:
|
|
3906
|
+
protocol: scheme,
|
|
3886
3907
|
host: ((Array.isArray(rawHost) ? rawHost[0] : rawHost) ?? "").split(":")[0] ?? "",
|
|
3887
3908
|
port: String(listenerPort),
|
|
3888
3909
|
path: reqPath.replace(/^\//, ""),
|
|
@@ -3949,7 +3970,7 @@ function handleLambdaRequest(req, res, lambda, opts) {
|
|
|
3949
3970
|
const body = Buffer.concat(chunks);
|
|
3950
3971
|
const forwardHeaders = { ...req.headers };
|
|
3951
3972
|
stripHopByHopHeaders(forwardHeaders);
|
|
3952
|
-
appendForwardedHeaders(forwardHeaders, req, opts.listenerPort);
|
|
3973
|
+
appendForwardedHeaders(forwardHeaders, req, opts.listenerPort, opts.tls ? "https" : "http");
|
|
3953
3974
|
const snapshot = snapshotFromIncoming(req, body);
|
|
3954
3975
|
for (const [name, value] of Object.entries(forwardHeaders)) {
|
|
3955
3976
|
if (value === void 0) continue;
|
|
@@ -4011,14 +4032,15 @@ function stripHopByHopHeaders(headers) {
|
|
|
4011
4032
|
/**
|
|
4012
4033
|
* Inject the ALB-style forwarding headers a downstream app may read. Appends
|
|
4013
4034
|
* the client IP to any existing `X-Forwarded-For` chain (ALB appends rather
|
|
4014
|
-
* than replaces) and stamps the scheme / listener port.
|
|
4035
|
+
* than replaces) and stamps the scheme / listener port. `scheme` follows the
|
|
4036
|
+
* listener's protocol so an HTTPS listener stamps `x-forwarded-proto: https`.
|
|
4015
4037
|
*/
|
|
4016
|
-
function appendForwardedHeaders(headers, req, listenerPort) {
|
|
4038
|
+
function appendForwardedHeaders(headers, req, listenerPort, scheme) {
|
|
4017
4039
|
const clientIp = req.socket.remoteAddress ?? "";
|
|
4018
4040
|
const existing = headers["x-forwarded-for"];
|
|
4019
4041
|
const chain = Array.isArray(existing) ? existing.join(", ") : existing;
|
|
4020
4042
|
headers["x-forwarded-for"] = chain ? `${chain}, ${clientIp}` : clientIp;
|
|
4021
|
-
headers["x-forwarded-proto"] =
|
|
4043
|
+
headers["x-forwarded-proto"] = scheme;
|
|
4022
4044
|
headers["x-forwarded-port"] = String(listenerPort);
|
|
4023
4045
|
}
|
|
4024
4046
|
function writeError(res, statusCode, message) {
|
|
@@ -4336,6 +4358,129 @@ function matchBitPrefix(a, b, prefixLength) {
|
|
|
4336
4358
|
return (a[fullBytes] & mask) === (b[fullBytes] & mask);
|
|
4337
4359
|
}
|
|
4338
4360
|
|
|
4361
|
+
//#endregion
|
|
4362
|
+
//#region src/local/front-door-tls.ts
|
|
4363
|
+
const execFileAsync = promisify(execFile);
|
|
4364
|
+
/**
|
|
4365
|
+
* Resolve a single global cert/key pair for HTTPS front-door listeners. When
|
|
4366
|
+
* BOTH `certPath` and `keyPath` are supplied, those PEM files are read from
|
|
4367
|
+
* disk. When neither is supplied, a long-lived self-signed cert is cached
|
|
4368
|
+
* under `$XDG_CACHE_HOME/cdk-local/alb-https/` (defaulting to
|
|
4369
|
+
* `~/.cache/cdk-local/alb-https/`) and reused across boots; it is regenerated
|
|
4370
|
+
* when missing or within `regenerateWithinDays` of expiry. Pairing is enforced
|
|
4371
|
+
* — supplying exactly one of `--tls-cert` / `--tls-key` is rejected with a
|
|
4372
|
+
* clear error before the server starts.
|
|
4373
|
+
*
|
|
4374
|
+
* The self-signed cert path subprocesses `openssl req -x509 ...`. `openssl`
|
|
4375
|
+
* is on macOS / Linux dev boxes and Docker base images by default; absence
|
|
4376
|
+
* surfaces as an actionable error pointing at the BYO recipe.
|
|
4377
|
+
*/
|
|
4378
|
+
async function resolveFrontDoorTlsMaterials(opts) {
|
|
4379
|
+
const hasCert = opts.certPath !== void 0 && opts.certPath !== "";
|
|
4380
|
+
const hasKey = opts.keyPath !== void 0 && opts.keyPath !== "";
|
|
4381
|
+
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.`);
|
|
4382
|
+
if (hasCert && hasKey) return {
|
|
4383
|
+
certPem: readPemOrThrow(opts.certPath, "--tls-cert"),
|
|
4384
|
+
keyPem: readPemOrThrow(opts.keyPath, "--tls-key")
|
|
4385
|
+
};
|
|
4386
|
+
return ensureSelfSignedCert({
|
|
4387
|
+
cacheDir: opts.cacheDir ?? defaultCacheDir(),
|
|
4388
|
+
regenerateWithinDays: opts.regenerateWithinDays ?? 30,
|
|
4389
|
+
validityDays: opts.validityDays ?? 825
|
|
4390
|
+
});
|
|
4391
|
+
}
|
|
4392
|
+
/** Default cache dir for the auto-generated self-signed cert. */
|
|
4393
|
+
function defaultCacheDir() {
|
|
4394
|
+
const xdg = process.env["XDG_CACHE_HOME"];
|
|
4395
|
+
return join(xdg && xdg !== "" ? xdg : join(homedir(), ".cache"), "cdk-local", "alb-https");
|
|
4396
|
+
}
|
|
4397
|
+
const CERT_FILENAME = "cert.pem";
|
|
4398
|
+
const KEY_FILENAME = "key.pem";
|
|
4399
|
+
async function ensureSelfSignedCert(opts) {
|
|
4400
|
+
const logger = getLogger().child("front-door-tls");
|
|
4401
|
+
const certPath = join(opts.cacheDir, CERT_FILENAME);
|
|
4402
|
+
const keyPath = join(opts.cacheDir, KEY_FILENAME);
|
|
4403
|
+
if (cachedPairIsFresh(certPath, keyPath, opts.regenerateWithinDays)) return {
|
|
4404
|
+
certPem: readFileSync(certPath),
|
|
4405
|
+
keyPem: readFileSync(keyPath)
|
|
4406
|
+
};
|
|
4407
|
+
mkdirSync(opts.cacheDir, { recursive: true });
|
|
4408
|
+
try {
|
|
4409
|
+
unlinkSync(certPath);
|
|
4410
|
+
} catch {}
|
|
4411
|
+
try {
|
|
4412
|
+
unlinkSync(keyPath);
|
|
4413
|
+
} catch {}
|
|
4414
|
+
try {
|
|
4415
|
+
await runOpenssl([
|
|
4416
|
+
"req",
|
|
4417
|
+
"-x509",
|
|
4418
|
+
"-newkey",
|
|
4419
|
+
"rsa:2048",
|
|
4420
|
+
"-nodes",
|
|
4421
|
+
"-keyout",
|
|
4422
|
+
keyPath,
|
|
4423
|
+
"-out",
|
|
4424
|
+
certPath,
|
|
4425
|
+
"-subj",
|
|
4426
|
+
"/CN=localhost",
|
|
4427
|
+
"-days",
|
|
4428
|
+
String(opts.validityDays),
|
|
4429
|
+
"-addext",
|
|
4430
|
+
"subjectAltName=DNS:localhost,IP:127.0.0.1"
|
|
4431
|
+
]);
|
|
4432
|
+
} catch (err) {
|
|
4433
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4434
|
+
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.`);
|
|
4435
|
+
}
|
|
4436
|
+
logger.info(`ALB front-door: generated self-signed cert at ${certPath} (valid ${opts.validityDays} days)`);
|
|
4437
|
+
return {
|
|
4438
|
+
certPem: readFileSync(certPath),
|
|
4439
|
+
keyPem: readFileSync(keyPath)
|
|
4440
|
+
};
|
|
4441
|
+
}
|
|
4442
|
+
function cachedPairIsFresh(certPath, keyPath, regenWithinDays) {
|
|
4443
|
+
try {
|
|
4444
|
+
statSync(certPath);
|
|
4445
|
+
statSync(keyPath);
|
|
4446
|
+
} catch {
|
|
4447
|
+
return false;
|
|
4448
|
+
}
|
|
4449
|
+
try {
|
|
4450
|
+
const notAfter = readCertNotAfter(certPath);
|
|
4451
|
+
if (notAfter === void 0) return false;
|
|
4452
|
+
const renewAt = notAfter.getTime() - regenWithinDays * 864e5;
|
|
4453
|
+
return Date.now() < renewAt;
|
|
4454
|
+
} catch {
|
|
4455
|
+
return false;
|
|
4456
|
+
}
|
|
4457
|
+
}
|
|
4458
|
+
/**
|
|
4459
|
+
* Read a cert's `notAfter` expiry timestamp. Used to decide whether to
|
|
4460
|
+
* regenerate the cached self-signed cert proactively. Returns `undefined`
|
|
4461
|
+
* when the cert is unreadable (caller then regenerates).
|
|
4462
|
+
*/
|
|
4463
|
+
function readCertNotAfter(certPath) {
|
|
4464
|
+
try {
|
|
4465
|
+
const cert = new X509Certificate(readFileSync(certPath));
|
|
4466
|
+
const parsed = new Date(cert.validTo);
|
|
4467
|
+
return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
|
|
4468
|
+
} catch {
|
|
4469
|
+
return;
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
async function runOpenssl(args) {
|
|
4473
|
+
await execFileAsync("openssl", args, { timeout: 3e4 });
|
|
4474
|
+
}
|
|
4475
|
+
function readPemOrThrow(path, flagName) {
|
|
4476
|
+
try {
|
|
4477
|
+
return readFileSync(path);
|
|
4478
|
+
} catch (err) {
|
|
4479
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4480
|
+
throw new Error(`${flagName}: cannot read PEM file at '${path}': ${msg}`);
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
|
|
4339
4484
|
//#endregion
|
|
4340
4485
|
//#region src/local/front-door-lambda-runner.ts
|
|
4341
4486
|
/** Forward the dev shell's AWS credential / region env into the Lambda container. */
|
|
@@ -4820,6 +4965,10 @@ async function buildFrontDoor(plan, options, logger) {
|
|
|
4820
4965
|
...action.messageBody !== void 0 && { messageBody: action.messageBody }
|
|
4821
4966
|
};
|
|
4822
4967
|
};
|
|
4968
|
+
const tlsMaterials = plan.listeners.some((l) => l.protocol === "HTTPS") ? await resolveFrontDoorTlsMaterials({
|
|
4969
|
+
certPath: options.tlsCert,
|
|
4970
|
+
keyPath: options.tlsKey
|
|
4971
|
+
}) : void 0;
|
|
4823
4972
|
try {
|
|
4824
4973
|
for (const listener of plan.listeners) {
|
|
4825
4974
|
const defaultRoute = listener.defaultAction ? toRouteAction(listener.defaultAction) : void 0;
|
|
@@ -4834,15 +4983,17 @@ async function buildFrontDoor(plan, options, logger) {
|
|
|
4834
4983
|
target: toRouteAction(r.action)
|
|
4835
4984
|
}));
|
|
4836
4985
|
const route = (req) => matchAlbPathRule(req, ruleRoutes) ?? defaultRoute;
|
|
4986
|
+
const tls = listener.protocol === "HTTPS" ? tlsMaterials : void 0;
|
|
4837
4987
|
const server = await startFrontDoorServer({
|
|
4838
4988
|
route,
|
|
4839
4989
|
port: listener.hostPort,
|
|
4840
4990
|
host: containerHost,
|
|
4841
4991
|
listenerPort: listener.listenerPort,
|
|
4842
|
-
label: `listener port ${listener.listenerPort}
|
|
4992
|
+
label: `listener port ${listener.listenerPort}`,
|
|
4993
|
+
...tls ? { tls } : {}
|
|
4843
4994
|
});
|
|
4844
4995
|
servers.push(server);
|
|
4845
|
-
logger.info(`ALB front-door:
|
|
4996
|
+
logger.info(`ALB front-door: ${server.scheme}://${server.host}:${server.port} (listener port ${listener.listenerPort})`);
|
|
4846
4997
|
if (listener.defaultAction) logger.info(` default -> ${describeAction(listener.defaultAction)}`);
|
|
4847
4998
|
for (const r of [...listener.rules].sort((a, b) => a.priority - b.priority)) logger.info(` ${describeConditions(r)} (priority ${r.priority}) -> ${describeAction(r.action)}`);
|
|
4848
4999
|
if (!listener.defaultAction) logger.info(" (no default action: unmatched requests return 404)");
|
|
@@ -5144,8 +5295,12 @@ function createLocalStartServiceCommand(opts = {}) {
|
|
|
5144
5295
|
* functions (`TargetType:"lambda"` target groups — #123: the TG -> backing
|
|
5145
5296
|
* `AWS::Lambda::Function` is resolved and the front-door invokes it locally per
|
|
5146
5297
|
* request); `redirect` / `fixed-response` actions. A single weighted forward may
|
|
5147
|
-
* mix ECS and Lambda targets.
|
|
5148
|
-
*
|
|
5298
|
+
* mix ECS and Lambda targets. HTTPS listeners are served — local TLS
|
|
5299
|
+
* termination uses a user-supplied or auto-generated self-signed cert/key
|
|
5300
|
+
* pair (the deployed `Listener.Certificates[]` ACM ARNs are not fetched,
|
|
5301
|
+
* because ACM private keys are not retrievable by design). Skipped with a
|
|
5302
|
+
* warning: TLS listeners (NLB-style, not ALB) and `authenticate-cognito` /
|
|
5303
|
+
* `authenticate-oidc` actions.
|
|
5149
5304
|
*/
|
|
5150
5305
|
const ALB_TYPE = "AWS::ElasticLoadBalancingV2::LoadBalancer";
|
|
5151
5306
|
const LISTENER_TYPE = "AWS::ElasticLoadBalancingV2::Listener";
|
|
@@ -5172,10 +5327,11 @@ function resolveAlbFrontDoor(stack, albLogicalId) {
|
|
|
5172
5327
|
const port = parsePort(props["Port"]);
|
|
5173
5328
|
if (port === void 0) continue;
|
|
5174
5329
|
const protocol = typeof props["Protocol"] === "string" ? props["Protocol"] : "HTTP";
|
|
5175
|
-
if (protocol !== "HTTP") {
|
|
5176
|
-
warnings.push(`Listener '${listenerLogicalId}' on port ${port} uses protocol ${protocol}; the local ALB front-door supports HTTP listeners only (TLS
|
|
5330
|
+
if (protocol !== "HTTP" && protocol !== "HTTPS") {
|
|
5331
|
+
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.`);
|
|
5177
5332
|
continue;
|
|
5178
5333
|
}
|
|
5334
|
+
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.`);
|
|
5179
5335
|
const defaultAction = resolveAction(props["DefaultActions"], resources, tgToService, stackName, `Listener '${listenerLogicalId}' (port ${port}) default action`, warnings);
|
|
5180
5336
|
const rules = [];
|
|
5181
5337
|
for (const { ruleLogicalId, ruleProps } of rulesByListener.get(listenerLogicalId) ?? []) {
|
|
@@ -5201,7 +5357,7 @@ function resolveAlbFrontDoor(stack, albLogicalId) {
|
|
|
5201
5357
|
if (!defaultAction && rules.length === 0) continue;
|
|
5202
5358
|
listeners.push({
|
|
5203
5359
|
listenerPort: port,
|
|
5204
|
-
listenerProtocol:
|
|
5360
|
+
listenerProtocol: protocol,
|
|
5205
5361
|
listenerLogicalId,
|
|
5206
5362
|
...defaultAction ? { defaultAction } : {},
|
|
5207
5363
|
rules
|
|
@@ -5791,6 +5947,7 @@ function albStrategy(options) {
|
|
|
5791
5947
|
listeners.push({
|
|
5792
5948
|
listenerPort: listener.listenerPort,
|
|
5793
5949
|
hostPort,
|
|
5950
|
+
protocol: listener.listenerProtocol,
|
|
5794
5951
|
...listener.defaultAction ? { defaultAction: qualify(listener.defaultAction) } : {},
|
|
5795
5952
|
rules: listener.rules.map((r) => ({
|
|
5796
5953
|
priority: r.priority,
|
|
@@ -5829,7 +5986,7 @@ function albStrategy(options) {
|
|
|
5829
5986
|
*/
|
|
5830
5987
|
function createLocalStartAlbCommand(opts = {}) {
|
|
5831
5988
|
setEmbedConfig(opts.embedConfig);
|
|
5832
|
-
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
|
|
5989
|
+
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) => {
|
|
5833
5990
|
await runEcsServiceEmulator(targets, options, albStrategy(options), opts.extraStateProviders);
|
|
5834
5991
|
})));
|
|
5835
5992
|
}
|
|
@@ -5906,4 +6063,4 @@ function createLocalListCommand(opts = {}) {
|
|
|
5906
6063
|
|
|
5907
6064
|
//#endregion
|
|
5908
6065
|
export { createLocalRunTaskCommand as a, createLocalStartServiceCommand as i, formatTargetListing as n, createLocalInvokeAgentCoreCommand as o, createLocalStartAlbCommand as r, createLocalInvokeCommand as s, createLocalListCommand as t };
|
|
5909
|
-
//# sourceMappingURL=local-list-
|
|
6066
|
+
//# sourceMappingURL=local-list-DT8qRbKy.js.map
|