cdk-local 0.26.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +87 -71
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{local-list-CnAKqGmz.js → local-list-yvjjjkSy.js} +304 -18
- package/dist/local-list-yvjjjkSy.js.map +1 -0
- package/package.json +1 -1
- package/dist/local-list-CnAKqGmz.js.map +0 -1
|
@@ -10,6 +10,7 @@ import { AssetManifestArtifact } from "@aws-cdk/cloud-assembly-api";
|
|
|
10
10
|
import { BaseCredentials, CdkAppMultiContext, NonInteractiveIoHost, Toolkit } from "@aws-cdk/toolkit-lib";
|
|
11
11
|
import { CloudFormationClient, DescribeStacksCommand, ListExportsCommand, ListStackResourcesCommand } from "@aws-sdk/client-cloudformation";
|
|
12
12
|
import { GetFunctionConfigurationCommand, LambdaClient } from "@aws-sdk/client-lambda";
|
|
13
|
+
import { GetParameterCommand, GetParametersCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
13
14
|
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
14
15
|
import { Readable } from "node:stream";
|
|
15
16
|
import { pipeline } from "node:stream/promises";
|
|
@@ -27,7 +28,6 @@ import { createServer as createServer$2 } from "node:https";
|
|
|
27
28
|
import * as chokidar from "chokidar";
|
|
28
29
|
import graphlib from "graphlib";
|
|
29
30
|
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
|
|
30
|
-
import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
31
31
|
|
|
32
32
|
//#region src/cli/options.ts
|
|
33
33
|
/**
|
|
@@ -2354,6 +2354,156 @@ function resolveWatchConfig() {
|
|
|
2354
2354
|
};
|
|
2355
2355
|
}
|
|
2356
2356
|
|
|
2357
|
+
//#endregion
|
|
2358
|
+
//#region src/local/ssm-parameter-resolver.ts
|
|
2359
|
+
/**
|
|
2360
|
+
* Resolve CloudFormation template `Parameters` of the SSM-backed types
|
|
2361
|
+
* `AWS::SSM::Parameter::Value<String>` / `AWS::SSM::Parameter::Value<List<String>>`
|
|
2362
|
+
* (what CDK synthesizes for `ssm.StringParameter.valueForStringParameter(...)`)
|
|
2363
|
+
* into their deployed values via SSM Parameter Store.
|
|
2364
|
+
*
|
|
2365
|
+
* Motivation (issue #94): a container / Lambda env var that `Ref`s such a
|
|
2366
|
+
* parameter cannot be resolved from the `--from-cfn-stack` state source.
|
|
2367
|
+
* That source is built from `ListStackResources` (deployed RESOURCES); a
|
|
2368
|
+
* CloudFormation PARAMETER is not a resource, so the `Ref` misses
|
|
2369
|
+
* `context.resources[<id>]` in `state-resolver.ts` and the env var is
|
|
2370
|
+
* warn-and-dropped. The synthesized template, however, carries the SSM
|
|
2371
|
+
* parameter NAME in each entry's `Default`:
|
|
2372
|
+
*
|
|
2373
|
+
* "Parameters": {
|
|
2374
|
+
* "SsmParameterValue...Parameter": {
|
|
2375
|
+
* "Type": "AWS::SSM::Parameter::Value<String>",
|
|
2376
|
+
* "Default": "/path/to/the/parameter"
|
|
2377
|
+
* }
|
|
2378
|
+
* }
|
|
2379
|
+
*
|
|
2380
|
+
* and the run already has working AWS credentials / region (the same ones
|
|
2381
|
+
* `--from-cfn-stack` uses for `ListStackResources`). This module reads each
|
|
2382
|
+
* parameter NAME from `Default`, batch-resolves the values via SSM
|
|
2383
|
+
* `GetParameters`, and returns a `logicalId -> value` map the CLI feeds
|
|
2384
|
+
* into the substitution context so a `Ref` to the parameter's logical id
|
|
2385
|
+
* resolves to the value instead of being dropped.
|
|
2386
|
+
*
|
|
2387
|
+
* Best-effort by design: on any SSM failure (no credentials, access
|
|
2388
|
+
* denied, throttling) the helper logs a warn and returns whatever it
|
|
2389
|
+
* could resolve (possibly nothing) — the caller then falls back to the
|
|
2390
|
+
* existing warn-and-drop behavior on the affected `Ref`s. It NEVER throws
|
|
2391
|
+
* out of the substitution pass.
|
|
2392
|
+
*/
|
|
2393
|
+
/** SSM-backed CFn parameter types CDK synthesizes for SSM lookups. */
|
|
2394
|
+
const SSM_STRING_TYPE = "AWS::SSM::Parameter::Value<String>";
|
|
2395
|
+
const SSM_LIST_TYPE = "AWS::SSM::Parameter::Value<List<String>>";
|
|
2396
|
+
/**
|
|
2397
|
+
* Scan a synthesized template's `Parameters` block for entries whose
|
|
2398
|
+
* `Type` is one of the SSM-backed parameter types AND whose `Default`
|
|
2399
|
+
* carries a usable SSM parameter name. Pure — no AWS calls.
|
|
2400
|
+
*
|
|
2401
|
+
* Entries without a non-empty string `Default` are skipped (CDK always
|
|
2402
|
+
* synthesizes the parameter name into `Default` for the
|
|
2403
|
+
* `valueForStringParameter` shape; a parameter declared without a default
|
|
2404
|
+
* has no name we can resolve, so it stays warn-and-drop).
|
|
2405
|
+
*
|
|
2406
|
+
* Exported for unit testing.
|
|
2407
|
+
*/
|
|
2408
|
+
function collectSsmParameterRefs(template) {
|
|
2409
|
+
const params = template?.Parameters;
|
|
2410
|
+
if (!params) return [];
|
|
2411
|
+
const out = [];
|
|
2412
|
+
for (const [logicalId, entry] of Object.entries(params)) {
|
|
2413
|
+
if (!entry || typeof entry !== "object") continue;
|
|
2414
|
+
const type = entry.Type;
|
|
2415
|
+
const isList = type === SSM_LIST_TYPE;
|
|
2416
|
+
if (type !== SSM_STRING_TYPE && !isList) continue;
|
|
2417
|
+
const ssmName = entry.Default;
|
|
2418
|
+
if (typeof ssmName !== "string" || ssmName.length === 0) continue;
|
|
2419
|
+
out.push({
|
|
2420
|
+
logicalId,
|
|
2421
|
+
ssmName,
|
|
2422
|
+
isList
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
return out;
|
|
2426
|
+
}
|
|
2427
|
+
/**
|
|
2428
|
+
* Batch-resolve a set of {@link SsmParameterRef}s via SSM `GetParameters`
|
|
2429
|
+
* and return a `logicalId -> resolved value` map. `List<String>`
|
|
2430
|
+
* parameters are comma-joined (CloudFormation surfaces the list type as a
|
|
2431
|
+
* comma-delimited string when the value is consumed as a `Ref`).
|
|
2432
|
+
*
|
|
2433
|
+
* `GetParameters` accepts up to 10 names per call, so the refs are
|
|
2434
|
+
* chunked. Names that SSM reports as invalid (`InvalidParameters`) are
|
|
2435
|
+
* left out of the result map so the caller falls back to warn-and-drop
|
|
2436
|
+
* on the corresponding `Ref`. `WithDecryption: true` is set so Secure
|
|
2437
|
+
* String parameters resolve too (matching how CloudFormation resolves
|
|
2438
|
+
* the `AWS::SSM::Parameter::Value<String>` type at deploy time).
|
|
2439
|
+
*
|
|
2440
|
+
* Security note: a decrypted SecureString value resolved here is then
|
|
2441
|
+
* baked into the container's `Environment` like any other resolved env
|
|
2442
|
+
* value, so it follows the standard plaintext-env exposure path — it can
|
|
2443
|
+
* appear on the `docker run -e KEY=VALUE` argv (visible in host `ps`) and
|
|
2444
|
+
* is not redacted by `redactAwsCredentialsInArgs` (which only covers
|
|
2445
|
+
* `SENSITIVE_ENV_KEYS`). This mirrors the deployed Lambda/ECS env and is
|
|
2446
|
+
* the same inherent exposure documented in `docker-runner`; routing
|
|
2447
|
+
* SecureString-resolved values through the value-from-process-env form is
|
|
2448
|
+
* tracked as a follow-up.
|
|
2449
|
+
*
|
|
2450
|
+
* Best-effort: a failed `GetParameters` chunk logs a warn and is skipped
|
|
2451
|
+
* (the other chunks still contribute their values); the function never
|
|
2452
|
+
* throws.
|
|
2453
|
+
*
|
|
2454
|
+
* `label` is the state-source label (e.g. `--from-cfn-stack`) used to
|
|
2455
|
+
* prefix warns so the user can tell which source produced them.
|
|
2456
|
+
*
|
|
2457
|
+
* Exported for unit testing.
|
|
2458
|
+
*/
|
|
2459
|
+
async function resolveSsmParameters(client, refs, label) {
|
|
2460
|
+
const out = {};
|
|
2461
|
+
if (refs.length === 0) return out;
|
|
2462
|
+
const logger = getLogger();
|
|
2463
|
+
const CHUNK = 10;
|
|
2464
|
+
const byName = /* @__PURE__ */ new Map();
|
|
2465
|
+
for (const ref of refs) {
|
|
2466
|
+
const list = byName.get(ref.ssmName);
|
|
2467
|
+
if (list) list.push(ref);
|
|
2468
|
+
else byName.set(ref.ssmName, [ref]);
|
|
2469
|
+
}
|
|
2470
|
+
const uniqueNames = [...byName.keys()];
|
|
2471
|
+
for (let i = 0; i < uniqueNames.length; i += CHUNK) {
|
|
2472
|
+
const names = uniqueNames.slice(i, i + CHUNK);
|
|
2473
|
+
let resolved;
|
|
2474
|
+
try {
|
|
2475
|
+
const resp = await client.send(new GetParametersCommand({
|
|
2476
|
+
Names: names,
|
|
2477
|
+
WithDecryption: true
|
|
2478
|
+
}));
|
|
2479
|
+
resolved = resp.Parameters ?? [];
|
|
2480
|
+
const invalid = resp.InvalidParameters ?? [];
|
|
2481
|
+
if (invalid.length > 0) logger.warn(`${label}: SSM GetParameters reported invalid parameter name(s): ${invalid.join(", ")}. Ref to the matching CloudFormation parameter(s) will warn-and-drop (was the SSM parameter created?).`);
|
|
2482
|
+
} catch (err) {
|
|
2483
|
+
logger.warn(`${label}: SSM GetParameters(${names.join(", ")}) failed: ${formatSsmError(err)}. Ref to the matching CloudFormation parameter(s) will warn-and-drop (grant ssm:GetParameters or override via --env-vars).`);
|
|
2484
|
+
continue;
|
|
2485
|
+
}
|
|
2486
|
+
for (const p of resolved) {
|
|
2487
|
+
if (typeof p.Name !== "string" || typeof p.Value !== "string") continue;
|
|
2488
|
+
const requesters = byName.get(p.Name);
|
|
2489
|
+
if (!requesters) continue;
|
|
2490
|
+
for (const ref of requesters) out[ref.logicalId] = p.Value;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
return out;
|
|
2494
|
+
}
|
|
2495
|
+
/**
|
|
2496
|
+
* Format an SSM SDK error as `<name>: <message>` so the warn names the
|
|
2497
|
+
* error class (e.g. `AccessDeniedException`, `ThrottlingException`).
|
|
2498
|
+
* Mirrors `formatAwsErrorForWarn` in `cfn-local-state-provider.ts`.
|
|
2499
|
+
* Exported for unit testing.
|
|
2500
|
+
*/
|
|
2501
|
+
function formatSsmError(err) {
|
|
2502
|
+
if (!(err instanceof Error)) return String(err);
|
|
2503
|
+
const name = err.name && err.name !== "Error" ? err.name : void 0;
|
|
2504
|
+
return name !== void 0 ? `${name}: ${err.message}` : err.message;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2357
2507
|
//#endregion
|
|
2358
2508
|
//#region src/local/cfn-local-state-provider.ts
|
|
2359
2509
|
/**
|
|
@@ -2425,6 +2575,7 @@ var CfnLocalStateProvider = class {
|
|
|
2425
2575
|
region;
|
|
2426
2576
|
client;
|
|
2427
2577
|
lambdaClient;
|
|
2578
|
+
ssmClient;
|
|
2428
2579
|
clientOptions;
|
|
2429
2580
|
disposed = false;
|
|
2430
2581
|
constructor(opts) {
|
|
@@ -2449,6 +2600,31 @@ var CfnLocalStateProvider = class {
|
|
|
2449
2600
|
});
|
|
2450
2601
|
return this.lambdaClient;
|
|
2451
2602
|
}
|
|
2603
|
+
getSsmClient() {
|
|
2604
|
+
if (this.disposed) throw new Error("CfnLocalStateProvider used after dispose()");
|
|
2605
|
+
if (!this.ssmClient) this.ssmClient = new SSMClient({
|
|
2606
|
+
region: this.region,
|
|
2607
|
+
...this.clientOptions.profile !== void 0 && { profile: this.clientOptions.profile }
|
|
2608
|
+
});
|
|
2609
|
+
return this.ssmClient;
|
|
2610
|
+
}
|
|
2611
|
+
/**
|
|
2612
|
+
* Resolve the synthesized template's SSM-backed `Parameters`
|
|
2613
|
+
* (`AWS::SSM::Parameter::Value<String>` /
|
|
2614
|
+
* `AWS::SSM::Parameter::Value<List<String>>`) into a
|
|
2615
|
+
* `parameterLogicalId -> value` map via SSM Parameter Store. See
|
|
2616
|
+
* {@link LocalStateProvider.resolveTemplateSsmParameters}.
|
|
2617
|
+
*
|
|
2618
|
+
* Returns an empty map (and opens no SSM client) when the template
|
|
2619
|
+
* declares no SSM-backed parameters. Best-effort: SSM failures are
|
|
2620
|
+
* absorbed inside {@link resolveSsmParameters} with a per-chunk warn.
|
|
2621
|
+
*/
|
|
2622
|
+
async resolveTemplateSsmParameters(template) {
|
|
2623
|
+
if (this.disposed) throw new Error("CfnLocalStateProvider used after dispose()");
|
|
2624
|
+
const refs = collectSsmParameterRefs(template);
|
|
2625
|
+
if (refs.length === 0) return {};
|
|
2626
|
+
return resolveSsmParameters(this.getSsmClient(), refs, this.label);
|
|
2627
|
+
}
|
|
2452
2628
|
/**
|
|
2453
2629
|
* Read a deployed Lambda function's already-resolved
|
|
2454
2630
|
* `Environment.Variables` via `lambda:GetFunctionConfiguration`. See
|
|
@@ -2560,6 +2736,10 @@ var CfnLocalStateProvider = class {
|
|
|
2560
2736
|
this.lambdaClient.destroy();
|
|
2561
2737
|
this.lambdaClient = void 0;
|
|
2562
2738
|
}
|
|
2739
|
+
if (this.ssmClient) {
|
|
2740
|
+
this.ssmClient.destroy();
|
|
2741
|
+
this.ssmClient = void 0;
|
|
2742
|
+
}
|
|
2563
2743
|
}
|
|
2564
2744
|
};
|
|
2565
2745
|
/**
|
|
@@ -3983,14 +4163,19 @@ function resolveRef(arg, context) {
|
|
|
3983
4163
|
};
|
|
3984
4164
|
if (arg.startsWith("AWS::")) return resolvePseudoParameter(arg, context.pseudoParameters);
|
|
3985
4165
|
const resource = context.resources[arg];
|
|
3986
|
-
if (
|
|
3987
|
-
kind: "unresolved",
|
|
3988
|
-
reason: `Ref '${arg}': no record in the state source (was the resource deployed?)`
|
|
3989
|
-
};
|
|
3990
|
-
return {
|
|
4166
|
+
if (resource) return {
|
|
3991
4167
|
kind: "literal",
|
|
3992
4168
|
value: resource.physicalId
|
|
3993
4169
|
};
|
|
4170
|
+
const parameterValue = context.parameters?.[arg];
|
|
4171
|
+
if (parameterValue !== void 0) return {
|
|
4172
|
+
kind: "literal",
|
|
4173
|
+
value: parameterValue
|
|
4174
|
+
};
|
|
4175
|
+
return {
|
|
4176
|
+
kind: "unresolved",
|
|
4177
|
+
reason: `Ref '${arg}': no record in the state source (was the resource deployed, or is it a CloudFormation parameter that could not be resolved — e.g. an SSM-backed AWS::SSM::Parameter::Value parameter whose SSM value was not readable?)`
|
|
4178
|
+
};
|
|
3994
4179
|
}
|
|
3995
4180
|
function resolvePseudoParameter(name, pseudo) {
|
|
3996
4181
|
if (!pseudo) return {
|
|
@@ -5110,6 +5295,7 @@ function buildSubstitutionContextFromImageContext(context) {
|
|
|
5110
5295
|
if (!context?.stateResources) return void 0;
|
|
5111
5296
|
const subContext = { resources: context.stateResources };
|
|
5112
5297
|
if (context.pseudoParameters) subContext.pseudoParameters = { ...context.pseudoParameters };
|
|
5298
|
+
if (context.stateParameters) subContext.parameters = { ...context.stateParameters };
|
|
5113
5299
|
return subContext;
|
|
5114
5300
|
}
|
|
5115
5301
|
/**
|
|
@@ -7139,6 +7325,10 @@ async function localInvokeCommand(target, options, extraStateProviders) {
|
|
|
7139
7325
|
const pseudo = await resolvePseudoParametersForInvoke(lambda.stack.region, options);
|
|
7140
7326
|
if (pseudo) subContext.pseudoParameters = pseudo;
|
|
7141
7327
|
}
|
|
7328
|
+
if (envHasIntrinsicValue$1(templateEnv) && stateProvider.resolveTemplateSsmParameters) {
|
|
7329
|
+
const ssmParams = await stateProvider.resolveTemplateSsmParameters(lambda.stack.template);
|
|
7330
|
+
if (Object.keys(ssmParams).length > 0) subContext.parameters = ssmParams;
|
|
7331
|
+
}
|
|
7142
7332
|
if (envHasCrossStackIntrinsic(templateEnv)) {
|
|
7143
7333
|
const resolver = await stateProvider.buildCrossStackResolver(loaded.region);
|
|
7144
7334
|
if (resolver) subContext.crossStackResolver = resolver;
|
|
@@ -15468,6 +15658,7 @@ async function buildContainerSpec(args) {
|
|
|
15468
15658
|
if (stateBundle) {
|
|
15469
15659
|
const context = { resources: stateBundle.state.resources };
|
|
15470
15660
|
if (stateBundle.pseudoParameters) context.pseudoParameters = stateBundle.pseudoParameters;
|
|
15661
|
+
if (stateBundle.ssmParameters) context.parameters = stateBundle.ssmParameters;
|
|
15471
15662
|
const { env, audit } = substituteEnvVarsFromState(templateEnv, context);
|
|
15472
15663
|
templateEnv = env;
|
|
15473
15664
|
for (const key of audit.resolvedKeys) getLogger().debug(`Lambda ${logicalId}: state source substituted env var ${key}`);
|
|
@@ -16277,6 +16468,10 @@ async function loadStateForRoutedStacks(stacks, routes, routesWithAuth, options,
|
|
|
16277
16468
|
}
|
|
16278
16469
|
if (deployedEnvByLambda.size > 0) bundle.deployedEnvByLambda = deployedEnvByLambda;
|
|
16279
16470
|
}
|
|
16471
|
+
if (stackHasIntrinsicEnv(stackName) && provider.resolveTemplateSsmParameters) {
|
|
16472
|
+
const ssmParameters = await provider.resolveTemplateSsmParameters(stack.template);
|
|
16473
|
+
if (Object.keys(ssmParameters).length > 0) bundle.ssmParameters = ssmParameters;
|
|
16474
|
+
}
|
|
16280
16475
|
out.set(stackName, bundle);
|
|
16281
16476
|
logger.debug(`${provider.label}: loaded state for ${stackName} (${loaded.region})`);
|
|
16282
16477
|
} finally {
|
|
@@ -16402,18 +16597,18 @@ const METADATA_ENDPOINT_IP = "169.254.170.2";
|
|
|
16402
16597
|
const DEFAULT_METADATA_ENDPOINT_SUBNET = "169.254.170.0/24";
|
|
16403
16598
|
/**
|
|
16404
16599
|
* Pure-functional subnet allocator. `cdkl run-task` uses the
|
|
16405
|
-
* default subnet; `cdkl start-service`
|
|
16406
|
-
*
|
|
16407
|
-
*
|
|
16408
|
-
*
|
|
16409
|
-
*
|
|
16410
|
-
* docker's `--subnet`
|
|
16600
|
+
* default subnet (octet 170); `cdkl start-service` uses a SINGLE
|
|
16601
|
+
* shared network at the fixed octet `SHARED_SVC_SUBNET_OCTET = 171`
|
|
16602
|
+
* (one octet up from run-task so the two CLI variants can coexist on
|
|
16603
|
+
* the same host). The link-local 169.254.0.0/16 space is reserved
|
|
16604
|
+
* AWS-wide for cloud metadata so collisions with user workloads are
|
|
16605
|
+
* unlikely, but the fixed /24 still keeps docker's `--subnet`
|
|
16606
|
+
* allocator from rejecting "Pool overlaps".
|
|
16411
16607
|
*
|
|
16412
16608
|
* `subnetOctet` is the second-from-last byte of the network: 170 →
|
|
16413
16609
|
* 169.254.170.0/24 (default), 171 → 169.254.171.0/24, etc. Valid
|
|
16414
|
-
* range is 1..254
|
|
16415
|
-
*
|
|
16416
|
-
* keeps the allocation logic in one place.
|
|
16610
|
+
* range is 1..254 — exported here so callers keep the CIDR /
|
|
16611
|
+
* sidecar-IP derivation in one place.
|
|
16417
16612
|
*/
|
|
16418
16613
|
function buildEndpointSubnet(subnetOctet) {
|
|
16419
16614
|
if (subnetOctet < 1 || subnetOctet > 254 || !Number.isInteger(subnetOctet)) throw new Error(`buildEndpointSubnet: subnetOctet must be an integer in 1..254 (got ${subnetOctet}).`);
|
|
@@ -16445,7 +16640,9 @@ const SHARED_SVC_SUBNET_OCTET = 171;
|
|
|
16445
16640
|
* CLI tears down ONCE at the end of the run.
|
|
16446
16641
|
*/
|
|
16447
16642
|
async function createSharedSvcNetwork(options = {}) {
|
|
16448
|
-
const
|
|
16643
|
+
const prefix = options.prefix ?? getEmbedConfig().resourceNamePrefix;
|
|
16644
|
+
await sweepOrphanedSvcNetworks(prefix);
|
|
16645
|
+
const networkName = `${prefix}-svc-${randomBytes(4).toString("hex")}`;
|
|
16449
16646
|
const { cidr, sidecarIp } = buildEndpointSubnet(171);
|
|
16450
16647
|
return {
|
|
16451
16648
|
networkName,
|
|
@@ -16462,6 +16659,81 @@ async function createSharedSvcNetwork(options = {}) {
|
|
|
16462
16659
|
};
|
|
16463
16660
|
}
|
|
16464
16661
|
/**
|
|
16662
|
+
* Startup orphan sweep for the shared `cdkl start-service` network
|
|
16663
|
+
* (Issue #93, design Option 1). When a `start-service` run is interrupted
|
|
16664
|
+
* before its end-of-run teardown, the shared `<prefix>-svc-<rand>` network
|
|
16665
|
+
* and its `<prefix>-svc-<rand>-metadata` sidecar LEAK. Because
|
|
16666
|
+
* `start-service` pins a single fixed subnet (`SHARED_SVC_SUBNET_OCTET`),
|
|
16667
|
+
* the next run's `docker network create` always fails with
|
|
16668
|
+
* "Pool overlaps with other one on this address space" — a state that,
|
|
16669
|
+
* unlike a port conflict, never self-heals. This sweep detects and removes
|
|
16670
|
+
* leaked `<prefix>-svc-*` networks that have NO live owner before the next
|
|
16671
|
+
* run re-creates the network.
|
|
16672
|
+
*
|
|
16673
|
+
* Classification heuristic: a `<prefix>-svc-*` network is ORPHANED when its
|
|
16674
|
+
* only attached container is its own `<name>-metadata` sidecar (or it has
|
|
16675
|
+
* zero attached containers). A network that still has a non-metadata
|
|
16676
|
+
* (user replica) container attached is a live concurrent run and is LEFT
|
|
16677
|
+
* untouched. Caveat: "only the metadata sidecar attached ⇒ orphan" can
|
|
16678
|
+
* misclassify a network in the sub-second window between sidecar start and
|
|
16679
|
+
* the first replica attach — so a second `start-service` launched in that
|
|
16680
|
+
* window could reclaim a concurrently-starting run's network out from under
|
|
16681
|
+
* it. This is an accepted limitation, not a benign one: start-service pins a
|
|
16682
|
+
* single fixed subnet, so two concurrent same-prefix runs were never
|
|
16683
|
+
* supported (the second run's `docker network create` already fails with
|
|
16684
|
+
* "Pool overlaps"). The sweep therefore cannot regress a
|
|
16685
|
+
* previously-working scenario.
|
|
16686
|
+
*
|
|
16687
|
+
* Resilient by design: a `docker network ls` / `inspect` failure must not
|
|
16688
|
+
* abort the run — it logs at debug and skips, matching the idempotent
|
|
16689
|
+
* teardown style in this file. Returns the list of swept network names.
|
|
16690
|
+
*/
|
|
16691
|
+
async function sweepOrphanedSvcNetworks(prefix) {
|
|
16692
|
+
const logger = getLogger().child("ecs-network");
|
|
16693
|
+
const filter = `${prefix}-svc-`;
|
|
16694
|
+
let names;
|
|
16695
|
+
try {
|
|
16696
|
+
const { stdout } = await execFileAsync$2(getDockerCmd(), [
|
|
16697
|
+
"network",
|
|
16698
|
+
"ls",
|
|
16699
|
+
"--filter",
|
|
16700
|
+
`name=${filter}`,
|
|
16701
|
+
"--format",
|
|
16702
|
+
"{{.Name}}"
|
|
16703
|
+
]);
|
|
16704
|
+
names = stdout.split("\n").map((n) => n.trim()).filter((n) => n.length > 0);
|
|
16705
|
+
} catch (err) {
|
|
16706
|
+
const e = err;
|
|
16707
|
+
logger.debug(`docker network ls (sweep) failed: ${e.stderr || e.message || String(err)}`);
|
|
16708
|
+
return [];
|
|
16709
|
+
}
|
|
16710
|
+
const swept = [];
|
|
16711
|
+
for (const name of names) {
|
|
16712
|
+
let attached;
|
|
16713
|
+
try {
|
|
16714
|
+
const { stdout } = await execFileAsync$2(getDockerCmd(), [
|
|
16715
|
+
"network",
|
|
16716
|
+
"inspect",
|
|
16717
|
+
name,
|
|
16718
|
+
"--format",
|
|
16719
|
+
"{{range .Containers}}{{.Name}} {{end}}"
|
|
16720
|
+
]);
|
|
16721
|
+
attached = stdout.split(/\s+/).map((c) => c.trim()).filter((c) => c.length > 0);
|
|
16722
|
+
} catch (err) {
|
|
16723
|
+
const e = err;
|
|
16724
|
+
logger.debug(`docker network inspect ${name} (sweep) failed: ${e.stderr || e.message || String(err)}`);
|
|
16725
|
+
continue;
|
|
16726
|
+
}
|
|
16727
|
+
const sidecarName = `${name}-metadata`;
|
|
16728
|
+
if (attached.some((c) => c !== sidecarName)) continue;
|
|
16729
|
+
logger.info(`Reclaiming orphaned shared network ${name} (no live owner)...`);
|
|
16730
|
+
await removeContainer(sidecarName);
|
|
16731
|
+
await destroyNetworkOnly(name);
|
|
16732
|
+
swept.push(name);
|
|
16733
|
+
}
|
|
16734
|
+
return swept;
|
|
16735
|
+
}
|
|
16736
|
+
/**
|
|
16465
16737
|
* Internal helper shared by `createTaskNetwork` (per-task) and
|
|
16466
16738
|
* `createSharedSvcNetwork` (per-CLI-run). Creates the docker network,
|
|
16467
16739
|
* pulls the sidecar image, and starts the sidecar at the documented
|
|
@@ -17364,6 +17636,7 @@ async function localRunTaskCommand(target, options, extraStateProviders) {
|
|
|
17364
17636
|
if (resolver) await applyCrossStackResolverToTask(task, {
|
|
17365
17637
|
resources: imageContext?.stateResources ?? {},
|
|
17366
17638
|
...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
|
|
17639
|
+
...imageContext?.stateParameters && { parameters: imageContext.stateParameters },
|
|
17367
17640
|
consumerRegion,
|
|
17368
17641
|
crossStackResolver: resolver
|
|
17369
17642
|
});
|
|
@@ -17501,6 +17774,10 @@ async function buildEcsImageResolutionContext$1(candidate, stateProvider, option
|
|
|
17501
17774
|
if (stateProvider && wantsState) {
|
|
17502
17775
|
const loaded = await stateProvider.load(candidate.stackName, candidate.region);
|
|
17503
17776
|
if (loaded) ctx.stateResources = loaded.resources;
|
|
17777
|
+
if (needs.needsEnvOrSecretSubstitution && stateProvider.resolveTemplateSsmParameters) {
|
|
17778
|
+
const ssmParameters = await stateProvider.resolveTemplateSsmParameters(candidate.template);
|
|
17779
|
+
if (Object.keys(ssmParameters).length > 0) ctx.stateParameters = ssmParameters;
|
|
17780
|
+
}
|
|
17504
17781
|
} 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.");
|
|
17505
17782
|
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).");
|
|
17506
17783
|
return ctx;
|
|
@@ -18840,6 +19117,7 @@ async function runOneTarget(target, runState, stacks, options, discovery, skipPu
|
|
|
18840
19117
|
const subContext = {
|
|
18841
19118
|
resources: imageContext?.stateResources ?? {},
|
|
18842
19119
|
...imageContext?.pseudoParameters && { pseudoParameters: imageContext.pseudoParameters },
|
|
19120
|
+
...imageContext?.stateParameters && { parameters: imageContext.stateParameters },
|
|
18843
19121
|
consumerRegion,
|
|
18844
19122
|
crossStackResolver: resolver
|
|
18845
19123
|
};
|
|
@@ -18918,6 +19196,10 @@ async function assumeTaskRole(roleArn, region) {
|
|
|
18918
19196
|
}
|
|
18919
19197
|
/**
|
|
18920
19198
|
* Build the substitution context the ECS resolver consumes.
|
|
19199
|
+
*
|
|
19200
|
+
* Exported for the site-level binding test that locks the `--from-cfn-stack`
|
|
19201
|
+
* SSM-parameter resolution call (issue #94) into this start-service call site
|
|
19202
|
+
* (mirrors `local-run-task`'s exported helper).
|
|
18921
19203
|
*/
|
|
18922
19204
|
async function buildEcsImageResolutionContext(target, stacks, options, stateProvider) {
|
|
18923
19205
|
const logger = getLogger();
|
|
@@ -18950,6 +19232,10 @@ async function buildEcsImageResolutionContext(target, stacks, options, stateProv
|
|
|
18950
19232
|
if (stateProvider && wantsState) {
|
|
18951
19233
|
const loaded = await stateProvider.load(candidate.stackName, candidate.region);
|
|
18952
19234
|
if (loaded) ctx.stateResources = loaded.resources;
|
|
19235
|
+
if (needs.needsEnvOrSecretSubstitution && stateProvider.resolveTemplateSsmParameters) {
|
|
19236
|
+
const ssmParameters = await stateProvider.resolveTemplateSsmParameters(candidate.template);
|
|
19237
|
+
if (Object.keys(ssmParameters).length > 0) ctx.stateParameters = ssmParameters;
|
|
19238
|
+
}
|
|
18953
19239
|
} 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.");
|
|
18954
19240
|
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.");
|
|
18955
19241
|
return ctx;
|
|
@@ -19122,5 +19408,5 @@ function createLocalListCommand(opts = {}) {
|
|
|
19122
19408
|
}
|
|
19123
19409
|
|
|
19124
19410
|
//#endregion
|
|
19125
|
-
export { materializeLayerFromArn as $, matchRoute as A, parseConnectionsPath as B, evaluateCachedLambdaPolicy as C, buildCorsConfigByApiId as D, applyCorsResponseHeaders as E, HOST_GATEWAY_MIN_VERSION as F, resolveRuntimeCodeMountPath as G, buildDisconnectEvent as H, probeHostGatewaySupport as I, substituteAgainstState as J, resolveRuntimeFileExtension as K, ConnectionRegistry as L, applyAuthorizerOverlay as M, buildHttpApiV2Event as N, buildCorsConfigFromCloudFrontChain as O, buildRestV1Event as P, resolveEnvVars as Q, buildMgmtEndpointEnvUrl as R, computeRequestIdentityHash as S, invokeTokenAuthorizer as T, buildMessageEvent as U, buildConnectEvent as V, createLocalInvokeCommand as W, substituteEnvVarsFromState as X, substituteAgainstStateAsync as Y, substituteEnvVarsFromStateAsync as Z, buildJwksUrlFromIssuer as _,
|
|
19126
|
-
//# sourceMappingURL=local-list-
|
|
19411
|
+
export { materializeLayerFromArn as $, matchRoute as A, parseConnectionsPath as B, evaluateCachedLambdaPolicy as C, buildCorsConfigByApiId as D, applyCorsResponseHeaders as E, HOST_GATEWAY_MIN_VERSION as F, resolveRuntimeCodeMountPath as G, buildDisconnectEvent as H, probeHostGatewaySupport as I, substituteAgainstState as J, resolveRuntimeFileExtension as K, ConnectionRegistry as L, applyAuthorizerOverlay as M, buildHttpApiV2Event as N, buildCorsConfigFromCloudFrontChain as O, buildRestV1Event as P, resolveEnvVars as Q, buildMgmtEndpointEnvUrl as R, computeRequestIdentityHash as S, invokeTokenAuthorizer as T, buildMessageEvent as U, buildConnectEvent as V, createLocalInvokeCommand as W, substituteEnvVarsFromState as X, substituteAgainstStateAsync as Y, substituteEnvVarsFromStateAsync as Z, buildJwksUrlFromIssuer as _, parseSelectionExpressionPath as _t, getContainerNetworkIp as a, isCfnFlagPresent as at, verifyJwtAuthorizer as b, resolveLambdaArnIntrinsic as bt, createAuthorizerCache as c, resolveCfnRegion as ct, availableApiIdentifiers as d, collectSsmParameterRefs as dt, derivePseudoParametersFromRegion as et, filterRoutesByApiIdentifier as f, resolveSsmParameters as ft, buildCognitoJwksUrl as g, discoverWebSocketApisOrThrow as gt, resolveServiceIntegrationParameters as h, discoverWebSocketApis as ht, CloudMapRegistry as i, createLocalStateProvider as it, translateLambdaResponse as j, matchPreflight as k, attachStageContext as l, resolveCfnStackName as lt, resolveSelectionExpression as m, listTargets as mt, formatTargetListing as n, tryResolveImageFnJoin as nt, createLocalRunTaskCommand as o, rejectExplicitCfnStackWithMultipleStacks as ot, groupRoutesByServer as p, countTargets as pt, resolveRuntimeImage as q, createLocalStartServiceCommand as r, LocalStateSourceError as rt, createLocalStartApiCommand as s, resolveCfnFallbackRegion as st, createLocalListCommand as t, substituteImagePlaceholders as tt, buildStageMap as u, CfnLocalStateProvider as ut, createJwksCache as v, discoverRoutes as vt, invokeRequestAuthorizer as w, buildMethodArn as x, verifyCognitoJwt as y, pickRefLogicalId as yt, handleConnectionsRequest as z };
|
|
19412
|
+
//# sourceMappingURL=local-list-yvjjjkSy.js.map
|