cdk-local 0.73.6 → 0.74.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/dist/cli.js +2 -2
- package/dist/{docker-cmd-voNPrcRh.js → docker-cmd-Dqx2YENO.js} +47 -15
- package/dist/docker-cmd-Dqx2YENO.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/internal.d.ts +2 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -2
- package/dist/{local-list-DgwUotOK.js → local-list-Dn0Al276.js} +558 -461
- package/dist/local-list-Dn0Al276.js.map +1 -0
- package/dist/{local-list-ChlobAQL.d.ts → local-list-cmhJbUBN.d.ts} +10 -2
- package/dist/{local-list-ChlobAQL.d.ts.map → local-list-cmhJbUBN.d.ts.map} +1 -1
- package/package.json +1 -1
- package/dist/docker-cmd-voNPrcRh.js.map +0 -1
- package/dist/local-list-DgwUotOK.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as runDockerStreaming, c as getEmbedConfig, i as runDockerForeground, n as formatDockerLoginError, o as spawnStreaming, r as getDockerCmd, s as getLogger, u as setEmbedConfig } from "./docker-cmd-
|
|
1
|
+
import { a as runDockerStreaming, c as getEmbedConfig, i as runDockerForeground, n as formatDockerLoginError, o as spawnStreaming, r as getDockerCmd, s as getLogger, u as setEmbedConfig } from "./docker-cmd-Dqx2YENO.js";
|
|
2
2
|
import { cpSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
4
|
import * as path$1 from "node:path";
|
|
@@ -6498,7 +6498,10 @@ async function pullImage(image, skipPull) {
|
|
|
6498
6498
|
}
|
|
6499
6499
|
logger.debug(`Pulling ${image} (silent — pass --verbose to stream progress)`);
|
|
6500
6500
|
try {
|
|
6501
|
-
await runDockerStreaming(["pull", image], {
|
|
6501
|
+
await runDockerStreaming(["pull", image], {
|
|
6502
|
+
streamLive: false,
|
|
6503
|
+
progressLabel: `Pulling ${image}`
|
|
6504
|
+
});
|
|
6502
6505
|
} catch (err) {
|
|
6503
6506
|
const e = err;
|
|
6504
6507
|
if (e.exitCode === void 0 || e.exitCode === null) throw new DockerRunnerError(`docker pull ${image} failed: ${e.message ?? String(err)}`);
|
|
@@ -6773,7 +6776,8 @@ async function buildDockerImage(asset, cdkOutDir, options) {
|
|
|
6773
6776
|
try {
|
|
6774
6777
|
await runDockerStreaming(buildArgs, {
|
|
6775
6778
|
cwd: contextDir,
|
|
6776
|
-
env: { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" }
|
|
6779
|
+
env: { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" },
|
|
6780
|
+
...options.progressLabel !== void 0 && { progressLabel: options.progressLabel }
|
|
6777
6781
|
});
|
|
6778
6782
|
} catch (err) {
|
|
6779
6783
|
const e = err;
|
|
@@ -7080,7 +7084,8 @@ async function buildContainerImage(asset, cdkOutDir, options) {
|
|
|
7080
7084
|
const actualTag = await buildDockerImage(asset, cdkOutDir, {
|
|
7081
7085
|
tag,
|
|
7082
7086
|
platform,
|
|
7083
|
-
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`)
|
|
7087
|
+
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for container Lambda asset (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`),
|
|
7088
|
+
progressLabel: `Building container image (platform=${platform})`
|
|
7084
7089
|
});
|
|
7085
7090
|
if (actualTag !== tag) {
|
|
7086
7091
|
logger.debug(`Re-tagging executable-built image '${actualTag}' → '${tag}'`);
|
|
@@ -15269,7 +15274,8 @@ async function localStartApiCommand(targets, options, extraStateProviders) {
|
|
|
15269
15274
|
const appCmd = resolveApp(options.app);
|
|
15270
15275
|
if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
|
|
15271
15276
|
const overrides = readEnvOverridesFile$4(options.envVars);
|
|
15272
|
-
const
|
|
15277
|
+
const debugPortRaw = options.debugPortBase ?? options.debugPort;
|
|
15278
|
+
const debugPortBase = debugPortRaw ? parseDebugPort(debugPortRaw) : void 0;
|
|
15273
15279
|
const perLambdaConcurrency = parsePerLambdaConcurrency(options.perLambdaConcurrency);
|
|
15274
15280
|
const inlineTmpDirs = /* @__PURE__ */ new Set();
|
|
15275
15281
|
const layerTmpDirs = /* @__PURE__ */ new Set();
|
|
@@ -15378,7 +15384,9 @@ async function localStartApiCommand(targets, options, extraStateProviders) {
|
|
|
15378
15384
|
layerTmpDirs,
|
|
15379
15385
|
stateByStack,
|
|
15380
15386
|
skipPull: options.pull === false,
|
|
15387
|
+
skipBuild: options.build === false,
|
|
15381
15388
|
...options.profile !== void 0 && { profile: options.profile },
|
|
15389
|
+
...options.ecrRoleArn !== void 0 && { ecrRoleArn: options.ecrRoleArn },
|
|
15382
15390
|
...options.layerRoleArn !== void 0 && { layerRoleArn: options.layerRoleArn },
|
|
15383
15391
|
...profileCredentials && { profileCredentials },
|
|
15384
15392
|
...profileCredsFile && { profileCredsFile },
|
|
@@ -16050,7 +16058,11 @@ async function buildContainerSpec(args) {
|
|
|
16050
16058
|
codeDir = lambda.codePath ?? materializeInlineCode$2(lambda.handler, lambda.inlineCode ?? "", resolveRuntimeFileExtension(lambda.runtime), inlineTmpDirs);
|
|
16051
16059
|
optDir = await materializeLambdaLayers$1(lambda.layers, layerTmpDirs, layerRoleArn);
|
|
16052
16060
|
} else {
|
|
16053
|
-
imageRef = (await resolveContainerImageForStartApi(lambda, skipPull, args.profile
|
|
16061
|
+
imageRef = (await resolveContainerImageForStartApi(lambda, skipPull, args.profile, {
|
|
16062
|
+
...args.skipBuild !== void 0 && { noBuild: args.skipBuild },
|
|
16063
|
+
...args.ecrRoleArn !== void 0 && { ecrRoleArn: args.ecrRoleArn },
|
|
16064
|
+
...args.stsRegion !== void 0 && { region: args.stsRegion }
|
|
16065
|
+
})).imageRef;
|
|
16054
16066
|
platform = architectureToPlatform(lambda.architecture);
|
|
16055
16067
|
}
|
|
16056
16068
|
let templateEnv = getTemplateEnv$1(lambda.resource);
|
|
@@ -16171,23 +16183,30 @@ async function buildContainerSpec(args) {
|
|
|
16171
16183
|
* Resolve a container Lambda's local docker image — local build from
|
|
16172
16184
|
* `cdk.out` asset manifest first, ECR-pull fallback when the asset
|
|
16173
16185
|
* manifest has no matching entry. Mirrors `cdkl invoke`'s
|
|
16174
|
-
* `resolveContainerImagePlan` shape
|
|
16175
|
-
* need the no-build flag (deterministic-tag cache reuse is automatic
|
|
16176
|
-
* across reloads because the per-Lambda tag is content-addressed).
|
|
16186
|
+
* `resolveContainerImagePlan` shape.
|
|
16177
16187
|
*
|
|
16178
|
-
*
|
|
16179
|
-
* `cdkl invoke`
|
|
16180
|
-
*
|
|
16188
|
+
* Issue #249 / C7 + C8 — `noBuild` and `ecrRoleArn` are accepted for
|
|
16189
|
+
* parity with `cdkl invoke` / `cdkl run-task`. `noBuild` short-circuits
|
|
16190
|
+
* the local-asset build path (the deterministic content-addressed tag
|
|
16191
|
+
* must already be in the local registry). `ecrRoleArn` lets the
|
|
16192
|
+
* ECR-pull fallback authenticate against cross-account / centralized
|
|
16193
|
+
* registries via STS AssumeRole — same-account / same-region pulls do
|
|
16194
|
+
* not need it.
|
|
16181
16195
|
*/
|
|
16182
|
-
async function resolveContainerImageForStartApi(lambda, skipPull, profile) {
|
|
16196
|
+
async function resolveContainerImageForStartApi(lambda, skipPull, profile, options) {
|
|
16183
16197
|
const logger = getLogger();
|
|
16184
16198
|
const localBuild = await resolveLocalBuildPlan$1(lambda);
|
|
16185
|
-
if (localBuild) return { imageRef: await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, {
|
|
16199
|
+
if (localBuild) return { imageRef: await buildContainerImage(localBuild.asset, localBuild.cdkOutDir, {
|
|
16200
|
+
architecture: lambda.architecture,
|
|
16201
|
+
...options?.noBuild !== void 0 && { noBuild: options.noBuild }
|
|
16202
|
+
}) };
|
|
16186
16203
|
if (!parseEcrUri(lambda.imageUri)) throw new Error(`Container Lambda '${lambda.logicalId}' has no matching asset in cdk.out, and Code.ImageUri '${lambda.imageUri}' is not an ECR URI ${getEmbedConfig().binaryName} can authenticate against. Re-synthesize the CDK app (so cdk.out includes the build context) or deploy the image to ECR first.`);
|
|
16187
|
-
logger.info(`No matching cdk.out asset for ${lambda.imageUri}; falling back to ECR pull
|
|
16204
|
+
logger.info(`No matching cdk.out asset for ${lambda.imageUri}; falling back to ECR pull...`);
|
|
16188
16205
|
return { imageRef: await pullEcrImage(lambda.imageUri, {
|
|
16189
16206
|
skipPull,
|
|
16190
|
-
...profile !== void 0 && { profile }
|
|
16207
|
+
...profile !== void 0 && { profile },
|
|
16208
|
+
...options?.ecrRoleArn !== void 0 && { ecrRoleArn: options.ecrRoleArn },
|
|
16209
|
+
...options?.region !== void 0 && { region: options.region }
|
|
16191
16210
|
}) };
|
|
16192
16211
|
}
|
|
16193
16212
|
/**
|
|
@@ -16925,7 +16944,7 @@ function createLocalStartApiCommand(opts = {}) {
|
|
|
16925
16944
|
* `--help` clusters. Chainable: returns `cmd`.
|
|
16926
16945
|
*/
|
|
16927
16946
|
function addStartApiSpecificOptions(cmd) {
|
|
16928
|
-
return cmd.addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--all-stacks", "Serve every stack's API in a multi-stack app (each API on its own port) instead of erroring out. Mutually exclusive with a positional target subset, --stack, and an explicit --from-cfn-stack <name>; the bare --from-cfn-stack flag stays compatible (binds each routed stack to its own CFn stack).").default(false)).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when the CDK app's source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <targets...> argument instead. Accepts a SINGLE identifier; for a subset pass multiple positional targets. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in Lambda env vars with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the resolved stack name per routed stack; pass an explicit value when a single CFn stack should serve every routed stack. Fn::GetAtt is warn-and-dropped in v1 (CFn ListStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).addOption(new Option("--mtls-truststore <path>", `PEM-encoded CA bundle for client-certificate verification (mutual TLS). When set, the local server switches from HTTP to HTTPS and the TLS handshake rejects clients whose certificate doesn't chain to one of these CAs. Verified certs are surfaced on the Lambda event under requestContext.identity.clientCert (REST v1) / requestContext.authentication.clientCert (HTTP API v2). Must be set together with --mtls-cert + --mtls-key; partial flag sets are rejected. Generate a CA + server + client cert for local dev: openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca.pem -subj "/CN=${getEmbedConfig().resourceNamePrefix}-ca" -days 365; openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-csr.pem -subj "/CN=localhost"; openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365; openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-csr.pem -subj "/CN=client"; openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365; curl --cacert ca.pem --cert client-cert.pem --key client-key.pem https://localhost:<port>/...`)).addOption(new Option("--mtls-cert <path>", "PEM-encoded server certificate for mutual TLS. Self-signed is fine for local dev. Must be set together with --mtls-truststore + --mtls-key.")).addOption(new Option("--mtls-key <path>", "PEM-encoded server private key matching --mtls-cert. Must be set together with --mtls-truststore + --mtls-cert.")).addOption(new Option("--strict-sigv4", "Opt-in: DENY AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id — e.g. a federated / Cognito Identity Pool / cross-account signer — OR no local AWS credentials configured) instead of the default warn-and-pass. DEFAULT off: cdk-local warn-and-passes unverifiable IAM requests with a placeholder principalId so local dev exercises app logic without reproducing an auth boundary it cannot fully emulate. OAC-fronted Function URLs always warn-and-pass regardless.").default(false));
|
|
16947
|
+
return cmd.addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--all-stacks", "Serve every stack's API in a multi-stack app (each API on its own port) instead of erroring out. Mutually exclusive with a positional target subset, --stack, and an explicit --from-cfn-stack <name>; the bare --from-cfn-stack flag stays compatible (binds each routed stack to its own CFn stack).").default(false)).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--no-build", "Skip docker build on every container Lambda local-asset build (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ZIP Lambdas and the IMAGE ECR-pull path. Compatible with --no-pull.")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--debug-port <port>", "Alias of --debug-port-base for parity with `cdkl invoke --debug-port`. Reserves a contiguous --debug-port range (one per Lambda) starting at <port>.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries on the container-Lambda ECR-pull fallback. Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when the CDK app's source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <targets...> argument instead. Accepts a SINGLE identifier; for a subset pass multiple positional targets. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in Lambda env vars with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the resolved stack name per routed stack; pass an explicit value when a single CFn stack should serve every routed stack. Fn::GetAtt is warn-and-dropped in v1 (CFn ListStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).addOption(new Option("--mtls-truststore <path>", `PEM-encoded CA bundle for client-certificate verification (mutual TLS). When set, the local server switches from HTTP to HTTPS and the TLS handshake rejects clients whose certificate doesn't chain to one of these CAs. Verified certs are surfaced on the Lambda event under requestContext.identity.clientCert (REST v1) / requestContext.authentication.clientCert (HTTP API v2). Must be set together with --mtls-cert + --mtls-key; partial flag sets are rejected. Generate a CA + server + client cert for local dev: openssl req -x509 -newkey rsa:2048 -nodes -keyout ca-key.pem -out ca.pem -subj "/CN=${getEmbedConfig().resourceNamePrefix}-ca" -days 365; openssl req -newkey rsa:2048 -nodes -keyout server-key.pem -out server-csr.pem -subj "/CN=localhost"; openssl x509 -req -in server-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -days 365; openssl req -newkey rsa:2048 -nodes -keyout client-key.pem -out client-csr.pem -subj "/CN=client"; openssl x509 -req -in client-csr.pem -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365; curl --cacert ca.pem --cert client-cert.pem --key client-key.pem https://localhost:<port>/...`)).addOption(new Option("--mtls-cert <path>", "PEM-encoded server certificate for mutual TLS. Self-signed is fine for local dev. Must be set together with --mtls-truststore + --mtls-key.")).addOption(new Option("--mtls-key <path>", "PEM-encoded server private key matching --mtls-cert. Must be set together with --mtls-truststore + --mtls-cert.")).addOption(new Option("--strict-sigv4", "Opt-in: DENY AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id — e.g. a federated / Cognito Identity Pool / cross-account signer — OR no local AWS credentials configured) instead of the default warn-and-pass. DEFAULT off: cdk-local warn-and-passes unverifiable IAM requests with a placeholder principalId so local dev exercises app logic without reproducing an auth boundary it cannot fully emulate. OAC-fronted Function URLs always warn-and-pass regardless.").default(false));
|
|
16929
16948
|
}
|
|
16930
16949
|
|
|
16931
16950
|
//#endregion
|
|
@@ -17554,7 +17573,7 @@ function createLocalInvokeCommand(opts = {}) {
|
|
|
17554
17573
|
* `--help` clusters. Chainable: returns `cmd`.
|
|
17555
17574
|
*/
|
|
17556
17575
|
function addInvokeSpecificOptions(cmd) {
|
|
17557
|
-
return cmd.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("--no-pull", "Skip docker pull. Semantics differ by code path: ZIP Lambdas skip pulling the public Lambda base image; Container Lambdas on the local-build path are a no-op (docker build does not refresh the FROM cache by default); Container Lambdas on the ECR-pull fallback skip docker pull AND error if the image is not in the local cache (re-run without --no-pull or pre-pull manually).")).addOption(new Option("--no-build", "Skip docker build on the IMAGE local-build path (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ZIP Lambdas and the IMAGE ECR-pull path. Compatible with --no-pull.")).addOption(new Option("--debug-port <port>", "Node --inspect-brk port (default: off)")).addOption(new Option("--container-host <host>", "Host to bind the RIE port to").default("127.0.0.1")).addOption(new Option("--assume-role [arn]", "Assume the Lambda's deployed execution role and forward STS-issued temp credentials to the container so the handler runs with the deployed function's narrow permissions. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) auto-resolves the function's execution role ARN from state (requires an active state source); (3) `--no-assume-role` explicitly opts out. Off by default — when omitted, the developer's shell credentials are forwarded unchanged (SAM-compatible default). STS failures degrade to a warn + dev-creds fallback.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers. Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. 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. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the resolved stack name; pass an explicit value when CFn stack name differs. Fn::GetAtt is warn-and-dropped in v1 (CFn ListStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
|
|
17576
|
+
return cmd.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\"}, \"Parameters\": {...}})")).addOption(new Option("--no-pull", "Skip docker pull. Semantics differ by code path: ZIP Lambdas skip pulling the public Lambda base image; Container Lambdas on the local-build path are a no-op (docker build does not refresh the FROM cache by default); Container Lambdas on the ECR-pull fallback skip docker pull AND error if the image is not in the local cache (re-run without --no-pull or pre-pull manually).")).addOption(new Option("--no-build", "Skip docker build on the IMAGE local-build path (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ZIP Lambdas and the IMAGE ECR-pull path. Compatible with --no-pull.")).addOption(new Option("--debug-port <port>", "Node --inspect-brk port (default: off)")).addOption(new Option("--container-host <host>", "Host IP the host uses to bind the RIE port to. Must be a numeric IP (Docker rejects hostnames here). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--assume-role [arn]", "Assume the Lambda's deployed execution role and forward STS-issued temp credentials to the container so the handler runs with the deployed function's narrow permissions. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) auto-resolves the function's execution role ARN from state (requires an active state source); (3) `--no-assume-role` explicitly opts out. Off by default — when omitted, the developer's shell credentials are forwarded unchanged (SAM-compatible default). STS failures degrade to a warn + dev-creds fallback.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers. Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. 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. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the resolved stack name; pass an explicit value when CFn stack name differs. Fn::GetAtt is warn-and-dropped in v1 (CFn ListStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
|
|
17558
17577
|
}
|
|
17559
17578
|
|
|
17560
17579
|
//#endregion
|
|
@@ -17616,7 +17635,7 @@ async function buildAgentCoreCodeImage(options) {
|
|
|
17616
17635
|
"--file",
|
|
17617
17636
|
dockerfilePath,
|
|
17618
17637
|
options.sourceDir
|
|
17619
|
-
]);
|
|
17638
|
+
], { progressLabel: `Building agent image (runtime=${options.runtime})` });
|
|
17620
17639
|
} catch (err) {
|
|
17621
17640
|
const stderr = err.stderr?.trim();
|
|
17622
17641
|
throw new LocalInvokeBuildError(`docker build failed for AgentCore code artifact (${options.sourceDir})${stderr ? `: ${stderr}` : ""}`);
|
|
@@ -19219,7 +19238,7 @@ function createLocalInvokeAgentCoreCommand(opts = {}) {
|
|
|
19219
19238
|
* in three `--help` clusters. Chainable: returns `cmd`.
|
|
19220
19239
|
*/
|
|
19221
19240
|
function addInvokeAgentCoreSpecificOptions(cmd) {
|
|
19222
|
-
return cmd.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
|
|
19241
|
+
return cmd.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\"}, \"Parameters\": {...}})")).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 this command SUPPLIES (the supplier role) when the runtime declares a customJwtAuthorizer — `cdkl invoke-agentcore` is the local-dev client making the outbound call, so it always presents this token to its own invocation. Verified against the runtime's OIDC discovery URL (signature / issuer / expiry / audience) before the container starts, then forwarded to /invocations as Authorization: Bearer <jwt>. Contrast with `cdkl start-alb --bearer-token`, where the role is reversed — the ALB front-door RECEIVES inbound requests and injects this token only as a default when an inbound request has none (default-when-missing).")).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). Defaults to linux/arm64 because the cloud AgentCore Runtime requires arm64. Override to linux/amd64 only when iterating against an amd64 dev container locally; note the image will not run on the cloud runtime as-is.").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 IP the host uses to bind the agent port to. Must be a numeric IP (Docker rejects hostnames here). Defaults to 127.0.0.1.").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."));
|
|
19223
19242
|
}
|
|
19224
19243
|
|
|
19225
19244
|
//#endregion
|
|
@@ -20130,10 +20149,19 @@ async function prepareOneImage(task, container, options) {
|
|
|
20130
20149
|
else if (entries.length === 1) asset = entries[0][1];
|
|
20131
20150
|
if (!asset) throw new EcsTaskRunnerError(`Container '${container.name}' references a CDK asset image but no matching entry was found in cdk.out. Re-synthesize the CDK app and retry.`);
|
|
20132
20151
|
const tag = `${getEmbedConfig().resourceNamePrefix}-run-task-${(image.assetHash ?? "single").slice(0, 16)}`;
|
|
20152
|
+
if (options.skipBuild === true) {
|
|
20153
|
+
const logger = getLogger().child("ecs-runner");
|
|
20154
|
+
if (image.assetHash === void 0) throw new LocalInvokeBuildError(`Cannot honor --no-build for ECS container '${container.name}': the asset has no stable assetHash (synthetic tag '${tag}' may collide with unrelated prior runs). Drop --no-build, or re-synthesize with an explicit assetHash on the ContainerImage.fromAsset call.`);
|
|
20155
|
+
logger.info(`Skipping docker build (--no-build) for container '${container.name}'; verifying ${tag} is in local registry...`);
|
|
20156
|
+
if (!await isImageInLocalCache(tag)) throw new LocalInvokeBuildError(`image '${tag}' not in local registry and --no-build is set (ECS container '${container.name}'); remove --no-build or run \`docker build\` manually first.`);
|
|
20157
|
+
logger.debug(`Local tag ${tag} is cached; skipping build for container '${container.name}'.`);
|
|
20158
|
+
return tag;
|
|
20159
|
+
}
|
|
20133
20160
|
const actualTag = await buildDockerImage(asset, cdkOutDir, {
|
|
20134
20161
|
tag,
|
|
20135
20162
|
...options.platformOverride !== void 0 && { platform: options.platformOverride },
|
|
20136
|
-
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for ECS container '${container.name}' (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`)
|
|
20163
|
+
wrapError: (stderr) => new LocalInvokeBuildError(`docker build failed for ECS container '${container.name}' (${asset.source.directory ?? asset.source.executable?.join(" ")}): ${stderr}`),
|
|
20164
|
+
progressLabel: `Building container image for '${container.name}'`
|
|
20137
20165
|
});
|
|
20138
20166
|
if (actualTag !== tag) try {
|
|
20139
20167
|
await runDockerStreaming([
|
|
@@ -20356,419 +20384,85 @@ function shellJoin(parts) {
|
|
|
20356
20384
|
}
|
|
20357
20385
|
|
|
20358
20386
|
//#endregion
|
|
20359
|
-
//#region src/
|
|
20360
|
-
|
|
20361
|
-
|
|
20362
|
-
|
|
20363
|
-
|
|
20364
|
-
|
|
20365
|
-
|
|
20366
|
-
|
|
20367
|
-
|
|
20368
|
-
|
|
20369
|
-
|
|
20370
|
-
|
|
20371
|
-
|
|
20372
|
-
|
|
20373
|
-
|
|
20374
|
-
|
|
20375
|
-
|
|
20376
|
-
|
|
20377
|
-
|
|
20378
|
-
if (!cleanupPromise) cleanupPromise = (async () => {
|
|
20379
|
-
try {
|
|
20380
|
-
await cleanupEcsRun(state, { keepRunning: options.keepRunning });
|
|
20381
|
-
} catch (err) {
|
|
20382
|
-
getLogger().debug(`cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
20383
|
-
}
|
|
20384
|
-
if (profileCredsFile) try {
|
|
20385
|
-
await profileCredsFile.dispose();
|
|
20386
|
-
} catch (err) {
|
|
20387
|
-
getLogger().debug(`Failed to remove profile credentials tmpdir ${profileCredsFile.hostPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
20388
|
-
}
|
|
20389
|
-
})();
|
|
20390
|
-
await cleanupPromise;
|
|
20391
|
-
};
|
|
20392
|
-
try {
|
|
20393
|
-
await applyRoleArnIfSet({
|
|
20394
|
-
roleArn: options.roleArn,
|
|
20395
|
-
region: options.region,
|
|
20396
|
-
profile: options.profile
|
|
20397
|
-
});
|
|
20398
|
-
await ensureDockerAvailable();
|
|
20399
|
-
const appCmd = resolveApp(options.app);
|
|
20400
|
-
if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
|
|
20401
|
-
logger.info("Synthesizing CDK app...");
|
|
20402
|
-
const synthesizer = new Synthesizer();
|
|
20403
|
-
const context = parseContextOptions(options.context);
|
|
20404
|
-
const synthOpts = {
|
|
20405
|
-
app: appCmd,
|
|
20406
|
-
output: options.output,
|
|
20407
|
-
...options.region && { region: options.region },
|
|
20408
|
-
...options.profile && { profile: options.profile },
|
|
20409
|
-
...Object.keys(context).length > 0 && { context }
|
|
20410
|
-
};
|
|
20411
|
-
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
20412
|
-
const resolvedTarget = await resolveSingleTarget(target, {
|
|
20413
|
-
entries: listTargets(stacks).ecsTaskDefinitions,
|
|
20414
|
-
message: "Select an ECS task definition to run",
|
|
20415
|
-
noun: "ECS task definitions",
|
|
20416
|
-
onMissing: () => new CdkLocalError(`${getEmbedConfig().cliName} run-task requires a <target> (an ECS task definition display path or logical ID). Run \`${getEmbedConfig().cliName} list\` to see them, or run it in a TTY to pick interactively.`, "LOCAL_RUN_TASK_TARGET_REQUIRED")
|
|
20417
|
-
});
|
|
20418
|
-
const candidate = pickCandidateStack$1(parseEcsTarget(resolvedTarget).stackPattern, stacks);
|
|
20419
|
-
stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
|
|
20420
|
-
const imageContext = await buildEcsImageResolutionContext$1(candidate, stateProvider, options);
|
|
20421
|
-
const task = resolveEcsTaskTarget(resolvedTarget, stacks, imageContext);
|
|
20422
|
-
logger.info(`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`);
|
|
20423
|
-
const taskNeeds = detectEcsImageResolutionNeeds(stacks.find((s) => s.stackName === task.stack.stackName) ?? task.stack);
|
|
20424
|
-
if (stateProvider && taskNeeds.needsCrossStackResolver) {
|
|
20425
|
-
const consumerRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? task.stack.region ?? "us-east-1";
|
|
20426
|
-
const resolver = await stateProvider.buildCrossStackResolver(consumerRegion);
|
|
20427
|
-
if (resolver) await applyCrossStackResolverToTask(task, {
|
|
20428
|
-
resources: imageContext?.stateResources ?? {},
|
|
20429
|
-
...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
|
|
20430
|
-
...imageContext?.stateParameters && { parameters: imageContext.stateParameters },
|
|
20431
|
-
...imageContext?.stateSensitiveParameters?.length && { sensitiveParameters: new Set(imageContext.stateSensitiveParameters) },
|
|
20432
|
-
consumerRegion,
|
|
20433
|
-
crossStackResolver: resolver
|
|
20434
|
-
});
|
|
20435
|
-
} else if (!stateProvider && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against deployed state.");
|
|
20436
|
-
sigintHandler = () => {
|
|
20437
|
-
sigintCount += 1;
|
|
20438
|
-
if (sigintCount >= 2) {
|
|
20439
|
-
process.stderr.write("Force-exit on second ^C; container cleanup skipped.\n");
|
|
20440
|
-
process.exit(130);
|
|
20441
|
-
}
|
|
20442
|
-
logger.info("Stopping task...");
|
|
20443
|
-
cleanup().then(() => process.exit(130));
|
|
20444
|
-
};
|
|
20445
|
-
process.on("SIGINT", sigintHandler);
|
|
20446
|
-
let assumedCredentials;
|
|
20447
|
-
let resolvedRoleArn;
|
|
20448
|
-
if (options.assumeTaskRole === true) {
|
|
20449
|
-
if (!task.taskRoleArn) throw new Error(`--assume-task-role passed without an ARN but the task definition has no resolvable TaskRoleArn. Either the task definition does not set TaskRoleArn, or it points at a resource ${getEmbedConfig().binaryName} cannot resolve to an IAM Role at synth time. Pass the ARN explicitly: --assume-task-role <arn>`);
|
|
20450
|
-
resolvedRoleArn = await resolvePlaceholderAccount$1(task.taskRoleArn, options.region, options.profile);
|
|
20451
|
-
assumedCredentials = await assumeTaskRole$1(resolvedRoleArn, options.region, options.profile);
|
|
20452
|
-
} else if (typeof options.assumeTaskRole === "string") {
|
|
20453
|
-
resolvedRoleArn = options.assumeTaskRole;
|
|
20454
|
-
assumedCredentials = await assumeTaskRole$1(resolvedRoleArn, options.region, options.profile);
|
|
20455
|
-
}
|
|
20456
|
-
const sidecarCredentials = await resolveSidecarCredentials(options, assumedCredentials);
|
|
20457
|
-
if (options.profile && sidecarCredentials && !assumedCredentials) profileCredsFile = await writeProfileCredentialsFile(options.profile, sidecarCredentials);
|
|
20458
|
-
const envOverrides = readEnvOverridesFile$1(options.envVars);
|
|
20459
|
-
const runOpts = {
|
|
20460
|
-
cluster: options.cluster,
|
|
20461
|
-
containerHost: options.containerHost,
|
|
20462
|
-
skipPull: options.pull === false,
|
|
20463
|
-
keepRunning: options.keepRunning,
|
|
20464
|
-
detach: options.detach
|
|
20465
|
-
};
|
|
20466
|
-
if (envOverrides) runOpts.envOverrides = envOverrides;
|
|
20467
|
-
if (sidecarCredentials) runOpts.taskCredentials = sidecarCredentials;
|
|
20468
|
-
if (resolvedRoleArn) runOpts.taskRoleArn = resolvedRoleArn;
|
|
20469
|
-
if (options.platform) runOpts.platformOverride = options.platform;
|
|
20470
|
-
if (options.region) runOpts.region = options.region;
|
|
20471
|
-
if (options.ecrRoleArn) runOpts.ecrRoleArn = options.ecrRoleArn;
|
|
20472
|
-
if (options.profile) runOpts.profile = options.profile;
|
|
20473
|
-
const hostPortOverrides = parseHostPortOverrides(options.hostPort);
|
|
20474
|
-
if (Object.keys(hostPortOverrides).length > 0) runOpts.hostPortOverrides = hostPortOverrides;
|
|
20475
|
-
if (profileCredsFile) runOpts.profileCredentialsFile = {
|
|
20476
|
-
hostPath: profileCredsFile.hostPath,
|
|
20477
|
-
containerPath: profileCredsFile.containerPath,
|
|
20478
|
-
profileName: profileCredsFile.profileName
|
|
20479
|
-
};
|
|
20480
|
-
const result = await runEcsTask(task, runOpts, state);
|
|
20481
|
-
if (options.detach) {
|
|
20482
|
-
logger.info(`Task containers started in detached mode; ${getEmbedConfig().binaryName} is exiting.`);
|
|
20483
|
-
logger.info(`Use 'docker ps --filter network=${result.state.network?.networkName ?? "<network>"}' to inspect; tear down with 'docker rm -f' and 'docker network rm'.`);
|
|
20484
|
-
sigintCount = 99;
|
|
20485
|
-
return;
|
|
20486
|
-
}
|
|
20487
|
-
if (result.essentialContainerName) logger.info(`Essential container '${result.essentialContainerName}' exited with code ${result.exitCode}.`);
|
|
20488
|
-
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
20489
|
-
} finally {
|
|
20490
|
-
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
20491
|
-
if (stateProvider) stateProvider.dispose();
|
|
20492
|
-
if (!options.detach) await cleanup();
|
|
20493
|
-
}
|
|
20494
|
-
}
|
|
20495
|
-
/**
|
|
20496
|
-
* If `arn` contains the `${AWS::AccountId}` placeholder emitted by the
|
|
20497
|
-
* resolver for inline same-stack IAM Roles, substitute the live caller
|
|
20498
|
-
* account via STS `GetCallerIdentity`. Otherwise pass through unchanged.
|
|
20499
|
-
*/
|
|
20500
|
-
async function resolvePlaceholderAccount$1(arn, region, profile) {
|
|
20501
|
-
if (!arn.includes("${AWS::AccountId}")) return arn;
|
|
20502
|
-
const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
|
|
20503
|
-
const sts = new STSClient(buildStsClientConfig({
|
|
20504
|
-
region,
|
|
20505
|
-
profile
|
|
20506
|
-
}));
|
|
20507
|
-
try {
|
|
20508
|
-
const account = (await sts.send(new GetCallerIdentityCommand({}))).Account;
|
|
20509
|
-
if (!account) throw new Error(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN '${arn}'. Pass the ARN explicitly: --assume-task-role <arn>`);
|
|
20510
|
-
return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
|
|
20511
|
-
} finally {
|
|
20512
|
-
sts.destroy();
|
|
20387
|
+
//#region src/local/ecs-service-resolver.ts
|
|
20388
|
+
function resolveEcsServiceTarget(target, stacks, context, options) {
|
|
20389
|
+
if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
|
|
20390
|
+
const parsed = parseEcsTarget(target);
|
|
20391
|
+
const stack = pickStack$1(parsed, stacks);
|
|
20392
|
+
const resources = stack.template.Resources ?? {};
|
|
20393
|
+
let serviceLogicalId;
|
|
20394
|
+
let serviceResource;
|
|
20395
|
+
if (parsed.isPath) {
|
|
20396
|
+
const index = buildCdkPathIndex(stack.template);
|
|
20397
|
+
const services = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId: l }) => resources[l]?.Type === "AWS::ECS::Service");
|
|
20398
|
+
if (services.length === 0) throw notFoundError(target, stack, resources, parsed);
|
|
20399
|
+
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.");
|
|
20400
|
+
serviceLogicalId = services[0].logicalId;
|
|
20401
|
+
serviceResource = resources[serviceLogicalId];
|
|
20402
|
+
} else {
|
|
20403
|
+
serviceResource = resources[parsed.pathOrId];
|
|
20404
|
+
if (!serviceResource) throw notFoundError(target, stack, resources, parsed);
|
|
20405
|
+
serviceLogicalId = parsed.pathOrId;
|
|
20513
20406
|
}
|
|
20407
|
+
if (!serviceLogicalId || !serviceResource) throw notFoundError(target, stack, resources, parsed);
|
|
20408
|
+
if (serviceResource.Type === "AWS::ECS::TaskDefinition") throw new EcsTaskResolutionError(`Resource '${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.`);
|
|
20409
|
+
if (serviceResource.Type !== "AWS::ECS::Service") throw new EcsTaskResolutionError(`Resource '${serviceLogicalId}' in ${stack.stackName} is ${serviceResource.Type}, not an AWS::ECS::Service.`);
|
|
20410
|
+
return extractServiceProperties(stack, serviceLogicalId, serviceResource, stacks, context, options);
|
|
20514
20411
|
}
|
|
20515
20412
|
/**
|
|
20516
|
-
*
|
|
20413
|
+
* Pure-functional extraction from the synth resource. Exposed for unit
|
|
20414
|
+
* testing the per-field resolution rules (DesiredCount default, missing
|
|
20415
|
+
* TaskDefinition, intrinsic shapes).
|
|
20517
20416
|
*/
|
|
20518
|
-
|
|
20519
|
-
const
|
|
20520
|
-
const
|
|
20521
|
-
|
|
20522
|
-
|
|
20523
|
-
|
|
20524
|
-
|
|
20525
|
-
|
|
20526
|
-
|
|
20527
|
-
|
|
20528
|
-
|
|
20529
|
-
|
|
20530
|
-
|
|
20531
|
-
|
|
20532
|
-
accessKeyId: creds.AccessKeyId,
|
|
20533
|
-
secretAccessKey: creds.SecretAccessKey,
|
|
20534
|
-
sessionToken: creds.SessionToken
|
|
20535
|
-
};
|
|
20536
|
-
} finally {
|
|
20537
|
-
sts.destroy();
|
|
20417
|
+
function extractServiceProperties(stack, serviceLogicalId, resource, stacks, context, options) {
|
|
20418
|
+
const props = resource.Properties ?? {};
|
|
20419
|
+
const warnings = [];
|
|
20420
|
+
const taskDefRef = props["TaskDefinition"];
|
|
20421
|
+
if (taskDefRef === void 0 || taskDefRef === null) throw new EcsTaskResolutionError(`ECS Service '${serviceLogicalId}' in ${stack.stackName} has no TaskDefinition property.`);
|
|
20422
|
+
const taskDefLogicalId = resolveTaskDefinitionReference(taskDefRef, stack, serviceLogicalId);
|
|
20423
|
+
const task = resolveEcsTaskTarget(`${stack.stackName}:${taskDefLogicalId}`, stacks, context);
|
|
20424
|
+
const desiredCount = parseDesiredCount(props["DesiredCount"], serviceLogicalId);
|
|
20425
|
+
const healthCheckGracePeriodSeconds = parseHealthCheckGrace(props["HealthCheckGracePeriodSeconds"], serviceLogicalId);
|
|
20426
|
+
const serviceName = parseServiceName(props["ServiceName"], serviceLogicalId);
|
|
20427
|
+
const serviceDisplayName = deriveServiceDisplayName(props["ServiceName"], serviceLogicalId, resource.Metadata);
|
|
20428
|
+
if (!options?.suppressLoadBalancerWarning && Array.isArray(props["LoadBalancers"]) && props["LoadBalancers"].length > 0) {
|
|
20429
|
+
const { cliName } = getEmbedConfig();
|
|
20430
|
+
warnings.push(`ECS Service '${serviceLogicalId}' declares LoadBalancers, but \`${cliName} start-service\` runs the replicas only; no local listener fronts them. Reach the containers via their published ports, or run \`${cliName} start-alb <Stack>/<Alb>\` to boot the same replicas behind a local front-door that round-robins the listener rules.`);
|
|
20538
20431
|
}
|
|
20432
|
+
const serviceConnect = extractServiceConnect(props["ServiceConnectConfiguration"], task);
|
|
20433
|
+
const out = {
|
|
20434
|
+
stack,
|
|
20435
|
+
serviceLogicalId,
|
|
20436
|
+
resource,
|
|
20437
|
+
serviceName,
|
|
20438
|
+
serviceDisplayName,
|
|
20439
|
+
desiredCount,
|
|
20440
|
+
healthCheckGracePeriodSeconds,
|
|
20441
|
+
task,
|
|
20442
|
+
serviceRegistries: extractServiceRegistries(props["ServiceRegistries"], serviceLogicalId, warnings),
|
|
20443
|
+
warnings
|
|
20444
|
+
};
|
|
20445
|
+
if (serviceConnect) out.serviceConnect = serviceConnect;
|
|
20446
|
+
return out;
|
|
20539
20447
|
}
|
|
20540
20448
|
/**
|
|
20541
|
-
*
|
|
20542
|
-
* Returns `undefined` when
|
|
20543
|
-
*
|
|
20544
|
-
|
|
20545
|
-
|
|
20546
|
-
|
|
20547
|
-
|
|
20548
|
-
|
|
20549
|
-
|
|
20550
|
-
|
|
20551
|
-
|
|
20552
|
-
|
|
20553
|
-
|
|
20554
|
-
|
|
20555
|
-
|
|
20556
|
-
|
|
20557
|
-
|
|
20558
|
-
} catch (err) {
|
|
20559
|
-
logger.warn(`Resolver needs \${AWS::AccountId} but STS GetCallerIdentity failed: ${err instanceof Error ? err.message : String(err)}. Substitution will be skipped; affected env / secret entries will be dropped with per-key warnings.`);
|
|
20560
|
-
}
|
|
20561
|
-
const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : void 0;
|
|
20562
|
-
ctx.pseudoParameters = {
|
|
20563
|
-
...accountId !== void 0 && { accountId },
|
|
20564
|
-
...region !== void 0 && { region },
|
|
20565
|
-
...partitionAndSuffix && {
|
|
20566
|
-
partition: partitionAndSuffix.partition,
|
|
20567
|
-
urlSuffix: partitionAndSuffix.urlSuffix
|
|
20568
|
-
}
|
|
20569
|
-
};
|
|
20570
|
-
}
|
|
20571
|
-
const wantsState = needs.needsStateResources || needs.needsEnvOrSecretSubstitution;
|
|
20572
|
-
if (stateProvider && wantsState) {
|
|
20573
|
-
const loaded = await stateProvider.load(candidate.stackName, candidate.region);
|
|
20574
|
-
if (loaded) ctx.stateResources = loaded.resources;
|
|
20575
|
-
else {
|
|
20576
|
-
const loadError = stateProvider.getLastLoadError?.();
|
|
20577
|
-
if (loadError) ctx.stateLoadFailureMessage = loadError;
|
|
20578
|
-
}
|
|
20579
|
-
if (needs.needsEnvOrSecretSubstitution && stateProvider.resolveTemplateSsmParameters) {
|
|
20580
|
-
const ssmParameters = await stateProvider.resolveTemplateSsmParameters(candidate.template);
|
|
20581
|
-
if (Object.keys(ssmParameters.values).length > 0) ctx.stateParameters = ssmParameters.values;
|
|
20582
|
-
if (ssmParameters.secureStringLogicalIds.length > 0) ctx.stateSensitiveParameters = ssmParameters.secureStringLogicalIds;
|
|
20583
|
-
}
|
|
20584
|
-
} else if (!stateProvider && needs.needsStateResources) logger.warn("Container Image references a same-stack AWS::ECR::Repository. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute the deployed repository URI. Otherwise the resolver will surface its existing error.");
|
|
20585
|
-
else if (!stateProvider && needs.needsEnvOrSecretSubstitution) logger.warn("Container Environment / Secrets entries contain CloudFormation intrinsics (Ref / Fn::GetAtt / Fn::Sub / Fn::Join). Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against deployed state. Without a state source these entries are dropped (per-key warnings will follow).");
|
|
20586
|
-
return ctx;
|
|
20587
|
-
}
|
|
20588
|
-
function pickCandidateStack$1(stackPattern, stacks) {
|
|
20589
|
-
if (stackPattern === null) {
|
|
20590
|
-
if (stacks.length === 1) return stacks[0];
|
|
20591
|
-
return;
|
|
20592
|
-
}
|
|
20593
|
-
const matched = matchStacks(stacks, [stackPattern]);
|
|
20594
|
-
if (matched.length === 1) return matched[0];
|
|
20595
|
-
}
|
|
20596
|
-
async function resolveCallerAccountId$1(region, profile) {
|
|
20597
|
-
const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
|
|
20598
|
-
const sts = new STSClient(buildStsClientConfig({
|
|
20599
|
-
region,
|
|
20600
|
-
profile
|
|
20601
|
-
}));
|
|
20602
|
-
try {
|
|
20603
|
-
return (await sts.send(new GetCallerIdentityCommand({}))).Account;
|
|
20604
|
-
} finally {
|
|
20605
|
-
sts.destroy();
|
|
20606
|
-
}
|
|
20607
|
-
}
|
|
20608
|
-
/**
|
|
20609
|
-
* Read the `--env-vars` JSON file using the same SAM-style shape as
|
|
20610
|
-
* `cdkl invoke --env-vars`: top-level keys are container names, with
|
|
20611
|
-
* `Parameters` reserved for global entries.
|
|
20612
|
-
*/
|
|
20613
|
-
function readEnvOverridesFile$1(filePath) {
|
|
20614
|
-
if (!filePath) return void 0;
|
|
20615
|
-
let raw;
|
|
20616
|
-
try {
|
|
20617
|
-
raw = readFileSync(filePath, "utf-8");
|
|
20618
|
-
} catch (err) {
|
|
20619
|
-
throw new Error(`Failed to read --env-vars file '${filePath}': ${err instanceof Error ? err.message : String(err)}`);
|
|
20620
|
-
}
|
|
20621
|
-
let parsed;
|
|
20622
|
-
try {
|
|
20623
|
-
parsed = JSON.parse(raw);
|
|
20624
|
-
} catch (err) {
|
|
20625
|
-
throw new Error(`Failed to parse --env-vars file '${filePath}' as JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
20626
|
-
}
|
|
20627
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`--env-vars file '${filePath}' must contain a JSON object at the top level.`);
|
|
20628
|
-
return parsed;
|
|
20629
|
-
}
|
|
20630
|
-
/**
|
|
20631
|
-
* Pick the credentials forwarded to the AWS-published
|
|
20632
|
-
* `amazon-ecs-local-container-endpoints` sidecar. Precedence:
|
|
20633
|
-
* 1. `--assume-task-role <arn>` (or bare `--assume-task-role` against
|
|
20634
|
-
* a resolvable `TaskRoleArn`) → STS-assumed temp creds. Highest
|
|
20635
|
-
* priority — when the user opted in to IAM emulation, those creds
|
|
20636
|
-
* drive the sidecar regardless of `--profile`.
|
|
20637
|
-
* 2. `--profile <p>` → resolved via {@link resolveProfileCredentials}
|
|
20638
|
-
* (the SDK's default credential provider chain — SSO / IAM
|
|
20639
|
-
* Identity Center / fromIni / role-assumption). NEW in this PR.
|
|
20640
|
-
* 3. Neither set → `undefined`; the sidecar runs with its own
|
|
20641
|
-
* default credential chain (typically empty inside a fresh
|
|
20642
|
-
* container — user containers will get 4xx from the credentials
|
|
20643
|
-
* endpoint, mimicking IAM-misconfigured prod).
|
|
20644
|
-
*
|
|
20645
|
-
* Extracted as an exported helper so a unit test can exercise every
|
|
20646
|
-
* branch without having to mock the full Synth + Docker + AWS pipeline
|
|
20647
|
-
* (the strategy used for the Lambda container path).
|
|
20648
|
-
*/
|
|
20649
|
-
async function resolveSidecarCredentials(options, assumedCredentials) {
|
|
20650
|
-
if (assumedCredentials) return assumedCredentials;
|
|
20651
|
-
if (options.profile) return resolveProfileCredentials(options.profile);
|
|
20652
|
-
}
|
|
20653
|
-
function createLocalRunTaskCommand(opts = {}) {
|
|
20654
|
-
setEmbedConfig(opts.embedConfig);
|
|
20655
|
-
const cmd = new Command("run-task").description("Run an AWS::ECS::TaskDefinition locally — pulls/builds images, sets up a per-task docker network with the AWS-published metadata-endpoints sidecar, and starts every container in dependsOn order. Target accepts a CDK display path (MyStack/MyService/TaskDef) or stack-qualified logical ID (MyStack:MyServiceTaskDefXYZ1234). Single-stack apps may omit the stack prefix. Omit <target> in an interactive terminal to pick the task definition from a list.").argument("[target]", "CDK display path or stack-qualified logical ID of the AWS::ECS::TaskDefinition to run (omit to pick interactively in a TTY)").action(withErrorHandling(async (target, options) => {
|
|
20656
|
-
await localRunTaskCommand(target, options, opts.extraStateProviders);
|
|
20657
|
-
}));
|
|
20658
|
-
addRunTaskSpecificOptions(cmd);
|
|
20659
|
-
[
|
|
20660
|
-
...commonOptions(),
|
|
20661
|
-
...appOptions(),
|
|
20662
|
-
...contextOptions
|
|
20663
|
-
].forEach((opt) => cmd.addOption(opt));
|
|
20664
|
-
cmd.addOption(regionOption);
|
|
20665
|
-
return cmd;
|
|
20666
|
-
}
|
|
20667
|
-
/**
|
|
20668
|
-
* Register the option block that `cdkl run-task` adds on top of the shared
|
|
20669
|
-
* common / app / context option helpers. Shared between `cdkl run-task` and
|
|
20670
|
-
* any host CLI (e.g. cdkd's `local run-task`) that wraps the single-task
|
|
20671
|
-
* ECS local runner, so adding or renaming a `run-task`-only flag here
|
|
20672
|
-
* propagates to every embedder without duplicate `.addOption(...)` blocks.
|
|
20673
|
-
*
|
|
20674
|
-
* Calling order only affects `--help` presentation (Commander parses
|
|
20675
|
-
* insertion-order-independent). The host-CLI convention is host-specific
|
|
20676
|
-
* options first, then this helper, then the shared common / app / context
|
|
20677
|
-
* options — host flags / run-task flags / common flags grouped in three
|
|
20678
|
-
* `--help` clusters. Chainable: returns `cmd`.
|
|
20679
|
-
*
|
|
20680
|
-
* NOTE: `run-task` does NOT compose with {@link addCommonEcsServiceOptions}
|
|
20681
|
-
* even though many flags overlap. The two ECS surfaces (single-task vs
|
|
20682
|
-
* multi-replica service) have intentionally divergent defaults
|
|
20683
|
-
* (`run-task` has no `--max-tasks` / `--restart-policy`; `start-service`
|
|
20684
|
-
* / `start-alb` have no `--host-port` / `--keep-running` / `--detach`),
|
|
20685
|
-
* and folding `run-task` into the service common block would mutate the
|
|
20686
|
-
* surface non-trivially. Each command keeps its own helper.
|
|
20687
|
-
*/
|
|
20688
|
-
function addRunTaskSpecificOptions(cmd) {
|
|
20689
|
-
return cmd.addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default(getEmbedConfig().resourceNamePrefix)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--host-port <containerPort=hostPort...>", "Publish a container port on a specific host port (e.g. 80=8080); repeatable. Default: host port == container port. Use this on macOS to map a privileged container port (< 1024) to a non-privileged host port and avoid the Docker Desktop admin-password prompt.")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed function role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--keep-running", "Don't docker rm -f the user containers on task exit (network + sidecar are still torn down). Use when you want to docker exec into a stopped container for post-mortems.").default(false)).addOption(new Option("--detach", "Start the containers in the background and exit (skip log streaming + auto teardown). Useful in CI smoke tests; caller manages container lifecycle.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", `Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (\`cdk deploy\`). Bare form uses the ${getEmbedConfig().binaryName} stack name; pass an explicit value when the CFn stack name differs. Fn::GetAtt in container Environment[].Value is warn-and-dropped: CFn ListStackResources does not return per-attribute values, and unlike Lambda (where \`cdkl invoke --from-cfn-stack\` recovers Fn::GetAtt from the deployed function via lambda:GetFunctionConfiguration), no ECS-side equivalent resolves attributes off a deployed task / service.`)).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
|
|
20690
|
-
}
|
|
20691
|
-
|
|
20692
|
-
//#endregion
|
|
20693
|
-
//#region src/local/ecs-service-resolver.ts
|
|
20694
|
-
function resolveEcsServiceTarget(target, stacks, context, options) {
|
|
20695
|
-
if (stacks.length === 0) throw new EcsTaskResolutionError("No stacks found in the synthesized assembly.");
|
|
20696
|
-
const parsed = parseEcsTarget(target);
|
|
20697
|
-
const stack = pickStack$1(parsed, stacks);
|
|
20698
|
-
const resources = stack.template.Resources ?? {};
|
|
20699
|
-
let serviceLogicalId;
|
|
20700
|
-
let serviceResource;
|
|
20701
|
-
if (parsed.isPath) {
|
|
20702
|
-
const index = buildCdkPathIndex(stack.template);
|
|
20703
|
-
const services = resolveCdkPathToLogicalIds(parsed.pathOrId, index).filter(({ logicalId: l }) => resources[l]?.Type === "AWS::ECS::Service");
|
|
20704
|
-
if (services.length === 0) throw notFoundError(target, stack, resources, parsed);
|
|
20705
|
-
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.");
|
|
20706
|
-
serviceLogicalId = services[0].logicalId;
|
|
20707
|
-
serviceResource = resources[serviceLogicalId];
|
|
20708
|
-
} else {
|
|
20709
|
-
serviceResource = resources[parsed.pathOrId];
|
|
20710
|
-
if (!serviceResource) throw notFoundError(target, stack, resources, parsed);
|
|
20711
|
-
serviceLogicalId = parsed.pathOrId;
|
|
20712
|
-
}
|
|
20713
|
-
if (!serviceLogicalId || !serviceResource) throw notFoundError(target, stack, resources, parsed);
|
|
20714
|
-
if (serviceResource.Type === "AWS::ECS::TaskDefinition") throw new EcsTaskResolutionError(`Resource '${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.`);
|
|
20715
|
-
if (serviceResource.Type !== "AWS::ECS::Service") throw new EcsTaskResolutionError(`Resource '${serviceLogicalId}' in ${stack.stackName} is ${serviceResource.Type}, not an AWS::ECS::Service.`);
|
|
20716
|
-
return extractServiceProperties(stack, serviceLogicalId, serviceResource, stacks, context, options);
|
|
20717
|
-
}
|
|
20718
|
-
/**
|
|
20719
|
-
* Pure-functional extraction from the synth resource. Exposed for unit
|
|
20720
|
-
* testing the per-field resolution rules (DesiredCount default, missing
|
|
20721
|
-
* TaskDefinition, intrinsic shapes).
|
|
20722
|
-
*/
|
|
20723
|
-
function extractServiceProperties(stack, serviceLogicalId, resource, stacks, context, options) {
|
|
20724
|
-
const props = resource.Properties ?? {};
|
|
20725
|
-
const warnings = [];
|
|
20726
|
-
const taskDefRef = props["TaskDefinition"];
|
|
20727
|
-
if (taskDefRef === void 0 || taskDefRef === null) throw new EcsTaskResolutionError(`ECS Service '${serviceLogicalId}' in ${stack.stackName} has no TaskDefinition property.`);
|
|
20728
|
-
const taskDefLogicalId = resolveTaskDefinitionReference(taskDefRef, stack, serviceLogicalId);
|
|
20729
|
-
const task = resolveEcsTaskTarget(`${stack.stackName}:${taskDefLogicalId}`, stacks, context);
|
|
20730
|
-
const desiredCount = parseDesiredCount(props["DesiredCount"], serviceLogicalId);
|
|
20731
|
-
const healthCheckGracePeriodSeconds = parseHealthCheckGrace(props["HealthCheckGracePeriodSeconds"], serviceLogicalId);
|
|
20732
|
-
const serviceName = parseServiceName(props["ServiceName"], serviceLogicalId);
|
|
20733
|
-
const serviceDisplayName = deriveServiceDisplayName(props["ServiceName"], serviceLogicalId, resource.Metadata);
|
|
20734
|
-
if (!options?.suppressLoadBalancerWarning && Array.isArray(props["LoadBalancers"]) && props["LoadBalancers"].length > 0) {
|
|
20735
|
-
const { cliName } = getEmbedConfig();
|
|
20736
|
-
warnings.push(`ECS Service '${serviceLogicalId}' declares LoadBalancers, but \`${cliName} start-service\` runs the replicas only; no local listener fronts them. Reach the containers via their published ports, or run \`${cliName} start-alb <Stack>/<Alb>\` to boot the same replicas behind a local front-door that round-robins the listener rules.`);
|
|
20737
|
-
}
|
|
20738
|
-
const serviceConnect = extractServiceConnect(props["ServiceConnectConfiguration"], task);
|
|
20739
|
-
const out = {
|
|
20740
|
-
stack,
|
|
20741
|
-
serviceLogicalId,
|
|
20742
|
-
resource,
|
|
20743
|
-
serviceName,
|
|
20744
|
-
serviceDisplayName,
|
|
20745
|
-
desiredCount,
|
|
20746
|
-
healthCheckGracePeriodSeconds,
|
|
20747
|
-
task,
|
|
20748
|
-
serviceRegistries: extractServiceRegistries(props["ServiceRegistries"], serviceLogicalId, warnings),
|
|
20749
|
-
warnings
|
|
20750
|
-
};
|
|
20751
|
-
if (serviceConnect) out.serviceConnect = serviceConnect;
|
|
20752
|
-
return out;
|
|
20753
|
-
}
|
|
20754
|
-
/**
|
|
20755
|
-
* Parse `ServiceConnectConfiguration` against the producer TaskDef.
|
|
20756
|
-
* Returns `undefined` when the block is missing OR `Enabled: false`.
|
|
20757
|
-
*
|
|
20758
|
-
* Reject conditions (surface as resolver-time errors so the user sees
|
|
20759
|
-
* them BEFORE the docker network is created):
|
|
20760
|
-
* - `Namespace` is not a literal string. CDK 2.x always emits a
|
|
20761
|
-
* literal string here (verified 2026-05-22); cross-stack /
|
|
20762
|
-
* intrinsic shapes are out of scope.
|
|
20763
|
-
* - `Services[].PortName` doesn't match any of the TaskDef's
|
|
20764
|
-
* `ContainerDefinitions[].PortMappings[].Name` entries.
|
|
20765
|
-
*
|
|
20766
|
-
* Note on `clientAliases[]` shape: each ClientAlias can declare a
|
|
20767
|
-
* `DnsName` (the bare short-name peers connect to, e.g. `orders`) AND
|
|
20768
|
-
* a `Port` (the listening port the alias maps to inside the consumer).
|
|
20769
|
-
* cdk-local surfaces both verbatim; the registry / `--add-host` overlay
|
|
20770
|
-
* publishes each `DnsName` as a bare alias pointing at the same IP as
|
|
20771
|
-
* the canonical fqdn.
|
|
20449
|
+
* Parse `ServiceConnectConfiguration` against the producer TaskDef.
|
|
20450
|
+
* Returns `undefined` when the block is missing OR `Enabled: false`.
|
|
20451
|
+
*
|
|
20452
|
+
* Reject conditions (surface as resolver-time errors so the user sees
|
|
20453
|
+
* them BEFORE the docker network is created):
|
|
20454
|
+
* - `Namespace` is not a literal string. CDK 2.x always emits a
|
|
20455
|
+
* literal string here (verified 2026-05-22); cross-stack /
|
|
20456
|
+
* intrinsic shapes are out of scope.
|
|
20457
|
+
* - `Services[].PortName` doesn't match any of the TaskDef's
|
|
20458
|
+
* `ContainerDefinitions[].PortMappings[].Name` entries.
|
|
20459
|
+
*
|
|
20460
|
+
* Note on `clientAliases[]` shape: each ClientAlias can declare a
|
|
20461
|
+
* `DnsName` (the bare short-name peers connect to, e.g. `orders`) AND
|
|
20462
|
+
* a `Port` (the listening port the alias maps to inside the consumer).
|
|
20463
|
+
* cdk-local surfaces both verbatim; the registry / `--add-host` overlay
|
|
20464
|
+
* publishes each `DnsName` as a bare alias pointing at the same IP as
|
|
20465
|
+
* the canonical fqdn.
|
|
20772
20466
|
*/
|
|
20773
20467
|
function extractServiceConnect(raw, task) {
|
|
20774
20468
|
if (!raw || typeof raw !== "object") return void 0;
|
|
@@ -21846,7 +21540,7 @@ async function softReloadReplica(args) {
|
|
|
21846
21540
|
const defaultDockerInspectWorkdirImpl = async (containerId) => {
|
|
21847
21541
|
const { execFile } = await import("node:child_process");
|
|
21848
21542
|
const { promisify } = await import("node:util");
|
|
21849
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
21543
|
+
const { getDockerCmd } = await import("./docker-cmd-Dqx2YENO.js").then((n) => n.t);
|
|
21850
21544
|
const { stdout } = await promisify(execFile)(getDockerCmd(), [
|
|
21851
21545
|
"inspect",
|
|
21852
21546
|
"--format",
|
|
@@ -21865,7 +21559,7 @@ let dockerInspectWorkdirImpl = defaultDockerInspectWorkdirImpl;
|
|
|
21865
21559
|
const defaultDockerCpImpl = async (src, dst) => {
|
|
21866
21560
|
const { execFile } = await import("node:child_process");
|
|
21867
21561
|
const { promisify } = await import("node:util");
|
|
21868
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
21562
|
+
const { getDockerCmd } = await import("./docker-cmd-Dqx2YENO.js").then((n) => n.t);
|
|
21869
21563
|
await promisify(execFile)(getDockerCmd(), [
|
|
21870
21564
|
"cp",
|
|
21871
21565
|
src,
|
|
@@ -21880,7 +21574,7 @@ let dockerCpImpl = defaultDockerCpImpl;
|
|
|
21880
21574
|
const defaultDockerRestartImpl = async (containerId) => {
|
|
21881
21575
|
const { execFile } = await import("node:child_process");
|
|
21882
21576
|
const { promisify } = await import("node:util");
|
|
21883
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
21577
|
+
const { getDockerCmd } = await import("./docker-cmd-Dqx2YENO.js").then((n) => n.t);
|
|
21884
21578
|
await promisify(execFile)(getDockerCmd(), ["restart", containerId]);
|
|
21885
21579
|
};
|
|
21886
21580
|
let dockerRestartImpl = defaultDockerRestartImpl;
|
|
@@ -21920,7 +21614,7 @@ async function disconnectOldFromSharedNetwork(oldInstance) {
|
|
|
21920
21614
|
const defaultDockerNetworkDisconnectImpl = async (networkName, containerId) => {
|
|
21921
21615
|
const { execFile } = await import("node:child_process");
|
|
21922
21616
|
const { promisify } = await import("node:util");
|
|
21923
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
21617
|
+
const { getDockerCmd } = await import("./docker-cmd-Dqx2YENO.js").then((n) => n.t);
|
|
21924
21618
|
await promisify(execFile)(getDockerCmd(), [
|
|
21925
21619
|
"network",
|
|
21926
21620
|
"disconnect",
|
|
@@ -22097,7 +21791,7 @@ function pickEssentialContainerId(instance, service) {
|
|
|
22097
21791
|
const defaultWaitForExitImpl = async (containerId) => {
|
|
22098
21792
|
const { execFile } = await import("node:child_process");
|
|
22099
21793
|
const { promisify } = await import("node:util");
|
|
22100
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
21794
|
+
const { getDockerCmd } = await import("./docker-cmd-Dqx2YENO.js").then((n) => n.t);
|
|
22101
21795
|
const { stdout } = await promisify(execFile)(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
|
|
22102
21796
|
const code = parseInt(stdout.trim(), 10);
|
|
22103
21797
|
return Number.isFinite(code) ? code : -1;
|
|
@@ -22117,7 +21811,7 @@ const EXIT_LOG_TAIL_LINES = 50;
|
|
|
22117
21811
|
const defaultReadContainerLogsImpl = async (containerId) => {
|
|
22118
21812
|
const { execFile } = await import("node:child_process");
|
|
22119
21813
|
const { promisify } = await import("node:util");
|
|
22120
|
-
const { getDockerCmd } = await import("./docker-cmd-
|
|
21814
|
+
const { getDockerCmd } = await import("./docker-cmd-Dqx2YENO.js").then((n) => n.t);
|
|
22121
21815
|
const { stdout, stderr } = await promisify(execFile)(getDockerCmd(), [
|
|
22122
21816
|
"logs",
|
|
22123
21817
|
"--tail",
|
|
@@ -24965,7 +24659,8 @@ async function runImageOverrideBuilds(overrides) {
|
|
|
24965
24659
|
try {
|
|
24966
24660
|
await runDockerStreaming(args, {
|
|
24967
24661
|
cwd: entry.contextDir,
|
|
24968
|
-
env: { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" }
|
|
24662
|
+
env: { BUILDX_NO_DEFAULT_ATTESTATIONS: "1" },
|
|
24663
|
+
progressLabel: `Building override image for '${target}'`
|
|
24969
24664
|
});
|
|
24970
24665
|
} catch (err) {
|
|
24971
24666
|
const e = err;
|
|
@@ -25401,7 +25096,7 @@ async function reloadAllServices(args) {
|
|
|
25401
25096
|
async function loadAssetContextForTarget(args) {
|
|
25402
25097
|
const { target, controller, stacks, cdkOutDir, assetLoader, logger } = args;
|
|
25403
25098
|
const parsed = parseEcsTarget(target);
|
|
25404
|
-
const candidate = pickCandidateStack(parsed.stackPattern, stacks);
|
|
25099
|
+
const candidate = pickCandidateStack$1(parsed.stackPattern, stacks);
|
|
25405
25100
|
if (!candidate) {
|
|
25406
25101
|
logger.debug(`loadAssetContext: stack pattern '${parsed.stackPattern}' from target '${target}' not in the assembly. Classifier will see no asset context (rebuild).`);
|
|
25407
25102
|
return;
|
|
@@ -25465,7 +25160,7 @@ async function loadAssetContextForTarget(args) {
|
|
|
25465
25160
|
*/
|
|
25466
25161
|
async function rollOneTarget(args) {
|
|
25467
25162
|
const { controller, newBoot, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, verdict, imageOverrideTag, logger } = args;
|
|
25468
|
-
const candidate = pickCandidateStack(parseEcsTarget(newBoot.target).stackPattern, stacks);
|
|
25163
|
+
const candidate = pickCandidateStack$1(parseEcsTarget(newBoot.target).stackPattern, stacks);
|
|
25469
25164
|
const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
|
|
25470
25165
|
try {
|
|
25471
25166
|
let resolved;
|
|
@@ -25517,7 +25212,7 @@ async function rollOneTarget(args) {
|
|
|
25517
25212
|
}
|
|
25518
25213
|
}
|
|
25519
25214
|
async function bootOneTarget(boot, runState, stacks, options, discovery, skipPull, extraStateProviders, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, imageOverrideTag) {
|
|
25520
|
-
const candidate = pickCandidateStack(parseEcsTarget(boot.target).stackPattern, stacks);
|
|
25215
|
+
const candidate = pickCandidateStack$1(parseEcsTarget(boot.target).stackPattern, stacks);
|
|
25521
25216
|
const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
|
|
25522
25217
|
try {
|
|
25523
25218
|
return await runOneTarget(boot, runState, stacks, options, discovery, skipPull, stateProvider, profileCredsFile, frontDoorPools, suppressLoadBalancerWarning, imageOverrideTag);
|
|
@@ -25556,7 +25251,7 @@ async function resolveServiceAndRunnerOpts(boot, stacks, options, discovery, ski
|
|
|
25556
25251
|
const logger = getLogger();
|
|
25557
25252
|
const target = boot.target;
|
|
25558
25253
|
const quiet = opts.quiet === true;
|
|
25559
|
-
const imageContext = await buildEcsImageResolutionContext(target, stacks, options, stateProvider);
|
|
25254
|
+
const imageContext = await buildEcsImageResolutionContext$1(target, stacks, options, stateProvider);
|
|
25560
25255
|
const service = resolveEcsServiceTarget(target, stacks, imageContext, { suppressLoadBalancerWarning });
|
|
25561
25256
|
if (!quiet) logger.info(`Target: ${service.stack.stackName}/${service.serviceLogicalId} (service=${service.serviceName}, desiredCount=${service.desiredCount}, task=${service.task.taskDefinitionLogicalId})`);
|
|
25562
25257
|
if (!quiet && service.serviceConnect) logger.info(`Service Connect: namespace='${service.serviceConnect.namespaceName}', ${service.serviceConnect.services.length} service(s) registered for peer discovery.`);
|
|
@@ -25577,21 +25272,23 @@ async function resolveServiceAndRunnerOpts(boot, stacks, options, discovery, ski
|
|
|
25577
25272
|
await applyCrossStackResolverToTask(service.task, subContext);
|
|
25578
25273
|
}
|
|
25579
25274
|
} else if (!stateProvider && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against deployed state.");
|
|
25275
|
+
const effectiveAssumeRole = resolveEcsAssumeRoleOption(options);
|
|
25580
25276
|
let assumedCredentials;
|
|
25581
25277
|
let resolvedRoleArn;
|
|
25582
|
-
if (
|
|
25583
|
-
if (!service.task.taskRoleArn) throw new LocalStartServiceError(`--assume-
|
|
25584
|
-
resolvedRoleArn = await resolvePlaceholderAccount(service.task.taskRoleArn, options.region, options.profile);
|
|
25585
|
-
assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region, options.profile);
|
|
25586
|
-
} else if (typeof
|
|
25587
|
-
resolvedRoleArn =
|
|
25588
|
-
assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region, options.profile);
|
|
25589
|
-
}
|
|
25590
|
-
const envOverrides = readEnvOverridesFile(options.envVars);
|
|
25278
|
+
if (effectiveAssumeRole === true) {
|
|
25279
|
+
if (!service.task.taskRoleArn) throw new LocalStartServiceError(`--assume-role passed without an ARN but service '${service.serviceLogicalId}' has no resolvable TaskRoleArn. Pass the ARN explicitly: --assume-role <arn>`);
|
|
25280
|
+
resolvedRoleArn = await resolvePlaceholderAccount$1(service.task.taskRoleArn, options.region, options.profile);
|
|
25281
|
+
assumedCredentials = await assumeTaskRole$1(resolvedRoleArn, options.region, options.profile);
|
|
25282
|
+
} else if (typeof effectiveAssumeRole === "string") {
|
|
25283
|
+
resolvedRoleArn = effectiveAssumeRole;
|
|
25284
|
+
assumedCredentials = await assumeTaskRole$1(resolvedRoleArn, options.region, options.profile);
|
|
25285
|
+
}
|
|
25286
|
+
const envOverrides = readEnvOverridesFile$1(options.envVars);
|
|
25591
25287
|
const taskOpts = {
|
|
25592
25288
|
cluster: options.cluster,
|
|
25593
25289
|
containerHost: options.containerHost,
|
|
25594
25290
|
skipPull,
|
|
25291
|
+
skipBuild: options.build === false,
|
|
25595
25292
|
keepRunning: false,
|
|
25596
25293
|
detach: true
|
|
25597
25294
|
};
|
|
@@ -25882,7 +25579,7 @@ function describeRuleActionForSummary(action) {
|
|
|
25882
25579
|
function describeForwardTargetForSummary(t) {
|
|
25883
25580
|
return t.kind === "lambda" ? `<Lambda: ${t.lambda.logicalId}>` : `<ECS: ${t.serviceTarget}>`;
|
|
25884
25581
|
}
|
|
25885
|
-
async function resolvePlaceholderAccount(arn, region, profile) {
|
|
25582
|
+
async function resolvePlaceholderAccount$1(arn, region, profile) {
|
|
25886
25583
|
if (!arn.includes("${AWS::AccountId}")) return arn;
|
|
25887
25584
|
const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
|
|
25888
25585
|
const sts = new STSClient(buildStsClientConfig({
|
|
@@ -25897,7 +25594,7 @@ async function resolvePlaceholderAccount(arn, region, profile) {
|
|
|
25897
25594
|
sts.destroy();
|
|
25898
25595
|
}
|
|
25899
25596
|
}
|
|
25900
|
-
async function assumeTaskRole(roleArn, region, profile) {
|
|
25597
|
+
async function assumeTaskRole$1(roleArn, region, profile) {
|
|
25901
25598
|
const { STSClient, AssumeRoleCommand } = await import("@aws-sdk/client-sts");
|
|
25902
25599
|
const sts = new STSClient(buildStsClientConfig({
|
|
25903
25600
|
region,
|
|
@@ -25924,9 +25621,9 @@ async function assumeTaskRole(roleArn, region, profile) {
|
|
|
25924
25621
|
* site-level binding test that locks the `--from-cfn-stack` SSM-parameter
|
|
25925
25622
|
* resolution call (issue #94).
|
|
25926
25623
|
*/
|
|
25927
|
-
async function buildEcsImageResolutionContext(target, stacks, options, stateProvider) {
|
|
25624
|
+
async function buildEcsImageResolutionContext$1(target, stacks, options, stateProvider) {
|
|
25928
25625
|
const logger = getLogger();
|
|
25929
|
-
const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
|
|
25626
|
+
const candidate = pickCandidateStack$1(parseEcsTarget(target).stackPattern, stacks);
|
|
25930
25627
|
if (!candidate) return void 0;
|
|
25931
25628
|
const needs = detectEcsImageResolutionNeeds(candidate);
|
|
25932
25629
|
if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution) return;
|
|
@@ -25937,7 +25634,7 @@ async function buildEcsImageResolutionContext(target, stacks, options, stateProv
|
|
|
25937
25634
|
if (!region) logger.warn(`Resolver references \${AWS::Region} but ${getEmbedConfig().binaryName} could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack.`);
|
|
25938
25635
|
let accountId;
|
|
25939
25636
|
try {
|
|
25940
|
-
accountId = await resolveCallerAccountId(region, options.profile);
|
|
25637
|
+
accountId = await resolveCallerAccountId$1(region, options.profile);
|
|
25941
25638
|
} catch (err) {
|
|
25942
25639
|
logger.warn(`Resolver needs \${AWS::AccountId} but STS GetCallerIdentity failed: ${err instanceof Error ? err.message : String(err)}. Substitution will be skipped; affected env / secret entries will be dropped with per-key warnings.`);
|
|
25943
25640
|
}
|
|
@@ -25968,7 +25665,7 @@ async function buildEcsImageResolutionContext(target, stacks, options, stateProv
|
|
|
25968
25665
|
else if (!stateProvider && needs.needsEnvOrSecretSubstitution) logger.warn("Container Environment / Secrets entries contain CloudFormation intrinsics. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against the deployed state.");
|
|
25969
25666
|
return ctx;
|
|
25970
25667
|
}
|
|
25971
|
-
function pickCandidateStack(stackPattern, stacks) {
|
|
25668
|
+
function pickCandidateStack$1(stackPattern, stacks) {
|
|
25972
25669
|
if (stackPattern === null) {
|
|
25973
25670
|
if (stacks.length === 1) return stacks[0];
|
|
25974
25671
|
return;
|
|
@@ -25976,7 +25673,7 @@ function pickCandidateStack(stackPattern, stacks) {
|
|
|
25976
25673
|
const matched = matchStacks(stacks, [stackPattern]);
|
|
25977
25674
|
if (matched.length === 1) return matched[0];
|
|
25978
25675
|
}
|
|
25979
|
-
async function resolveCallerAccountId(region, profile) {
|
|
25676
|
+
async function resolveCallerAccountId$1(region, profile) {
|
|
25980
25677
|
const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
|
|
25981
25678
|
const sts = new STSClient(buildStsClientConfig({
|
|
25982
25679
|
region,
|
|
@@ -25988,7 +25685,7 @@ async function resolveCallerAccountId(region, profile) {
|
|
|
25988
25685
|
sts.destroy();
|
|
25989
25686
|
}
|
|
25990
25687
|
}
|
|
25991
|
-
function readEnvOverridesFile(filePath) {
|
|
25688
|
+
function readEnvOverridesFile$1(filePath) {
|
|
25992
25689
|
if (!filePath) return void 0;
|
|
25993
25690
|
let raw;
|
|
25994
25691
|
try {
|
|
@@ -26133,12 +25830,12 @@ async function resolveAndBuildImageOverrides(args) {
|
|
|
26133
25830
|
const { perTarget, stacks, options, extraStateProviders, logger } = args;
|
|
26134
25831
|
const resolvedForPeek = [];
|
|
26135
25832
|
for (const target of perTarget) {
|
|
26136
|
-
const candidate = pickCandidateStack(parseEcsTarget(target).stackPattern, stacks);
|
|
25833
|
+
const candidate = pickCandidateStack$1(parseEcsTarget(target).stackPattern, stacks);
|
|
26137
25834
|
const stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
|
|
26138
25835
|
let imageContext;
|
|
26139
25836
|
let service;
|
|
26140
25837
|
try {
|
|
26141
|
-
imageContext = await buildEcsImageResolutionContext(target, stacks, options, stateProvider);
|
|
25838
|
+
imageContext = await buildEcsImageResolutionContext$1(target, stacks, options, stateProvider);
|
|
26142
25839
|
service = resolveEcsServiceTarget(target, stacks, imageContext, { suppressLoadBalancerWarning: true });
|
|
26143
25840
|
} catch (err) {
|
|
26144
25841
|
logger.debug(`--image-override peek failed for '${target}': ${err instanceof Error ? err.message : String(err)}.`);
|
|
@@ -26197,13 +25894,74 @@ function enforceStrictOverrides(strict, uncoveredPinnedTargets) {
|
|
|
26197
25894
|
throw new LocalStartServiceError(`--strict-overrides set, but ${uncoveredPinnedTargets.length} pinned target(s) remain uncovered: ${uncoveredPinnedTargets.join(", ")}. Pass --image-override <service>=<dockerfile> for each, drop --strict-overrides, or drop --from-cfn-stack to iterate on local CDK assets.`);
|
|
26198
25895
|
}
|
|
26199
25896
|
/**
|
|
25897
|
+
* Build the canonical `--cluster <name>` Option for every ECS surface.
|
|
25898
|
+
*
|
|
25899
|
+
* Shared between `cdkl run-task` and the `start-service` / `start-alb`
|
|
25900
|
+
* pair so the default (the active embed config's `resourceNamePrefix`)
|
|
25901
|
+
* is defined exactly once — issue #249 / C12. Building it as a fresh
|
|
25902
|
+
* Option per call (not a module-level const) is load-bearing: the
|
|
25903
|
+
* embed config is host-installed at command-factory time, and a
|
|
25904
|
+
* module-level Option would freeze the host-default prefix at
|
|
25905
|
+
* import time.
|
|
25906
|
+
*/
|
|
25907
|
+
function ecsClusterOption() {
|
|
25908
|
+
return new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default(getEmbedConfig().resourceNamePrefix);
|
|
25909
|
+
}
|
|
25910
|
+
/**
|
|
25911
|
+
* Add the `--assume-task-role` flag plus its `--assume-role` alias to
|
|
25912
|
+
* an ECS command. Issue #249 / C6 — every non-ECS command calls the
|
|
25913
|
+
* same concept `--assume-role`, so the ECS surface accepts the
|
|
25914
|
+
* cross-command name as a NON-BREAKING alias. Precedence when both
|
|
25915
|
+
* are passed: `--assume-role` (new) wins. {@link resolveEcsAssumeRoleOption}
|
|
25916
|
+
* computes the effective value and fires a one-time deprecation warn
|
|
25917
|
+
* when only the legacy `--assume-task-role` is set.
|
|
25918
|
+
*
|
|
25919
|
+
* Breaking unification of all three parsers (`invoke`, `start-api`,
|
|
25920
|
+
* ECS) is tracked separately under issue #256.
|
|
25921
|
+
*/
|
|
25922
|
+
function addEcsAssumeRoleOptions(cmd) {
|
|
25923
|
+
return cmd.addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed task role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override. DEPRECATED alias of `--assume-role`; both forms work, but `--assume-role` is the cross-command name and will become the only form in a future release.")).addOption(new Option("--assume-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed task role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override. Cross-command alias matching the same flag on `invoke` / `invoke-agentcore` / `start-api`; supersedes the older `--assume-task-role` (which still works)."));
|
|
25924
|
+
}
|
|
25925
|
+
/**
|
|
25926
|
+
* Module-level latch for the `--assume-task-role` deprecation warn.
|
|
25927
|
+
* Per-process single-fire, so a `--watch` reload that re-enters the
|
|
25928
|
+
* resolver on every source-change firing does not spam the user.
|
|
25929
|
+
*
|
|
25930
|
+
* Test-only `__resetAssumeTaskRoleDeprecationLatch` lets the unit suite
|
|
25931
|
+
* drive the latch through repeated invocations.
|
|
25932
|
+
*/
|
|
25933
|
+
let assumeTaskRoleDeprecationWarned = false;
|
|
25934
|
+
/**
|
|
25935
|
+
* Collapse `--assume-task-role` (legacy) and `--assume-role` (new) into
|
|
25936
|
+
* a single effective value. When BOTH are set, `--assume-role` wins
|
|
25937
|
+
* (matches Commander's "later wins" default for repeatable flags, and
|
|
25938
|
+
* gives the cross-command name priority). When ONLY `--assume-task-role`
|
|
25939
|
+
* is set, fires a **per-process one-time** deprecation warn naming the
|
|
25940
|
+
* replacement — the latch above prevents `--watch` reloads from
|
|
25941
|
+
* re-emitting the warn on every source-change firing.
|
|
25942
|
+
*
|
|
25943
|
+
* Issue #249 / C6.
|
|
25944
|
+
*/
|
|
25945
|
+
function resolveEcsAssumeRoleOption(options) {
|
|
25946
|
+
if (options.assumeRole !== void 0) return options.assumeRole;
|
|
25947
|
+
if (options.assumeTaskRole !== void 0) {
|
|
25948
|
+
if (!assumeTaskRoleDeprecationWarned) {
|
|
25949
|
+
assumeTaskRoleDeprecationWarned = true;
|
|
25950
|
+
getLogger().warn("--assume-task-role is deprecated; use --assume-role instead. Both forms continue to work, but --assume-role is the cross-command name.");
|
|
25951
|
+
}
|
|
25952
|
+
return options.assumeTaskRole;
|
|
25953
|
+
}
|
|
25954
|
+
}
|
|
25955
|
+
/**
|
|
26200
25956
|
* Add the CLI options shared by both ECS-service commands (`start-service` and
|
|
26201
25957
|
* `start-alb`) to a command. The command-specific argument / description and
|
|
26202
25958
|
* the one unique option (`--host-port` vs `--lb-port`) are added by each
|
|
26203
25959
|
* factory.
|
|
26204
25960
|
*/
|
|
26205
25961
|
function addCommonEcsServiceOptions(cmd) {
|
|
26206
|
-
cmd.addOption(
|
|
25962
|
+
cmd.addOption(ecsClusterOption()).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1"));
|
|
25963
|
+
addEcsAssumeRoleOptions(cmd);
|
|
25964
|
+
cmd.addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--no-build", "Skip docker build on the local CDK-asset path for every container (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ECR-pull / public-registry images. Per-container --image-override mappings take precedence (the override tag is used as-is, --no-build does not apply). Compatible with --no-pull.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--max-tasks <n>", `Hard cap on local replica count. Caps the template DesiredCount so local dev machines don't run an unbounded number of containers. Cannot exceed ${83} due to the per-replica link-local /24 subnet allocator's range.`).default(3).argParser(parseMaxTasks)).addOption(new Option("--restart-policy <policy>", "How to react when an essential container exits. 'on-failure' (default) restarts only on non-zero exit; 'always' restarts on every exit; 'none' shuts the replica down and runs the service degraded.").default("on-failure").argParser(parseRestartPolicy)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", `Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (\`cdk deploy\`). Bare form uses the ${getEmbedConfig().binaryName} stack name; pass an explicit value when the CFn stack name differs. Fn::GetAtt in container Environment[].Value is warn-and-dropped: CFn ListStackResources does not return per-attribute values, and unlike Lambda (where \`cdkl invoke --from-cfn-stack\` recovers Fn::GetAtt from the deployed function via lambda:GetFunctionConfiguration), no ECS-side equivalent resolves attributes off a deployed task / service.`)).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).addOption(new Option("--no-logs", "Disable foreground streaming of each replica container stdout/stderr. By default every booted replica streams its docker logs to the host terminal with a [svc=<service> r=<replica-index> c=<container>] prefix (parity with `run-task`). Pass --no-logs for multi-replica / multi-service runs whose interleaved log volume is unreadable; `docker logs -f <id>` in a separate terminal stays available.")).addOption(new Option("--shadow-ready-timeout <ms>", `Issue #265 — milliseconds the \`--watch\` rolling primitive waits for a shadow replica's first essential-container port to accept a TCP connection before swapping Cloud Map + front-door registrations off the old replica. Defaults to 60000 (60s) which covers realistic prod-shaped Node app cold-starts (TS->JS compile, full node_modules graph, framework boot, DB pool init). Bump higher when the reload log surfaces "TCP probe <ip>:<port> did not accept within <N>ms" — typical for Java / heavy ORM init / \`--inspect-brk\` attach pauses. Honored by --watch on \`${getEmbedConfig().binaryName} start-service\` and \`${getEmbedConfig().binaryName} start-alb\`. Env var: \`${getEmbedConfig().envPrefix}_SHADOW_READY_TIMEOUT_MS\` (flag wins over env).`).argParser(parseShadowReadyTimeout));
|
|
26207
25965
|
[
|
|
26208
25966
|
...commonOptions(),
|
|
26209
25967
|
...appOptions(),
|
|
@@ -26232,6 +25990,345 @@ function addImageOverrideOptions(cmd) {
|
|
|
26232
25990
|
return cmd.addOption(new Option("--image-override <service=dockerfile or dockerfile...>", "Replace a service target's deployed-registry image with a local `docker build` of the supplied Dockerfile (repeatable). Two forms: <service>=<dockerfile> binds the service explicitly; a bare <dockerfile> opens a multi-select picker against the still-uncovered pinned targets (one Dockerfile across N services). Mix freely. Lets `--from-cfn-stack` still reach real AWS state while you iterate locally on the application container.")).addOption(new Option("--image-build-arg <KEY=VAL...>", "Global `docker build --build-arg KEY=VAL` pair applied to every --image-override build (repeatable). Lets a CDK Dockerfile that branches on an ARG (env target, base image tag, package mirror) honor that ARG locally without editing the Dockerfile.")).addOption(new Option("--image-build-secret <id=src...>", "Global `docker build --secret id=<id>,src=<src>` entry applied to every --image-override build (repeatable). Enables `RUN --mount=type=secret,id=<id>` in the Dockerfile — the standard private-registry / npm-token recipe (e.g. `--image-build-secret npmrc=./.npmrc`).")).addOption(new Option("--image-target <stage or svc=stage...>", "Repeatable. Bare `<stage>` is a global --target for every --image-override build; `<service>=<stage>` (issue #240) scopes the --target to one overridden target so a monorepo with different multi-stage Dockerfiles per service can stop each at its own intermediate stage. Per-service form overrides the global on the named target.")).addOption(new Option("--no-interactive-overrides", "Suppress the interactive boot prompt that walks each pinned target asking for a Dockerfile path, and the multi-select prompt fired by `--image-override <dockerfile>` (picker form). Useful for CI / scripted invocations.")).addOption(new Option("--strict-overrides", "Fail fast at boot when any pinned target remains uncovered after `--image-override` + the boot prompt resolve. Off by default; the boot WARN per uncovered pinned target still fires regardless.").default(false));
|
|
26233
25991
|
}
|
|
26234
25992
|
|
|
25993
|
+
//#endregion
|
|
25994
|
+
//#region src/cli/commands/local-run-task.ts
|
|
25995
|
+
/**
|
|
25996
|
+
* `cdkl run-task <target>` — Phase 1 of the ECS local-execution
|
|
25997
|
+
* trilogy. Synthesizes the CDK app, locates the target
|
|
25998
|
+
* `AWS::ECS::TaskDefinition`, stands up a per-task docker network with
|
|
25999
|
+
* the AWS-published `amazon-ecs-local-container-endpoints` sidecar, and
|
|
26000
|
+
* starts every container in `dependsOn` order. The essential
|
|
26001
|
+
* container's exit code drives the CLI's exit.
|
|
26002
|
+
*/
|
|
26003
|
+
async function localRunTaskCommand(target, options, extraStateProviders) {
|
|
26004
|
+
const logger = getLogger();
|
|
26005
|
+
if (options.verbose) logger.setLevel("debug");
|
|
26006
|
+
const state = createEcsRunState();
|
|
26007
|
+
let sigintHandler;
|
|
26008
|
+
let sigintCount = 0;
|
|
26009
|
+
let stateProvider;
|
|
26010
|
+
let profileCredsFile;
|
|
26011
|
+
let cleanupPromise;
|
|
26012
|
+
const cleanup = async () => {
|
|
26013
|
+
if (!cleanupPromise) cleanupPromise = (async () => {
|
|
26014
|
+
try {
|
|
26015
|
+
await cleanupEcsRun(state, { keepRunning: options.keepRunning });
|
|
26016
|
+
} catch (err) {
|
|
26017
|
+
getLogger().debug(`cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
26018
|
+
}
|
|
26019
|
+
if (profileCredsFile) try {
|
|
26020
|
+
await profileCredsFile.dispose();
|
|
26021
|
+
} catch (err) {
|
|
26022
|
+
getLogger().debug(`Failed to remove profile credentials tmpdir ${profileCredsFile.hostPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
26023
|
+
}
|
|
26024
|
+
})();
|
|
26025
|
+
await cleanupPromise;
|
|
26026
|
+
};
|
|
26027
|
+
try {
|
|
26028
|
+
await applyRoleArnIfSet({
|
|
26029
|
+
roleArn: options.roleArn,
|
|
26030
|
+
region: options.region,
|
|
26031
|
+
profile: options.profile
|
|
26032
|
+
});
|
|
26033
|
+
await ensureDockerAvailable();
|
|
26034
|
+
const appCmd = resolveApp(options.app);
|
|
26035
|
+
if (!appCmd) throw new Error(`No CDK app specified. Pass --app, set ${getEmbedConfig().envPrefix}_APP, or add "app" to cdk.json.`);
|
|
26036
|
+
logger.info("Synthesizing CDK app...");
|
|
26037
|
+
const synthesizer = new Synthesizer();
|
|
26038
|
+
const context = parseContextOptions(options.context);
|
|
26039
|
+
const synthOpts = {
|
|
26040
|
+
app: appCmd,
|
|
26041
|
+
output: options.output,
|
|
26042
|
+
...options.region && { region: options.region },
|
|
26043
|
+
...options.profile && { profile: options.profile },
|
|
26044
|
+
...Object.keys(context).length > 0 && { context }
|
|
26045
|
+
};
|
|
26046
|
+
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
26047
|
+
const resolvedTarget = await resolveSingleTarget(target, {
|
|
26048
|
+
entries: listTargets(stacks).ecsTaskDefinitions,
|
|
26049
|
+
message: "Select an ECS task definition to run",
|
|
26050
|
+
noun: "ECS task definitions",
|
|
26051
|
+
onMissing: () => new CdkLocalError(`${getEmbedConfig().cliName} run-task requires a <target> (an ECS task definition display path or logical ID). Run \`${getEmbedConfig().cliName} list\` to see them, or run it in a TTY to pick interactively.`, "LOCAL_RUN_TASK_TARGET_REQUIRED")
|
|
26052
|
+
});
|
|
26053
|
+
const candidate = pickCandidateStack(parseEcsTarget(resolvedTarget).stackPattern, stacks);
|
|
26054
|
+
stateProvider = createLocalStateProvider(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region), extraStateProviders);
|
|
26055
|
+
const imageContext = await buildEcsImageResolutionContext(candidate, stateProvider, options);
|
|
26056
|
+
const task = resolveEcsTaskTarget(resolvedTarget, stacks, imageContext);
|
|
26057
|
+
logger.info(`Target: ${task.stack.stackName}/${task.taskDefinitionLogicalId} (family=${task.family}, containers=${task.containers.length})`);
|
|
26058
|
+
const taskNeeds = detectEcsImageResolutionNeeds(stacks.find((s) => s.stackName === task.stack.stackName) ?? task.stack);
|
|
26059
|
+
if (stateProvider && taskNeeds.needsCrossStackResolver) {
|
|
26060
|
+
const consumerRegion = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? task.stack.region ?? "us-east-1";
|
|
26061
|
+
const resolver = await stateProvider.buildCrossStackResolver(consumerRegion);
|
|
26062
|
+
if (resolver) await applyCrossStackResolverToTask(task, {
|
|
26063
|
+
resources: imageContext?.stateResources ?? {},
|
|
26064
|
+
...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
|
|
26065
|
+
...imageContext?.stateParameters && { parameters: imageContext.stateParameters },
|
|
26066
|
+
...imageContext?.stateSensitiveParameters?.length && { sensitiveParameters: new Set(imageContext.stateSensitiveParameters) },
|
|
26067
|
+
consumerRegion,
|
|
26068
|
+
crossStackResolver: resolver
|
|
26069
|
+
});
|
|
26070
|
+
} else if (!stateProvider && taskNeeds.needsCrossStackResolver) logger.warn("Container Environment / Secrets entries contain Fn::ImportValue / Fn::GetStackOutput intrinsics. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against deployed state.");
|
|
26071
|
+
sigintHandler = () => {
|
|
26072
|
+
sigintCount += 1;
|
|
26073
|
+
if (sigintCount >= 2) {
|
|
26074
|
+
process.stderr.write("Force-exit on second ^C; container cleanup skipped.\n");
|
|
26075
|
+
process.exit(130);
|
|
26076
|
+
}
|
|
26077
|
+
logger.info("Stopping task...");
|
|
26078
|
+
cleanup().then(() => process.exit(130));
|
|
26079
|
+
};
|
|
26080
|
+
process.on("SIGINT", sigintHandler);
|
|
26081
|
+
const effectiveAssumeRole = resolveEcsAssumeRoleOption(options);
|
|
26082
|
+
let assumedCredentials;
|
|
26083
|
+
let resolvedRoleArn;
|
|
26084
|
+
if (effectiveAssumeRole === true) {
|
|
26085
|
+
if (!task.taskRoleArn) throw new Error(`--assume-role passed without an ARN but the task definition has no resolvable TaskRoleArn. Either the task definition does not set TaskRoleArn, or it points at a resource ${getEmbedConfig().binaryName} cannot resolve to an IAM Role at synth time. Pass the ARN explicitly: --assume-role <arn>`);
|
|
26086
|
+
resolvedRoleArn = await resolvePlaceholderAccount(task.taskRoleArn, options.region, options.profile);
|
|
26087
|
+
assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region, options.profile);
|
|
26088
|
+
} else if (typeof effectiveAssumeRole === "string") {
|
|
26089
|
+
resolvedRoleArn = effectiveAssumeRole;
|
|
26090
|
+
assumedCredentials = await assumeTaskRole(resolvedRoleArn, options.region, options.profile);
|
|
26091
|
+
}
|
|
26092
|
+
const sidecarCredentials = await resolveSidecarCredentials(options, assumedCredentials);
|
|
26093
|
+
if (options.profile && sidecarCredentials && !assumedCredentials) profileCredsFile = await writeProfileCredentialsFile(options.profile, sidecarCredentials);
|
|
26094
|
+
const envOverrides = readEnvOverridesFile(options.envVars);
|
|
26095
|
+
const runOpts = {
|
|
26096
|
+
cluster: options.cluster,
|
|
26097
|
+
containerHost: options.containerHost,
|
|
26098
|
+
skipPull: options.pull === false,
|
|
26099
|
+
skipBuild: options.build === false,
|
|
26100
|
+
keepRunning: options.keepRunning,
|
|
26101
|
+
detach: options.detach
|
|
26102
|
+
};
|
|
26103
|
+
if (envOverrides) runOpts.envOverrides = envOverrides;
|
|
26104
|
+
if (sidecarCredentials) runOpts.taskCredentials = sidecarCredentials;
|
|
26105
|
+
if (resolvedRoleArn) runOpts.taskRoleArn = resolvedRoleArn;
|
|
26106
|
+
if (options.platform) runOpts.platformOverride = options.platform;
|
|
26107
|
+
if (options.region) runOpts.region = options.region;
|
|
26108
|
+
if (options.ecrRoleArn) runOpts.ecrRoleArn = options.ecrRoleArn;
|
|
26109
|
+
if (options.profile) runOpts.profile = options.profile;
|
|
26110
|
+
const hostPortOverrides = parseHostPortOverrides(options.hostPort);
|
|
26111
|
+
if (Object.keys(hostPortOverrides).length > 0) runOpts.hostPortOverrides = hostPortOverrides;
|
|
26112
|
+
if (profileCredsFile) runOpts.profileCredentialsFile = {
|
|
26113
|
+
hostPath: profileCredsFile.hostPath,
|
|
26114
|
+
containerPath: profileCredsFile.containerPath,
|
|
26115
|
+
profileName: profileCredsFile.profileName
|
|
26116
|
+
};
|
|
26117
|
+
const result = await runEcsTask(task, runOpts, state);
|
|
26118
|
+
if (options.detach) {
|
|
26119
|
+
logger.info(`Task containers started in detached mode; ${getEmbedConfig().binaryName} is exiting.`);
|
|
26120
|
+
logger.info(`Use 'docker ps --filter network=${result.state.network?.networkName ?? "<network>"}' to inspect; tear down with 'docker rm -f' and 'docker network rm'.`);
|
|
26121
|
+
sigintCount = 99;
|
|
26122
|
+
return;
|
|
26123
|
+
}
|
|
26124
|
+
if (result.essentialContainerName) logger.info(`Essential container '${result.essentialContainerName}' exited with code ${result.exitCode}.`);
|
|
26125
|
+
if (result.exitCode !== 0) process.exitCode = result.exitCode;
|
|
26126
|
+
} finally {
|
|
26127
|
+
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
26128
|
+
if (stateProvider) stateProvider.dispose();
|
|
26129
|
+
if (!options.detach) await cleanup();
|
|
26130
|
+
}
|
|
26131
|
+
}
|
|
26132
|
+
/**
|
|
26133
|
+
* If `arn` contains the `${AWS::AccountId}` placeholder emitted by the
|
|
26134
|
+
* resolver for inline same-stack IAM Roles, substitute the live caller
|
|
26135
|
+
* account via STS `GetCallerIdentity`. Otherwise pass through unchanged.
|
|
26136
|
+
*/
|
|
26137
|
+
async function resolvePlaceholderAccount(arn, region, profile) {
|
|
26138
|
+
if (!arn.includes("${AWS::AccountId}")) return arn;
|
|
26139
|
+
const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
|
|
26140
|
+
const sts = new STSClient(buildStsClientConfig({
|
|
26141
|
+
region,
|
|
26142
|
+
profile
|
|
26143
|
+
}));
|
|
26144
|
+
try {
|
|
26145
|
+
const account = (await sts.send(new GetCallerIdentityCommand({}))).Account;
|
|
26146
|
+
if (!account) throw new Error(`--assume-task-role: GetCallerIdentity returned no Account; cannot resolve placeholder ARN '${arn}'. Pass the ARN explicitly: --assume-task-role <arn>`);
|
|
26147
|
+
return arn.split(TASK_ROLE_ACCOUNT_PLACEHOLDER).join(account);
|
|
26148
|
+
} finally {
|
|
26149
|
+
sts.destroy();
|
|
26150
|
+
}
|
|
26151
|
+
}
|
|
26152
|
+
/**
|
|
26153
|
+
* Assume `roleArn` and return temp credentials.
|
|
26154
|
+
*/
|
|
26155
|
+
async function assumeTaskRole(roleArn, region, profile) {
|
|
26156
|
+
const { STSClient, AssumeRoleCommand } = await import("@aws-sdk/client-sts");
|
|
26157
|
+
const sts = new STSClient(buildStsClientConfig({
|
|
26158
|
+
region,
|
|
26159
|
+
profile
|
|
26160
|
+
}));
|
|
26161
|
+
try {
|
|
26162
|
+
const creds = (await sts.send(new AssumeRoleCommand({
|
|
26163
|
+
RoleArn: roleArn,
|
|
26164
|
+
RoleSessionName: `${getEmbedConfig().resourceNamePrefix}-run-task-${Date.now()}`,
|
|
26165
|
+
DurationSeconds: 3600
|
|
26166
|
+
}))).Credentials;
|
|
26167
|
+
if (!creds?.AccessKeyId || !creds.SecretAccessKey || !creds.SessionToken) throw new Error(`AssumeRole(${roleArn}) returned no usable credentials.`);
|
|
26168
|
+
return {
|
|
26169
|
+
accessKeyId: creds.AccessKeyId,
|
|
26170
|
+
secretAccessKey: creds.SecretAccessKey,
|
|
26171
|
+
sessionToken: creds.SessionToken
|
|
26172
|
+
};
|
|
26173
|
+
} finally {
|
|
26174
|
+
sts.destroy();
|
|
26175
|
+
}
|
|
26176
|
+
}
|
|
26177
|
+
/**
|
|
26178
|
+
* Build the substitution context the ECS task resolver consumes.
|
|
26179
|
+
* Returns `undefined` when no container's `Image` field needs
|
|
26180
|
+
* substitution — the resolver behaves as before in that case.
|
|
26181
|
+
*/
|
|
26182
|
+
async function buildEcsImageResolutionContext(candidate, stateProvider, options) {
|
|
26183
|
+
const logger = getLogger();
|
|
26184
|
+
if (!candidate) return void 0;
|
|
26185
|
+
const needs = detectEcsImageResolutionNeeds(candidate);
|
|
26186
|
+
if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution) return;
|
|
26187
|
+
const ctx = {};
|
|
26188
|
+
const wantsPseudoForEnvOrSecret = !!stateProvider && needs.needsEnvOrSecretSubstitution;
|
|
26189
|
+
if (needs.needsPseudoParameters || wantsPseudoForEnvOrSecret) {
|
|
26190
|
+
const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
|
|
26191
|
+
if (!region) logger.warn(`Resolver references \${AWS::Region} but ${getEmbedConfig().binaryName} could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack.`);
|
|
26192
|
+
let accountId;
|
|
26193
|
+
try {
|
|
26194
|
+
accountId = await resolveCallerAccountId(region, options.profile);
|
|
26195
|
+
} catch (err) {
|
|
26196
|
+
logger.warn(`Resolver needs \${AWS::AccountId} but STS GetCallerIdentity failed: ${err instanceof Error ? err.message : String(err)}. Substitution will be skipped; affected env / secret entries will be dropped with per-key warnings.`);
|
|
26197
|
+
}
|
|
26198
|
+
const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : void 0;
|
|
26199
|
+
ctx.pseudoParameters = {
|
|
26200
|
+
...accountId !== void 0 && { accountId },
|
|
26201
|
+
...region !== void 0 && { region },
|
|
26202
|
+
...partitionAndSuffix && {
|
|
26203
|
+
partition: partitionAndSuffix.partition,
|
|
26204
|
+
urlSuffix: partitionAndSuffix.urlSuffix
|
|
26205
|
+
}
|
|
26206
|
+
};
|
|
26207
|
+
}
|
|
26208
|
+
const wantsState = needs.needsStateResources || needs.needsEnvOrSecretSubstitution;
|
|
26209
|
+
if (stateProvider && wantsState) {
|
|
26210
|
+
const loaded = await stateProvider.load(candidate.stackName, candidate.region);
|
|
26211
|
+
if (loaded) ctx.stateResources = loaded.resources;
|
|
26212
|
+
else {
|
|
26213
|
+
const loadError = stateProvider.getLastLoadError?.();
|
|
26214
|
+
if (loadError) ctx.stateLoadFailureMessage = loadError;
|
|
26215
|
+
}
|
|
26216
|
+
if (needs.needsEnvOrSecretSubstitution && stateProvider.resolveTemplateSsmParameters) {
|
|
26217
|
+
const ssmParameters = await stateProvider.resolveTemplateSsmParameters(candidate.template);
|
|
26218
|
+
if (Object.keys(ssmParameters.values).length > 0) ctx.stateParameters = ssmParameters.values;
|
|
26219
|
+
if (ssmParameters.secureStringLogicalIds.length > 0) ctx.stateSensitiveParameters = ssmParameters.secureStringLogicalIds;
|
|
26220
|
+
}
|
|
26221
|
+
} else if (!stateProvider && needs.needsStateResources) logger.warn("Container Image references a same-stack AWS::ECR::Repository. Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute the deployed repository URI. Otherwise the resolver will surface its existing error.");
|
|
26222
|
+
else if (!stateProvider && needs.needsEnvOrSecretSubstitution) logger.warn("Container Environment / Secrets entries contain CloudFormation intrinsics (Ref / Fn::GetAtt / Fn::Sub / Fn::Join). Pass a state-source flag (e.g. --from-cfn-stack or a host-provided extension) to substitute them against deployed state. Without a state source these entries are dropped (per-key warnings will follow).");
|
|
26223
|
+
return ctx;
|
|
26224
|
+
}
|
|
26225
|
+
function pickCandidateStack(stackPattern, stacks) {
|
|
26226
|
+
if (stackPattern === null) {
|
|
26227
|
+
if (stacks.length === 1) return stacks[0];
|
|
26228
|
+
return;
|
|
26229
|
+
}
|
|
26230
|
+
const matched = matchStacks(stacks, [stackPattern]);
|
|
26231
|
+
if (matched.length === 1) return matched[0];
|
|
26232
|
+
}
|
|
26233
|
+
async function resolveCallerAccountId(region, profile) {
|
|
26234
|
+
const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
|
|
26235
|
+
const sts = new STSClient(buildStsClientConfig({
|
|
26236
|
+
region,
|
|
26237
|
+
profile
|
|
26238
|
+
}));
|
|
26239
|
+
try {
|
|
26240
|
+
return (await sts.send(new GetCallerIdentityCommand({}))).Account;
|
|
26241
|
+
} finally {
|
|
26242
|
+
sts.destroy();
|
|
26243
|
+
}
|
|
26244
|
+
}
|
|
26245
|
+
/**
|
|
26246
|
+
* Read the `--env-vars` JSON file using the same SAM-style shape as
|
|
26247
|
+
* `cdkl invoke --env-vars`: top-level keys are container names, with
|
|
26248
|
+
* `Parameters` reserved for global entries.
|
|
26249
|
+
*/
|
|
26250
|
+
function readEnvOverridesFile(filePath) {
|
|
26251
|
+
if (!filePath) return void 0;
|
|
26252
|
+
let raw;
|
|
26253
|
+
try {
|
|
26254
|
+
raw = readFileSync(filePath, "utf-8");
|
|
26255
|
+
} catch (err) {
|
|
26256
|
+
throw new Error(`Failed to read --env-vars file '${filePath}': ${err instanceof Error ? err.message : String(err)}`);
|
|
26257
|
+
}
|
|
26258
|
+
let parsed;
|
|
26259
|
+
try {
|
|
26260
|
+
parsed = JSON.parse(raw);
|
|
26261
|
+
} catch (err) {
|
|
26262
|
+
throw new Error(`Failed to parse --env-vars file '${filePath}' as JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
26263
|
+
}
|
|
26264
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`--env-vars file '${filePath}' must contain a JSON object at the top level.`);
|
|
26265
|
+
return parsed;
|
|
26266
|
+
}
|
|
26267
|
+
/**
|
|
26268
|
+
* Pick the credentials forwarded to the AWS-published
|
|
26269
|
+
* `amazon-ecs-local-container-endpoints` sidecar. Precedence:
|
|
26270
|
+
* 1. `--assume-task-role <arn>` (or bare `--assume-task-role` against
|
|
26271
|
+
* a resolvable `TaskRoleArn`) → STS-assumed temp creds. Highest
|
|
26272
|
+
* priority — when the user opted in to IAM emulation, those creds
|
|
26273
|
+
* drive the sidecar regardless of `--profile`.
|
|
26274
|
+
* 2. `--profile <p>` → resolved via {@link resolveProfileCredentials}
|
|
26275
|
+
* (the SDK's default credential provider chain — SSO / IAM
|
|
26276
|
+
* Identity Center / fromIni / role-assumption). NEW in this PR.
|
|
26277
|
+
* 3. Neither set → `undefined`; the sidecar runs with its own
|
|
26278
|
+
* default credential chain (typically empty inside a fresh
|
|
26279
|
+
* container — user containers will get 4xx from the credentials
|
|
26280
|
+
* endpoint, mimicking IAM-misconfigured prod).
|
|
26281
|
+
*
|
|
26282
|
+
* Extracted as an exported helper so a unit test can exercise every
|
|
26283
|
+
* branch without having to mock the full Synth + Docker + AWS pipeline
|
|
26284
|
+
* (the strategy used for the Lambda container path).
|
|
26285
|
+
*/
|
|
26286
|
+
async function resolveSidecarCredentials(options, assumedCredentials) {
|
|
26287
|
+
if (assumedCredentials) return assumedCredentials;
|
|
26288
|
+
if (options.profile) return resolveProfileCredentials(options.profile);
|
|
26289
|
+
}
|
|
26290
|
+
function createLocalRunTaskCommand(opts = {}) {
|
|
26291
|
+
setEmbedConfig(opts.embedConfig);
|
|
26292
|
+
const cmd = new Command("run-task").description("Run an AWS::ECS::TaskDefinition locally — pulls/builds images, sets up a per-task docker network with the AWS-published metadata-endpoints sidecar, and starts every container in dependsOn order. Target accepts a CDK display path (MyStack/MyService/TaskDef) or stack-qualified logical ID (MyStack:MyServiceTaskDefXYZ1234). Single-stack apps may omit the stack prefix. Omit <target> in an interactive terminal to pick the task definition from a list.").argument("[target]", "CDK display path or stack-qualified logical ID of the AWS::ECS::TaskDefinition to run (omit to pick interactively in a TTY)").action(withErrorHandling(async (target, options) => {
|
|
26293
|
+
await localRunTaskCommand(target, options, opts.extraStateProviders);
|
|
26294
|
+
}));
|
|
26295
|
+
addRunTaskSpecificOptions(cmd);
|
|
26296
|
+
[
|
|
26297
|
+
...commonOptions(),
|
|
26298
|
+
...appOptions(),
|
|
26299
|
+
...contextOptions
|
|
26300
|
+
].forEach((opt) => cmd.addOption(opt));
|
|
26301
|
+
cmd.addOption(regionOption);
|
|
26302
|
+
return cmd;
|
|
26303
|
+
}
|
|
26304
|
+
/**
|
|
26305
|
+
* Register the option block that `cdkl run-task` adds on top of the shared
|
|
26306
|
+
* common / app / context option helpers. Shared between `cdkl run-task` and
|
|
26307
|
+
* any host CLI (e.g. cdkd's `local run-task`) that wraps the single-task
|
|
26308
|
+
* ECS local runner, so adding or renaming a `run-task`-only flag here
|
|
26309
|
+
* propagates to every embedder without duplicate `.addOption(...)` blocks.
|
|
26310
|
+
*
|
|
26311
|
+
* Calling order only affects `--help` presentation (Commander parses
|
|
26312
|
+
* insertion-order-independent). The host-CLI convention is host-specific
|
|
26313
|
+
* options first, then this helper, then the shared common / app / context
|
|
26314
|
+
* options — host flags / run-task flags / common flags grouped in three
|
|
26315
|
+
* `--help` clusters. Chainable: returns `cmd`.
|
|
26316
|
+
*
|
|
26317
|
+
* NOTE: `run-task` does NOT compose with {@link addCommonEcsServiceOptions}
|
|
26318
|
+
* even though many flags overlap. The two ECS surfaces (single-task vs
|
|
26319
|
+
* multi-replica service) have intentionally divergent defaults
|
|
26320
|
+
* (`run-task` has no `--max-tasks` / `--restart-policy`; `start-service`
|
|
26321
|
+
* / `start-alb` have no `--host-port` / `--keep-running` / `--detach`),
|
|
26322
|
+
* and folding `run-task` into the service common block would mutate the
|
|
26323
|
+
* surface non-trivially. Each command keeps its own helper.
|
|
26324
|
+
*/
|
|
26325
|
+
function addRunTaskSpecificOptions(cmd) {
|
|
26326
|
+
cmd.addOption(ecsClusterOption()).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--host-port <containerPort=hostPort...>", "Publish a container port on a specific host port (e.g. 80=8080); repeatable. Default: host port == container port. Use this on macOS to map a privileged container port (< 1024) to a non-privileged host port and avoid the Docker Desktop admin-password prompt."));
|
|
26327
|
+
addEcsAssumeRoleOptions(cmd);
|
|
26328
|
+
cmd.addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--no-build", "Skip docker build on every CDK-asset container (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ECR-pull / public-registry containers. Compatible with --no-pull.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--keep-running", "Don't docker rm -f the user containers on task exit (network + sidecar are still torn down). Use when you want to docker exec into a stopped container for post-mortems.").default(false)).addOption(new Option("--detach", "Start the containers in the background and exit (skip log streaming + auto teardown). Useful in CI smoke tests; caller manages container lifecycle.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", `Read a deployed CloudFormation stack via ListStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (\`cdk deploy\`). Bare form uses the ${getEmbedConfig().binaryName} stack name; pass an explicit value when the CFn stack name differs. Fn::GetAtt in container Environment[].Value is warn-and-dropped: CFn ListStackResources does not return per-attribute values, and unlike Lambda (where \`cdkl invoke --from-cfn-stack\` recovers Fn::GetAtt from the deployed function via lambda:GetFunctionConfiguration), no ECS-side equivalent resolves attributes off a deployed task / service.`)).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region."));
|
|
26329
|
+
return cmd;
|
|
26330
|
+
}
|
|
26331
|
+
|
|
26235
26332
|
//#endregion
|
|
26236
26333
|
//#region src/cli/commands/local-start-service.ts
|
|
26237
26334
|
/**
|
|
@@ -27156,7 +27253,7 @@ function createLocalStartAlbCommand(opts = {}) {
|
|
|
27156
27253
|
*/
|
|
27157
27254
|
function addAlbSpecificOptions(cmd) {
|
|
27158
27255
|
addImageOverrideOptions(cmd);
|
|
27159
|
-
return cmd.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", "Terminate TLS locally for cloud-HTTPS listeners. Default: a cloud-HTTPS listener is served over plain HTTP locally (X-Forwarded-Proto: https is preserved so the upstream app still sees the deployed listener protocol). Implied by --tls-cert / --tls-key. Use this when local-dev cookies need Secure / SameSite=None, when the upstream app inspects TLS metadata, or for mTLS / SNI testing — otherwise plain HTTP is friendlier (no self-signed cert warnings in curl / browser).")).addOption(new Option("--tls-cert <path>", "PEM-encoded server certificate for HTTPS front-door listeners. Implies --tls. Must be set together with --tls-key. Pass --tls alone (without --tls-cert / --tls-key) 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. Implies --tls. Must be set together with --tls-cert.")).addOption(new Option("--no-verify-auth", "Disable local enforcement of authenticate-cognito / authenticate-oidc actions. Every request is served as if the auth check passed. Useful for local dev where you do not want to mint a Bearer token at all.")).addOption(new Option("--bearer-token <jwt>", "Default Bearer JWT
|
|
27256
|
+
return cmd.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", "Terminate TLS locally for cloud-HTTPS listeners. Default: a cloud-HTTPS listener is served over plain HTTP locally (X-Forwarded-Proto: https is preserved so the upstream app still sees the deployed listener protocol). Implied by --tls-cert / --tls-key. Use this when local-dev cookies need Secure / SameSite=None, when the upstream app inspects TLS metadata, or for mTLS / SNI testing — otherwise plain HTTP is friendlier (no self-signed cert warnings in curl / browser).")).addOption(new Option("--tls-cert <path>", "PEM-encoded server certificate for HTTPS front-door listeners. Implies --tls. Must be set together with --tls-key. Pass --tls alone (without --tls-cert / --tls-key) 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. Implies --tls. Must be set together with --tls-cert.")).addOption(new Option("--no-verify-auth", "Disable local enforcement of authenticate-cognito / authenticate-oidc actions. Every request is served as if the auth check passed. Useful for local dev where you do not want to mint a Bearer token at all.")).addOption(new Option("--bearer-token <jwt>", "Default Bearer JWT this command INJECTS only when an inbound request has none (the default-when-missing role) — `cdkl start-alb` is the local ALB front-door RECEIVING outside-in requests, so this token is the fallback the front-door slots in as Authorization: Bearer <jwt> if the caller did not already supply one. Verified against the same JWKS / OIDC discovery URL the deployed ALB would (signature + iss + aud + exp). Cookie pass-through (AWSELBAuthSessionCookie-*) also bypasses the guard. Contrast with `cdkl invoke-agentcore --bearer-token`, where the role is reversed — that command is the outbound client and ALWAYS presents this token (the supplier).")).addOption(new Option("--watch", "Hot-reload: re-synth + per-replica reload of every ECS service behind the ALB when the CDK source changes (honors cdk.json watch.include/exclude; cdk.out, node_modules, .git are always excluded). A per-firing classifier picks the per-replica primitive: source-only edits on interpreted-language handlers (Node/Python/Ruby/shell) take a bind-mount FAST PATH (`docker cp` the new source into each replica + `docker restart`; no rebuild, front-door pool entry unchanged since the IP/port are preserved). Dockerfile / dependency manifest / compiled-language source / ambiguous edits fall through to the rebuild rolling primitive — boot a shadow under a bumped generation suffix, wait for its container port to accept a TCP connection, atomically register it in the front-door pool, then drop the old entry and retire the old container. Either path rolls one replica at a time, so a continuous external request stream against the listener port sees zero connection refusals across the reload. The host front-door (TLS, JWKS cache, Lambda-target containers, listener sockets) stays up across the reload. Lambda target groups behind the ALB are a no-op on reload (the warm RIE container keeps its boot-time image). Off by default; existing replica(s) keep serving when synth fails mid-reload.").default(false));
|
|
27160
27257
|
}
|
|
27161
27258
|
|
|
27162
27259
|
//#endregion
|
|
@@ -27252,5 +27349,5 @@ function addListSpecificOptions(cmd) {
|
|
|
27252
27349
|
}
|
|
27253
27350
|
|
|
27254
27351
|
//#endregion
|
|
27255
|
-
export {
|
|
27256
|
-
//# sourceMappingURL=local-list-
|
|
27352
|
+
export { MCP_PROTOCOL_VERSION as $, pickAgentCoreCandidateStack as $n, applyAuthorizerOverlay as $t, mergeForService as A, rejectExplicitCfnStackWithMultipleStacks as An, startApiServer as At, DEFAULT_SHADOW_READY_TIMEOUT_MS as B, listTargets as Bn, buildMethodArn as Bt, parseRestartPolicy as C, substituteAgainstState as Cn, materializeLayerFromArn as Ct, ImageOverrideError as D, LocalStateSourceError as Dn, filterRoutesByApiIdentifiers as Dt, runEcsServiceEmulator as E, substituteEnvVarsFromStateAsync as En, filterRoutesByApiIdentifier as Et, isLocalCdkAssetImage as F, collectSsmParameterRefs as Fn, buildJwksUrlFromIssuer as Ft, addInvokeAgentCoreSpecificOptions as G, pickRefLogicalId as Gn, attachAuthorizers as Gt, setShadowReadyTimeoutMs as H, discoverWebSocketApisOrThrow as Hn, evaluateCachedLambdaPolicy as Ht, listPinnedTargets as I, resolveSsmParameters as In, createJwksCache as It, A2A_CONTAINER_PORT as J, AGENTCORE_AGUI_PROTOCOL as Jn, buildCorsConfigFromCloudFrontChain as Jt, createLocalInvokeAgentCoreCommand as K, resolveLambdaArnIntrinsic as Kn, applyCorsResponseHeaders as Kt, buildCloudMapIndex as L, resolveWatchConfig as Ln, verifyCognitoJwt as Lt, resolveImageOverrides as M, resolveCfnRegion as Mn, resolveServiceIntegrationParameters as Mt, runImageOverrideBuilds as N, resolveCfnStackName as Nn, defaultCredentialsLoader as Nt, buildImageOverrideTag as O, createLocalStateProvider as On, groupRoutesByServer as Ot, describePinnedImageUri as P, CfnLocalStateProvider as Pn, buildCognitoJwksUrl as Pt, MCP_PATH as Q, AgentCoreResolutionError as Qn, translateLambdaResponse as Qt, CloudMapRegistry as R, resolveSingleTarget as Rn, verifyJwtAuthorizer as Rt, parseMaxTasks as S, EcsTaskResolutionError as Sn, buildStageMap as St, resolveSharedSidecarCredentials as T, substituteEnvVarsFromState as Tn, availableApiIdentifiers as Tt, getContainerNetworkIp as U, parseSelectionExpressionPath as Un, invokeRequestAuthorizer as Ut, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as V, discoverWebSocketApis as Vn, computeRequestIdentityHash as Vt, attachContainerLogStreamer as W, discoverRoutes as Wn, invokeTokenAuthorizer as Wt, a2aInvokeOnce as X, AGENTCORE_MCP_PROTOCOL as Xn, matchPreflight as Xt, A2A_PATH as Y, AGENTCORE_HTTP_PROTOCOL as Yn, isFunctionUrlOacFronted as Yt, MCP_CONTAINER_PORT as Z, AGENTCORE_RUNTIME_TYPE as Zn, matchRoute as Zt, addCommonEcsServiceOptions as _, architectureToPlatform as _n, createWatchPredicates as _t, albStrategy as a, tryParseStatus as an, LocalInvokeBuildError as ar, invokeAgentCore as at, buildEcsImageResolutionContext$1 as b, resolveRuntimeFileExtension as bn, createFileWatcher as bt, resolveAlbTarget as c, probeHostGatewaySupport as cn, SUPPORTED_CODE_RUNTIMES as ct, addStartServiceSpecificOptions as d, buildMgmtEndpointEnvUrl as dn, renderCodeDockerfile as dt, buildHttpApiV2Event as en, resolveAgentCoreTarget as er, mcpInvokeOnce as et, createLocalStartServiceCommand as f, handleConnectionsRequest as fn, toCmdArgv as ft, MAX_TASKS_SUBNET_RANGE_CAP as g, buildMessageEvent as gn, createLocalStartApiCommand as gt, createLocalRunTaskCommand as h, buildDisconnectEvent as hn, addStartApiSpecificOptions as ht, addAlbSpecificOptions as i, selectIntegrationResponse as in, tryResolveImageFnJoin as ir, AGENTCORE_SESSION_ID_HEADER as it, parseImageOverrideFlags as j, resolveCfnFallbackRegion as jn, resolveSelectionExpression as jt, enforceImageOverrideOrphans as k, isCfnFlagPresent as kn, readMtlsMaterialsFromDisk as kt, isApplicationLoadBalancer as l, bufferToBody as ln, buildAgentCoreCodeImage as lt, addRunTaskSpecificOptions as m, buildConnectEvent as mn, createLocalInvokeCommand as mt, createLocalListCommand as n, evaluateResponseParameters as nn, formatStateRemedy as nr, AGENTCORE_SIGV4_SERVICE as nt, createLocalStartAlbCommand as o, VtlEvaluationError as on, buildStsClientConfig as or, waitForAgentCorePing as ot, serviceStrategy as p, parseConnectionsPath as pn, addInvokeSpecificOptions as pt, invokeAgentCoreWs as q, AGENTCORE_A2A_PROTOCOL as qn, buildCorsConfigByApiId as qt, formatTargetListing as r, pickResponseTemplate as rn, substituteImagePlaceholders as rr, signAgentCoreInvocation as rt, parseLbPortOverrides as s, HOST_GATEWAY_MIN_VERSION as sn, resolveProfileCredentials as sr, downloadAndExtractS3Bundle as st, addListSpecificOptions as t, buildRestV1Event as tn, derivePseudoParametersFromRegion as tr, parseSseForJsonRpc as tt, resolveAlbFrontDoor as u, ConnectionRegistry as un, computeCodeImageTag as ut, addEcsAssumeRoleOptions as v, buildContainerImage as vn, resolveApiTargetSubset as vt, resolveEcsAssumeRoleOption as w, substituteAgainstStateAsync as wn, resolveEnvVars as wt, ecsClusterOption as x, resolveRuntimeImage as xn, attachStageContext as xt, addImageOverrideOptions as y, resolveRuntimeCodeMountPath as yn, createAuthorizerCache as yt, classifySourceChange as z, countTargets as zn, verifyJwtViaDiscovery as zt };
|
|
27353
|
+
//# sourceMappingURL=local-list-Dn0Al276.js.map
|