@go-to-k/cdkd 0.91.1 → 0.91.3
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 +1 -1
- package/dist/cli.js +782 -475
- package/dist/cli.js.map +3 -3
- package/dist/go-to-k-cdkd-0.91.3.tgz +0 -0
- package/package.json +1 -1
- package/dist/go-to-k-cdkd-0.91.1.tgz +0 -0
package/dist/cli.js
CHANGED
|
@@ -47837,22 +47837,32 @@ var ECSProvider = class {
|
|
|
47837
47837
|
essential: def["Essential"],
|
|
47838
47838
|
command: def["Command"],
|
|
47839
47839
|
entryPoint: def["EntryPoint"],
|
|
47840
|
-
environment:
|
|
47841
|
-
|
|
47842
|
-
|
|
47840
|
+
environment: this.convertEnvironment(
|
|
47841
|
+
def["Environment"]
|
|
47842
|
+
),
|
|
47843
|
+
environmentFiles: this.convertEnvironmentFiles(
|
|
47844
|
+
def["EnvironmentFiles"]
|
|
47845
|
+
),
|
|
47846
|
+
secrets: this.convertSecrets(def["Secrets"]),
|
|
47843
47847
|
portMappings: this.convertPortMappings(
|
|
47844
47848
|
def["PortMappings"]
|
|
47845
47849
|
),
|
|
47846
|
-
mountPoints:
|
|
47847
|
-
|
|
47848
|
-
|
|
47850
|
+
mountPoints: this.convertMountPoints(
|
|
47851
|
+
def["MountPoints"]
|
|
47852
|
+
),
|
|
47853
|
+
volumesFrom: this.convertVolumesFrom(
|
|
47854
|
+
def["VolumesFrom"]
|
|
47855
|
+
),
|
|
47856
|
+
dependsOn: this.convertDependsOn(
|
|
47857
|
+
def["DependsOn"]
|
|
47858
|
+
),
|
|
47849
47859
|
links: def["Links"],
|
|
47850
47860
|
workingDirectory: def["WorkingDirectory"],
|
|
47851
47861
|
disableNetworking: def["DisableNetworking"],
|
|
47852
47862
|
privileged: def["Privileged"],
|
|
47853
47863
|
readonlyRootFilesystem: def["ReadonlyRootFilesystem"],
|
|
47854
47864
|
user: def["User"],
|
|
47855
|
-
ulimits: def["Ulimits"],
|
|
47865
|
+
ulimits: this.convertUlimits(def["Ulimits"]),
|
|
47856
47866
|
logConfiguration: this.convertLogConfiguration(
|
|
47857
47867
|
def["LogConfiguration"]
|
|
47858
47868
|
),
|
|
@@ -47881,6 +47891,103 @@ var ECSProvider = class {
|
|
|
47881
47891
|
name: m["Name"]
|
|
47882
47892
|
}));
|
|
47883
47893
|
}
|
|
47894
|
+
/**
|
|
47895
|
+
* Convert CFn Environment (KeyValuePair) to ECS SDK format.
|
|
47896
|
+
* CFn template emits `{Name, Value}` (PascalCase); ECS SDK requires
|
|
47897
|
+
* `{name, value}` (camelCase). Pre-fix the cast `as KeyValuePair[]`
|
|
47898
|
+
* silently dropped both fields and AWS rejected RegisterTaskDefinition
|
|
47899
|
+
* with a generic null/empty validation error.
|
|
47900
|
+
*/
|
|
47901
|
+
convertEnvironment(entries) {
|
|
47902
|
+
if (!entries)
|
|
47903
|
+
return void 0;
|
|
47904
|
+
return entries.map((e) => ({
|
|
47905
|
+
name: e["Name"],
|
|
47906
|
+
value: e["Value"]
|
|
47907
|
+
}));
|
|
47908
|
+
}
|
|
47909
|
+
/**
|
|
47910
|
+
* Convert CFn EnvironmentFiles (S3-backed env-var files) to ECS SDK format.
|
|
47911
|
+
* CFn: `{Type, Value}` → SDK: `{type, value}`.
|
|
47912
|
+
*/
|
|
47913
|
+
convertEnvironmentFiles(entries) {
|
|
47914
|
+
if (!entries)
|
|
47915
|
+
return void 0;
|
|
47916
|
+
return entries.map((e) => ({
|
|
47917
|
+
type: e["Type"],
|
|
47918
|
+
value: e["Value"]
|
|
47919
|
+
}));
|
|
47920
|
+
}
|
|
47921
|
+
/**
|
|
47922
|
+
* Convert CFn Secrets to ECS SDK format.
|
|
47923
|
+
* CFn: `{Name, ValueFrom}` → SDK: `{name, valueFrom}`.
|
|
47924
|
+
* Same PascalCase→camelCase trap as convertEnvironment — discovered
|
|
47925
|
+
* end-to-end via the local-run-task-from-state integ on 2026-05-12
|
|
47926
|
+
* (issue #291 fixture).
|
|
47927
|
+
*/
|
|
47928
|
+
convertSecrets(entries) {
|
|
47929
|
+
if (!entries)
|
|
47930
|
+
return void 0;
|
|
47931
|
+
return entries.map((e) => ({
|
|
47932
|
+
name: e["Name"],
|
|
47933
|
+
valueFrom: e["ValueFrom"]
|
|
47934
|
+
}));
|
|
47935
|
+
}
|
|
47936
|
+
/**
|
|
47937
|
+
* Convert CFn MountPoints to ECS SDK format.
|
|
47938
|
+
* CFn: `{SourceVolume, ContainerPath, ReadOnly}` → SDK: `{sourceVolume,
|
|
47939
|
+
* containerPath, readOnly}`.
|
|
47940
|
+
*/
|
|
47941
|
+
convertMountPoints(entries) {
|
|
47942
|
+
if (!entries)
|
|
47943
|
+
return void 0;
|
|
47944
|
+
return entries.map((e) => ({
|
|
47945
|
+
sourceVolume: e["SourceVolume"],
|
|
47946
|
+
containerPath: e["ContainerPath"],
|
|
47947
|
+
readOnly: e["ReadOnly"]
|
|
47948
|
+
}));
|
|
47949
|
+
}
|
|
47950
|
+
/**
|
|
47951
|
+
* Convert CFn VolumesFrom to ECS SDK format.
|
|
47952
|
+
* CFn: `{SourceContainer, ReadOnly}` → SDK: `{sourceContainer, readOnly}`.
|
|
47953
|
+
*/
|
|
47954
|
+
convertVolumesFrom(entries) {
|
|
47955
|
+
if (!entries)
|
|
47956
|
+
return void 0;
|
|
47957
|
+
return entries.map((e) => ({
|
|
47958
|
+
sourceContainer: e["SourceContainer"],
|
|
47959
|
+
readOnly: e["ReadOnly"]
|
|
47960
|
+
}));
|
|
47961
|
+
}
|
|
47962
|
+
/**
|
|
47963
|
+
* Convert CFn DependsOn to ECS SDK format.
|
|
47964
|
+
* CFn: `{ContainerName, Condition}` → SDK: `{containerName, condition}`.
|
|
47965
|
+
* The pre-existing local-run-task-multi-container integ was relying
|
|
47966
|
+
* on ECS SDK being lenient about the dependsOn key casing on input,
|
|
47967
|
+
* but per the SDK type definition the input is camelCase so this
|
|
47968
|
+
* converter brings the actual wire shape in line with the contract.
|
|
47969
|
+
*/
|
|
47970
|
+
convertDependsOn(entries) {
|
|
47971
|
+
if (!entries)
|
|
47972
|
+
return void 0;
|
|
47973
|
+
return entries.map((e) => ({
|
|
47974
|
+
containerName: e["ContainerName"],
|
|
47975
|
+
condition: e["Condition"]
|
|
47976
|
+
}));
|
|
47977
|
+
}
|
|
47978
|
+
/**
|
|
47979
|
+
* Convert CFn Ulimits to ECS SDK format.
|
|
47980
|
+
* CFn: `{Name, SoftLimit, HardLimit}` → SDK: `{name, softLimit, hardLimit}`.
|
|
47981
|
+
*/
|
|
47982
|
+
convertUlimits(entries) {
|
|
47983
|
+
if (!entries)
|
|
47984
|
+
return void 0;
|
|
47985
|
+
return entries.map((e) => ({
|
|
47986
|
+
name: e["Name"],
|
|
47987
|
+
softLimit: e["SoftLimit"],
|
|
47988
|
+
hardLimit: e["HardLimit"]
|
|
47989
|
+
}));
|
|
47990
|
+
}
|
|
47884
47991
|
/**
|
|
47885
47992
|
* Convert CFn LogConfiguration to ECS SDK format
|
|
47886
47993
|
*/
|
|
@@ -70168,167 +70275,447 @@ async function loadStateForStack(stackName, synthRegion, opts) {
|
|
|
70168
70275
|
// src/local/lambda-resolver.ts
|
|
70169
70276
|
import { existsSync as existsSync4, statSync as statSync3 } from "node:fs";
|
|
70170
70277
|
import { dirname, isAbsolute, resolve as resolve4 } from "node:path";
|
|
70171
|
-
|
|
70172
|
-
|
|
70173
|
-
|
|
70174
|
-
|
|
70175
|
-
|
|
70176
|
-
|
|
70177
|
-
|
|
70178
|
-
|
|
70179
|
-
|
|
70180
|
-
|
|
70181
|
-
|
|
70182
|
-
);
|
|
70183
|
-
}
|
|
70184
|
-
const colonIdx = target.indexOf(":");
|
|
70185
|
-
const slashIdx = target.indexOf("/");
|
|
70186
|
-
if (colonIdx > 0 && (slashIdx === -1 || colonIdx < slashIdx)) {
|
|
70187
|
-
const stackPattern = target.substring(0, colonIdx);
|
|
70188
|
-
const pathOrId = target.substring(colonIdx + 1);
|
|
70189
|
-
if (pathOrId.length === 0) {
|
|
70190
|
-
throw new LocalInvokeResolutionError(`Target '${target}' has no logical ID after ':'.`);
|
|
70191
|
-
}
|
|
70192
|
-
return { stackPattern, pathOrId, isPath: pathOrId.includes("/") };
|
|
70193
|
-
}
|
|
70194
|
-
if (slashIdx > 0) {
|
|
70195
|
-
return { stackPattern: target.substring(0, slashIdx), pathOrId: target, isPath: true };
|
|
70278
|
+
|
|
70279
|
+
// src/local/intrinsic-image.ts
|
|
70280
|
+
function tryResolveImageFnJoin(raw, resources, context) {
|
|
70281
|
+
if (!raw || typeof raw !== "object")
|
|
70282
|
+
return { kind: "not-applicable" };
|
|
70283
|
+
const obj = raw;
|
|
70284
|
+
const arg = obj["Fn::Join"];
|
|
70285
|
+
if (arg === void 0)
|
|
70286
|
+
return { kind: "not-applicable" };
|
|
70287
|
+
if (!Array.isArray(arg) || arg.length !== 2 || !Array.isArray(arg[1])) {
|
|
70288
|
+
return { kind: "unsupported-join", reason: "Fn::Join must be [delimiter, [elements]]" };
|
|
70196
70289
|
}
|
|
70197
|
-
|
|
70198
|
-
|
|
70199
|
-
|
|
70200
|
-
|
|
70201
|
-
|
|
70290
|
+
const [delimiter, elements] = arg;
|
|
70291
|
+
if (typeof delimiter !== "string") {
|
|
70292
|
+
return {
|
|
70293
|
+
kind: "unsupported-join",
|
|
70294
|
+
reason: `Fn::Join delimiter must be a string, got ${typeof delimiter}`
|
|
70295
|
+
};
|
|
70202
70296
|
}
|
|
70203
|
-
const
|
|
70204
|
-
const
|
|
70205
|
-
|
|
70206
|
-
|
|
70207
|
-
let match;
|
|
70208
|
-
if (parsed.isPath) {
|
|
70209
|
-
const index = buildCdkPathIndex(template);
|
|
70210
|
-
const resolvedPaths = resolveCdkPathToLogicalIds(parsed.pathOrId, index);
|
|
70211
|
-
const lambdaMatches = resolvedPaths.filter(
|
|
70212
|
-
({ logicalId: logicalId2 }) => resources[logicalId2]?.Type === "AWS::Lambda::Function"
|
|
70213
|
-
);
|
|
70214
|
-
if (lambdaMatches.length === 0) {
|
|
70215
|
-
throw notFoundError(target, stack, resources);
|
|
70216
|
-
}
|
|
70217
|
-
if (lambdaMatches.length > 1) {
|
|
70218
|
-
throw new LocalInvokeResolutionError(
|
|
70219
|
-
`Target '${target}' matches ${lambdaMatches.length} Lambda functions in ${stack.stackName}: ` + lambdaMatches.map((m2) => m2.logicalId).join(", ") + ". Refine the path or use the stack:LogicalId form."
|
|
70220
|
-
);
|
|
70221
|
-
}
|
|
70222
|
-
const m = lambdaMatches[0];
|
|
70223
|
-
match = { logicalId: m.logicalId, resource: resources[m.logicalId] };
|
|
70224
|
-
} else {
|
|
70225
|
-
const resource2 = resources[parsed.pathOrId];
|
|
70226
|
-
if (!resource2) {
|
|
70227
|
-
throw notFoundError(target, stack, resources);
|
|
70228
|
-
}
|
|
70229
|
-
match = { logicalId: parsed.pathOrId, resource: resource2 };
|
|
70297
|
+
const repoLogicalId = findEcrRepositoryRefInTree(elements, resources);
|
|
70298
|
+
const stateResources = context?.stateResources;
|
|
70299
|
+
if (repoLogicalId && !stateResources) {
|
|
70300
|
+
return { kind: "needs-state", repoLogicalId };
|
|
70230
70301
|
}
|
|
70231
|
-
const
|
|
70232
|
-
|
|
70233
|
-
|
|
70234
|
-
|
|
70235
|
-
|
|
70236
|
-
|
|
70302
|
+
const parts = [];
|
|
70303
|
+
for (const element of elements) {
|
|
70304
|
+
const r = resolveImageIntrinsic(element, resources, context);
|
|
70305
|
+
if (r === void 0) {
|
|
70306
|
+
if (!repoLogicalId)
|
|
70307
|
+
return { kind: "not-applicable" };
|
|
70308
|
+
return {
|
|
70309
|
+
kind: "unsupported-join",
|
|
70310
|
+
reason: "one or more Fn::Join elements could not be resolved"
|
|
70311
|
+
};
|
|
70237
70312
|
}
|
|
70238
|
-
|
|
70239
|
-
`Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not a Lambda function. cdkd local invoke only works on AWS::Lambda::Function resources in v1.`
|
|
70240
|
-
);
|
|
70313
|
+
parts.push(r);
|
|
70241
70314
|
}
|
|
70242
|
-
return
|
|
70315
|
+
return { kind: "resolved", uri: parts.join(delimiter) };
|
|
70243
70316
|
}
|
|
70244
|
-
function
|
|
70245
|
-
if (
|
|
70246
|
-
|
|
70247
|
-
|
|
70248
|
-
|
|
70249
|
-
`Multiple stacks in app, target '${parsed.pathOrId}' is missing a stack prefix. Use 'StackName:${parsed.pathOrId}' or 'StackName/...' (path form). Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`
|
|
70250
|
-
);
|
|
70251
|
-
}
|
|
70252
|
-
const matched = matchStacks(stacks, [parsed.stackPattern]);
|
|
70253
|
-
if (matched.length === 0) {
|
|
70254
|
-
throw new LocalInvokeResolutionError(
|
|
70255
|
-
`Stack '${parsed.stackPattern}' not found. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`
|
|
70256
|
-
);
|
|
70257
|
-
}
|
|
70258
|
-
if (matched.length > 1) {
|
|
70259
|
-
throw new LocalInvokeResolutionError(
|
|
70260
|
-
`Stack pattern '${parsed.stackPattern}' matched ${matched.length} stacks: ` + matched.map((s) => s.stackName).join(", ") + ". Use a more specific pattern."
|
|
70261
|
-
);
|
|
70317
|
+
function findEcrRepositoryRefInTree(node, resources) {
|
|
70318
|
+
if (node === null || node === void 0)
|
|
70319
|
+
return void 0;
|
|
70320
|
+
if (typeof node === "string" || typeof node === "number" || typeof node === "boolean") {
|
|
70321
|
+
return void 0;
|
|
70262
70322
|
}
|
|
70263
|
-
|
|
70264
|
-
|
|
70265
|
-
|
|
70266
|
-
|
|
70267
|
-
|
|
70268
|
-
|
|
70269
|
-
|
|
70270
|
-
const imageUri = extractImageUri(code["ImageUri"]);
|
|
70271
|
-
if (imageUri !== void 0) {
|
|
70272
|
-
return extractImageLambdaProperties({
|
|
70273
|
-
stack,
|
|
70274
|
-
logicalId,
|
|
70275
|
-
resource,
|
|
70276
|
-
memoryMb,
|
|
70277
|
-
timeoutSec,
|
|
70278
|
-
props,
|
|
70279
|
-
imageUri
|
|
70280
|
-
});
|
|
70323
|
+
if (Array.isArray(node)) {
|
|
70324
|
+
for (const item of node) {
|
|
70325
|
+
const hit = findEcrRepositoryRefInTree(item, resources);
|
|
70326
|
+
if (hit)
|
|
70327
|
+
return hit;
|
|
70328
|
+
}
|
|
70329
|
+
return void 0;
|
|
70281
70330
|
}
|
|
70282
|
-
|
|
70283
|
-
|
|
70284
|
-
|
|
70285
|
-
|
|
70286
|
-
|
|
70287
|
-
)
|
|
70331
|
+
if (typeof node !== "object")
|
|
70332
|
+
return void 0;
|
|
70333
|
+
const obj = node;
|
|
70334
|
+
if (typeof obj["Ref"] === "string") {
|
|
70335
|
+
const target = obj["Ref"];
|
|
70336
|
+
if (resources[target]?.Type === "AWS::ECR::Repository")
|
|
70337
|
+
return target;
|
|
70338
|
+
return void 0;
|
|
70288
70339
|
}
|
|
70289
|
-
|
|
70290
|
-
|
|
70340
|
+
const getAtt = obj["Fn::GetAtt"];
|
|
70341
|
+
if (getAtt !== void 0) {
|
|
70342
|
+
let lid;
|
|
70343
|
+
if (Array.isArray(getAtt) && typeof getAtt[0] === "string")
|
|
70344
|
+
lid = getAtt[0];
|
|
70345
|
+
else if (typeof getAtt === "string")
|
|
70346
|
+
lid = getAtt.split(".")[0];
|
|
70347
|
+
if (lid && resources[lid]?.Type === "AWS::ECR::Repository")
|
|
70348
|
+
return lid;
|
|
70349
|
+
return void 0;
|
|
70291
70350
|
}
|
|
70292
|
-
const
|
|
70293
|
-
|
|
70294
|
-
|
|
70295
|
-
|
|
70351
|
+
for (const value of Object.values(obj)) {
|
|
70352
|
+
const hit = findEcrRepositoryRefInTree(value, resources);
|
|
70353
|
+
if (hit)
|
|
70354
|
+
return hit;
|
|
70296
70355
|
}
|
|
70297
|
-
|
|
70298
|
-
return {
|
|
70299
|
-
kind: "zip",
|
|
70300
|
-
stack,
|
|
70301
|
-
logicalId,
|
|
70302
|
-
resource,
|
|
70303
|
-
runtime,
|
|
70304
|
-
handler,
|
|
70305
|
-
memoryMb,
|
|
70306
|
-
timeoutSec,
|
|
70307
|
-
codePath,
|
|
70308
|
-
layers,
|
|
70309
|
-
...inlineCode !== void 0 && { inlineCode }
|
|
70310
|
-
};
|
|
70356
|
+
return void 0;
|
|
70311
70357
|
}
|
|
70312
|
-
function
|
|
70313
|
-
|
|
70314
|
-
|
|
70315
|
-
|
|
70316
|
-
|
|
70317
|
-
|
|
70318
|
-
if (typeof sub === "string" && sub.length > 0)
|
|
70319
|
-
return sub;
|
|
70320
|
-
if (Array.isArray(sub) && typeof sub[0] === "string")
|
|
70321
|
-
return sub[0];
|
|
70322
|
-
}
|
|
70358
|
+
function resolveImageIntrinsic(node, resources, context) {
|
|
70359
|
+
const v = resolveImageIntrinsicAny(node, resources, context);
|
|
70360
|
+
if (typeof v === "string")
|
|
70361
|
+
return v;
|
|
70362
|
+
if (typeof v === "number" || typeof v === "boolean")
|
|
70363
|
+
return String(v);
|
|
70323
70364
|
return void 0;
|
|
70324
70365
|
}
|
|
70325
|
-
function
|
|
70326
|
-
|
|
70327
|
-
|
|
70328
|
-
|
|
70329
|
-
|
|
70330
|
-
|
|
70331
|
-
|
|
70366
|
+
function resolveImageIntrinsicAny(node, resources, context) {
|
|
70367
|
+
if (node === null || node === void 0)
|
|
70368
|
+
return void 0;
|
|
70369
|
+
if (typeof node === "string" || typeof node === "number" || typeof node === "boolean") {
|
|
70370
|
+
return node;
|
|
70371
|
+
}
|
|
70372
|
+
if (Array.isArray(node)) {
|
|
70373
|
+
return void 0;
|
|
70374
|
+
}
|
|
70375
|
+
if (typeof node !== "object")
|
|
70376
|
+
return void 0;
|
|
70377
|
+
const obj = node;
|
|
70378
|
+
const keys = Object.keys(obj);
|
|
70379
|
+
if (keys.length !== 1)
|
|
70380
|
+
return void 0;
|
|
70381
|
+
const intrinsic = keys[0];
|
|
70382
|
+
const arg = obj[intrinsic];
|
|
70383
|
+
if (intrinsic === "Ref") {
|
|
70384
|
+
if (typeof arg !== "string")
|
|
70385
|
+
return void 0;
|
|
70386
|
+
if (arg.startsWith("AWS::")) {
|
|
70387
|
+
const p = context?.pseudoParameters;
|
|
70388
|
+
if (!p)
|
|
70389
|
+
return void 0;
|
|
70390
|
+
if (arg === "AWS::URLSuffix")
|
|
70391
|
+
return p.urlSuffix;
|
|
70392
|
+
if (arg === "AWS::Partition")
|
|
70393
|
+
return p.partition;
|
|
70394
|
+
if (arg === "AWS::Region")
|
|
70395
|
+
return p.region;
|
|
70396
|
+
if (arg === "AWS::AccountId")
|
|
70397
|
+
return p.accountId;
|
|
70398
|
+
return void 0;
|
|
70399
|
+
}
|
|
70400
|
+
const refResource = resources[arg];
|
|
70401
|
+
if (refResource?.Type !== "AWS::ECR::Repository")
|
|
70402
|
+
return void 0;
|
|
70403
|
+
const stateEntry = context?.stateResources?.[arg];
|
|
70404
|
+
if (!stateEntry)
|
|
70405
|
+
return void 0;
|
|
70406
|
+
return stateEntry.physicalId;
|
|
70407
|
+
}
|
|
70408
|
+
if (intrinsic === "Fn::GetAtt") {
|
|
70409
|
+
let logicalId;
|
|
70410
|
+
let attr;
|
|
70411
|
+
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && typeof arg[1] === "string") {
|
|
70412
|
+
logicalId = arg[0];
|
|
70413
|
+
attr = arg[1];
|
|
70414
|
+
} else if (typeof arg === "string") {
|
|
70415
|
+
const dot = arg.indexOf(".");
|
|
70416
|
+
if (dot > 0 && dot < arg.length - 1) {
|
|
70417
|
+
logicalId = arg.slice(0, dot);
|
|
70418
|
+
attr = arg.slice(dot + 1);
|
|
70419
|
+
}
|
|
70420
|
+
}
|
|
70421
|
+
if (!logicalId || !attr)
|
|
70422
|
+
return void 0;
|
|
70423
|
+
if (resources[logicalId]?.Type !== "AWS::ECR::Repository")
|
|
70424
|
+
return void 0;
|
|
70425
|
+
const cached = context?.stateResources?.[logicalId]?.attributes?.[attr];
|
|
70426
|
+
if (typeof cached === "string" && cached.length > 0)
|
|
70427
|
+
return cached;
|
|
70428
|
+
return void 0;
|
|
70429
|
+
}
|
|
70430
|
+
if (intrinsic === "Fn::Split") {
|
|
70431
|
+
if (!Array.isArray(arg) || arg.length !== 2)
|
|
70432
|
+
return void 0;
|
|
70433
|
+
const argArr = arg;
|
|
70434
|
+
const delim = argArr[0];
|
|
70435
|
+
if (typeof delim !== "string")
|
|
70436
|
+
return void 0;
|
|
70437
|
+
const src = resolveImageIntrinsicAny(argArr[1], resources, context);
|
|
70438
|
+
if (typeof src !== "string")
|
|
70439
|
+
return void 0;
|
|
70440
|
+
return src.split(delim);
|
|
70441
|
+
}
|
|
70442
|
+
if (intrinsic === "Fn::Select") {
|
|
70443
|
+
if (!Array.isArray(arg) || arg.length !== 2)
|
|
70444
|
+
return void 0;
|
|
70445
|
+
const argArr = arg;
|
|
70446
|
+
const rawIndex = argArr[0];
|
|
70447
|
+
let index;
|
|
70448
|
+
if (typeof rawIndex === "number") {
|
|
70449
|
+
index = rawIndex;
|
|
70450
|
+
} else if (typeof rawIndex === "string" && /^-?\d+$/.test(rawIndex)) {
|
|
70451
|
+
index = Number.parseInt(rawIndex, 10);
|
|
70452
|
+
}
|
|
70453
|
+
if (index === void 0 || !Number.isFinite(index))
|
|
70454
|
+
return void 0;
|
|
70455
|
+
const list = resolveImageIntrinsicAny(argArr[1], resources, context);
|
|
70456
|
+
if (Array.isArray(list)) {
|
|
70457
|
+
if (index < 0 || index >= list.length)
|
|
70458
|
+
return void 0;
|
|
70459
|
+
const picked = list[index];
|
|
70460
|
+
if (typeof picked === "string")
|
|
70461
|
+
return picked;
|
|
70462
|
+
return void 0;
|
|
70463
|
+
}
|
|
70464
|
+
if (Array.isArray(argArr[1])) {
|
|
70465
|
+
const listLiteral = argArr[1];
|
|
70466
|
+
if (index < 0 || index >= listLiteral.length)
|
|
70467
|
+
return void 0;
|
|
70468
|
+
return resolveImageIntrinsic(listLiteral[index], resources, context);
|
|
70469
|
+
}
|
|
70470
|
+
return void 0;
|
|
70471
|
+
}
|
|
70472
|
+
if (intrinsic === "Fn::Join") {
|
|
70473
|
+
if (!Array.isArray(arg) || arg.length !== 2)
|
|
70474
|
+
return void 0;
|
|
70475
|
+
const [delim, parts] = arg;
|
|
70476
|
+
if (typeof delim !== "string" || !Array.isArray(parts))
|
|
70477
|
+
return void 0;
|
|
70478
|
+
const resolved = [];
|
|
70479
|
+
for (const part of parts) {
|
|
70480
|
+
const r = resolveImageIntrinsic(part, resources, context);
|
|
70481
|
+
if (r === void 0)
|
|
70482
|
+
return void 0;
|
|
70483
|
+
resolved.push(r);
|
|
70484
|
+
}
|
|
70485
|
+
return resolved.join(delim);
|
|
70486
|
+
}
|
|
70487
|
+
if (intrinsic === "Fn::Sub") {
|
|
70488
|
+
let template;
|
|
70489
|
+
if (typeof arg === "string")
|
|
70490
|
+
template = arg;
|
|
70491
|
+
else if (Array.isArray(arg) && typeof arg[0] === "string")
|
|
70492
|
+
template = arg[0];
|
|
70493
|
+
if (template === void 0)
|
|
70494
|
+
return void 0;
|
|
70495
|
+
const out = substituteImagePlaceholders(template, resources, context);
|
|
70496
|
+
if (out.includes("${"))
|
|
70497
|
+
return void 0;
|
|
70498
|
+
return out;
|
|
70499
|
+
}
|
|
70500
|
+
return void 0;
|
|
70501
|
+
}
|
|
70502
|
+
function substituteImagePlaceholders(flat, resources, context) {
|
|
70503
|
+
if (!flat.includes("${"))
|
|
70504
|
+
return flat;
|
|
70505
|
+
return flat.replace(/\$\{([^}]+)\}/g, (full, key) => {
|
|
70506
|
+
if (context?.pseudoParameters) {
|
|
70507
|
+
if (key === "AWS::AccountId" && context.pseudoParameters.accountId) {
|
|
70508
|
+
return context.pseudoParameters.accountId;
|
|
70509
|
+
}
|
|
70510
|
+
if (key === "AWS::Region" && context.pseudoParameters.region) {
|
|
70511
|
+
return context.pseudoParameters.region;
|
|
70512
|
+
}
|
|
70513
|
+
if (key === "AWS::Partition" && context.pseudoParameters.partition) {
|
|
70514
|
+
return context.pseudoParameters.partition;
|
|
70515
|
+
}
|
|
70516
|
+
if (key === "AWS::URLSuffix" && context.pseudoParameters.urlSuffix) {
|
|
70517
|
+
return context.pseudoParameters.urlSuffix;
|
|
70518
|
+
}
|
|
70519
|
+
}
|
|
70520
|
+
if (context?.stateResources) {
|
|
70521
|
+
const dot = key.indexOf(".");
|
|
70522
|
+
const logicalId = dot === -1 ? key : key.slice(0, dot);
|
|
70523
|
+
const refResource = resources[logicalId];
|
|
70524
|
+
const stateEntry = context.stateResources[logicalId];
|
|
70525
|
+
if (refResource?.Type === "AWS::ECR::Repository" && stateEntry) {
|
|
70526
|
+
if (dot === -1) {
|
|
70527
|
+
return stateEntry.physicalId;
|
|
70528
|
+
}
|
|
70529
|
+
const attr = key.slice(dot + 1);
|
|
70530
|
+
const cached = stateEntry.attributes?.[attr];
|
|
70531
|
+
if (typeof cached === "string")
|
|
70532
|
+
return cached;
|
|
70533
|
+
}
|
|
70534
|
+
}
|
|
70535
|
+
return full;
|
|
70536
|
+
});
|
|
70537
|
+
}
|
|
70538
|
+
|
|
70539
|
+
// src/local/lambda-resolver.ts
|
|
70540
|
+
var LocalInvokeResolutionError = class _LocalInvokeResolutionError extends Error {
|
|
70541
|
+
constructor(message) {
|
|
70542
|
+
super(message);
|
|
70543
|
+
this.name = "LocalInvokeResolutionError";
|
|
70544
|
+
Object.setPrototypeOf(this, _LocalInvokeResolutionError.prototype);
|
|
70545
|
+
}
|
|
70546
|
+
};
|
|
70547
|
+
function parseTarget(target) {
|
|
70548
|
+
if (typeof target !== "string" || target.length === 0) {
|
|
70549
|
+
throw new LocalInvokeResolutionError(
|
|
70550
|
+
"Empty target. Pass a CDK display path (e.g. 'MyStack/MyApi/Handler') or stack-qualified logical ID (e.g. 'MyStack:MyApiHandler1234ABCD')."
|
|
70551
|
+
);
|
|
70552
|
+
}
|
|
70553
|
+
const colonIdx = target.indexOf(":");
|
|
70554
|
+
const slashIdx = target.indexOf("/");
|
|
70555
|
+
if (colonIdx > 0 && (slashIdx === -1 || colonIdx < slashIdx)) {
|
|
70556
|
+
const stackPattern = target.substring(0, colonIdx);
|
|
70557
|
+
const pathOrId = target.substring(colonIdx + 1);
|
|
70558
|
+
if (pathOrId.length === 0) {
|
|
70559
|
+
throw new LocalInvokeResolutionError(`Target '${target}' has no logical ID after ':'.`);
|
|
70560
|
+
}
|
|
70561
|
+
return { stackPattern, pathOrId, isPath: pathOrId.includes("/") };
|
|
70562
|
+
}
|
|
70563
|
+
if (slashIdx > 0) {
|
|
70564
|
+
return { stackPattern: target.substring(0, slashIdx), pathOrId: target, isPath: true };
|
|
70565
|
+
}
|
|
70566
|
+
return { stackPattern: null, pathOrId: target, isPath: false };
|
|
70567
|
+
}
|
|
70568
|
+
function resolveLambdaTarget(target, stacks) {
|
|
70569
|
+
if (stacks.length === 0) {
|
|
70570
|
+
throw new LocalInvokeResolutionError("No stacks found in the synthesized assembly.");
|
|
70571
|
+
}
|
|
70572
|
+
const parsed = parseTarget(target);
|
|
70573
|
+
const stack = pickStack(parsed, stacks);
|
|
70574
|
+
const template = stack.template;
|
|
70575
|
+
const resources = template.Resources ?? {};
|
|
70576
|
+
let match;
|
|
70577
|
+
if (parsed.isPath) {
|
|
70578
|
+
const index = buildCdkPathIndex(template);
|
|
70579
|
+
const resolvedPaths = resolveCdkPathToLogicalIds(parsed.pathOrId, index);
|
|
70580
|
+
const lambdaMatches = resolvedPaths.filter(
|
|
70581
|
+
({ logicalId: logicalId2 }) => resources[logicalId2]?.Type === "AWS::Lambda::Function"
|
|
70582
|
+
);
|
|
70583
|
+
if (lambdaMatches.length === 0) {
|
|
70584
|
+
throw notFoundError(target, stack, resources);
|
|
70585
|
+
}
|
|
70586
|
+
if (lambdaMatches.length > 1) {
|
|
70587
|
+
throw new LocalInvokeResolutionError(
|
|
70588
|
+
`Target '${target}' matches ${lambdaMatches.length} Lambda functions in ${stack.stackName}: ` + lambdaMatches.map((m2) => m2.logicalId).join(", ") + ". Refine the path or use the stack:LogicalId form."
|
|
70589
|
+
);
|
|
70590
|
+
}
|
|
70591
|
+
const m = lambdaMatches[0];
|
|
70592
|
+
match = { logicalId: m.logicalId, resource: resources[m.logicalId] };
|
|
70593
|
+
} else {
|
|
70594
|
+
const resource2 = resources[parsed.pathOrId];
|
|
70595
|
+
if (!resource2) {
|
|
70596
|
+
throw notFoundError(target, stack, resources);
|
|
70597
|
+
}
|
|
70598
|
+
match = { logicalId: parsed.pathOrId, resource: resource2 };
|
|
70599
|
+
}
|
|
70600
|
+
const { logicalId, resource } = match;
|
|
70601
|
+
if (resource.Type !== "AWS::Lambda::Function") {
|
|
70602
|
+
if (resource.Type.startsWith("Custom::")) {
|
|
70603
|
+
throw new LocalInvokeResolutionError(
|
|
70604
|
+
`Resource '${logicalId}' in ${stack.stackName} is a Custom Resource (${resource.Type}), not a Lambda function. Custom Resources are invoked by the deploy framework, not by users. If you want to test the underlying handler, target the ServiceToken Lambda directly.`
|
|
70605
|
+
);
|
|
70606
|
+
}
|
|
70607
|
+
throw new LocalInvokeResolutionError(
|
|
70608
|
+
`Resource '${logicalId}' in ${stack.stackName} is ${resource.Type}, not a Lambda function. cdkd local invoke only works on AWS::Lambda::Function resources in v1.`
|
|
70609
|
+
);
|
|
70610
|
+
}
|
|
70611
|
+
return extractLambdaProperties(stack, logicalId, resource, resources);
|
|
70612
|
+
}
|
|
70613
|
+
function pickStack(parsed, stacks) {
|
|
70614
|
+
if (parsed.stackPattern === null) {
|
|
70615
|
+
if (stacks.length === 1)
|
|
70616
|
+
return stacks[0];
|
|
70617
|
+
throw new LocalInvokeResolutionError(
|
|
70618
|
+
`Multiple stacks in app, target '${parsed.pathOrId}' is missing a stack prefix. Use 'StackName:${parsed.pathOrId}' or 'StackName/...' (path form). Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`
|
|
70619
|
+
);
|
|
70620
|
+
}
|
|
70621
|
+
const matched = matchStacks(stacks, [parsed.stackPattern]);
|
|
70622
|
+
if (matched.length === 0) {
|
|
70623
|
+
throw new LocalInvokeResolutionError(
|
|
70624
|
+
`Stack '${parsed.stackPattern}' not found. Available stacks: ${stacks.map((s) => s.stackName).join(", ")}.`
|
|
70625
|
+
);
|
|
70626
|
+
}
|
|
70627
|
+
if (matched.length > 1) {
|
|
70628
|
+
throw new LocalInvokeResolutionError(
|
|
70629
|
+
`Stack pattern '${parsed.stackPattern}' matched ${matched.length} stacks: ` + matched.map((s) => s.stackName).join(", ") + ". Use a more specific pattern."
|
|
70630
|
+
);
|
|
70631
|
+
}
|
|
70632
|
+
return matched[0];
|
|
70633
|
+
}
|
|
70634
|
+
function extractLambdaProperties(stack, logicalId, resource, resources) {
|
|
70635
|
+
const props = resource.Properties ?? {};
|
|
70636
|
+
const memoryMb = typeof props["MemorySize"] === "number" ? props["MemorySize"] : 128;
|
|
70637
|
+
const timeoutSec = typeof props["Timeout"] === "number" ? props["Timeout"] : 3;
|
|
70638
|
+
const code = props["Code"] ?? {};
|
|
70639
|
+
const imageUri = extractImageUri(code["ImageUri"], logicalId, stack.stackName, resources);
|
|
70640
|
+
if (imageUri !== void 0) {
|
|
70641
|
+
return extractImageLambdaProperties({
|
|
70642
|
+
stack,
|
|
70643
|
+
logicalId,
|
|
70644
|
+
resource,
|
|
70645
|
+
memoryMb,
|
|
70646
|
+
timeoutSec,
|
|
70647
|
+
props,
|
|
70648
|
+
imageUri
|
|
70649
|
+
});
|
|
70650
|
+
}
|
|
70651
|
+
const runtime = typeof props["Runtime"] === "string" ? props["Runtime"] : "";
|
|
70652
|
+
const handler = typeof props["Handler"] === "string" ? props["Handler"] : "";
|
|
70653
|
+
if (!runtime) {
|
|
70654
|
+
throw new LocalInvokeResolutionError(
|
|
70655
|
+
`Lambda '${logicalId}' has no Runtime property and no Code.ImageUri. cdkd cannot tell if this is a ZIP or a container Lambda.`
|
|
70656
|
+
);
|
|
70657
|
+
}
|
|
70658
|
+
if (!handler) {
|
|
70659
|
+
throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has no Handler property.`);
|
|
70660
|
+
}
|
|
70661
|
+
const inlineCode = typeof code["ZipFile"] === "string" ? code["ZipFile"] : void 0;
|
|
70662
|
+
let codePath = null;
|
|
70663
|
+
if (!inlineCode) {
|
|
70664
|
+
codePath = resolveAssetCodePath(stack, logicalId, resource);
|
|
70665
|
+
}
|
|
70666
|
+
const layers = resolveLambdaLayers(stack, logicalId, props);
|
|
70667
|
+
return {
|
|
70668
|
+
kind: "zip",
|
|
70669
|
+
stack,
|
|
70670
|
+
logicalId,
|
|
70671
|
+
resource,
|
|
70672
|
+
runtime,
|
|
70673
|
+
handler,
|
|
70674
|
+
memoryMb,
|
|
70675
|
+
timeoutSec,
|
|
70676
|
+
codePath,
|
|
70677
|
+
layers,
|
|
70678
|
+
...inlineCode !== void 0 && { inlineCode }
|
|
70679
|
+
};
|
|
70680
|
+
}
|
|
70681
|
+
function extractImageUri(value, logicalId, stackName, resources) {
|
|
70682
|
+
if (typeof value === "string" && value.length > 0)
|
|
70683
|
+
return value;
|
|
70684
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
70685
|
+
const obj = value;
|
|
70686
|
+
const sub = obj["Fn::Sub"];
|
|
70687
|
+
if (typeof sub === "string" && sub.length > 0)
|
|
70688
|
+
return sub;
|
|
70689
|
+
if (Array.isArray(sub) && typeof sub[0] === "string")
|
|
70690
|
+
return sub[0];
|
|
70691
|
+
if ("Fn::Join" in obj) {
|
|
70692
|
+
const joinResolved = tryResolveImageFnJoin(value, resources, void 0);
|
|
70693
|
+
if (joinResolved.kind === "resolved")
|
|
70694
|
+
return joinResolved.uri;
|
|
70695
|
+
if (joinResolved.kind === "needs-state") {
|
|
70696
|
+
throw new LocalInvokeResolutionError(
|
|
70697
|
+
`Lambda '${logicalId}' in ${stackName} references same-stack ECR repository '${joinResolved.repoLogicalId}' via Fn::Join. cdkd local invoke cannot resolve the repository URI without state \u2014 deploy the stack first (so cdkd records the repository physical id), rebuild via lambda.DockerImageCode.fromImageAsset, or pin a public image.`
|
|
70698
|
+
);
|
|
70699
|
+
}
|
|
70700
|
+
if (joinResolved.kind === "unsupported-join") {
|
|
70701
|
+
throw new LocalInvokeResolutionError(
|
|
70702
|
+
`Lambda '${logicalId}' in ${stackName} has an unsupported Fn::Join Code.ImageUri shape: ${joinResolved.reason}. cdkd local invoke recognizes the canonical CDK 2.x lambda.DockerImageCode.fromEcr Fn::Join shape (delimiter "" with nested Fn::Select/Fn::Split over an ECR Repository Arn GetAtt + Ref to the repo).`
|
|
70703
|
+
);
|
|
70704
|
+
}
|
|
70705
|
+
throw new LocalInvokeResolutionError(
|
|
70706
|
+
`Lambda '${logicalId}' in ${stackName} has an Fn::Join Code.ImageUri that cdkd local invoke cannot resolve. The shape likely references AWS pseudo parameters (e.g. \${AWS::URLSuffix}) for an imported ECR repository. Workarounds: rebuild via lambda.DockerImageCode.fromImageAsset, or pin a fully-literal public image URI.`
|
|
70707
|
+
);
|
|
70708
|
+
}
|
|
70709
|
+
}
|
|
70710
|
+
return void 0;
|
|
70711
|
+
}
|
|
70712
|
+
function extractImageLambdaProperties(args) {
|
|
70713
|
+
const { stack, logicalId, resource, memoryMb, timeoutSec, props, imageUri } = args;
|
|
70714
|
+
const rawImageConfig = props["ImageConfig"] ?? {};
|
|
70715
|
+
const imageConfig = {};
|
|
70716
|
+
if (Array.isArray(rawImageConfig["Command"])) {
|
|
70717
|
+
imageConfig.command = rawImageConfig["Command"].filter(
|
|
70718
|
+
(s) => typeof s === "string"
|
|
70332
70719
|
);
|
|
70333
70720
|
}
|
|
70334
70721
|
if (Array.isArray(rawImageConfig["EntryPoint"])) {
|
|
@@ -70511,7 +70898,8 @@ function isLiteralEnvValue(value) {
|
|
|
70511
70898
|
}
|
|
70512
70899
|
|
|
70513
70900
|
// src/local/state-resolver.ts
|
|
70514
|
-
function substituteAgainstState(value,
|
|
70901
|
+
function substituteAgainstState(value, contextOrResources) {
|
|
70902
|
+
const context = isContext(contextOrResources) ? contextOrResources : { resources: contextOrResources };
|
|
70515
70903
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
70516
70904
|
return { kind: "literal", value };
|
|
70517
70905
|
}
|
|
@@ -70532,24 +70920,40 @@ function substituteAgainstState(value, resources) {
|
|
|
70532
70920
|
const intrinsic = keys[0];
|
|
70533
70921
|
const arg = obj[intrinsic];
|
|
70534
70922
|
if (intrinsic === "Ref") {
|
|
70535
|
-
return resolveRef(arg,
|
|
70923
|
+
return resolveRef(arg, context);
|
|
70536
70924
|
}
|
|
70537
70925
|
if (intrinsic === "Fn::GetAtt") {
|
|
70538
|
-
return resolveGetAtt(arg,
|
|
70926
|
+
return resolveGetAtt(arg, context);
|
|
70539
70927
|
}
|
|
70540
70928
|
if (intrinsic === "Fn::Sub") {
|
|
70541
|
-
return resolveSub(arg,
|
|
70929
|
+
return resolveSub(arg, context);
|
|
70930
|
+
}
|
|
70931
|
+
if (intrinsic === "Fn::Join") {
|
|
70932
|
+
return resolveJoin(arg, context);
|
|
70542
70933
|
}
|
|
70543
70934
|
return {
|
|
70544
70935
|
kind: "unresolved",
|
|
70545
|
-
reason: `unsupported intrinsic '${intrinsic}' (
|
|
70936
|
+
reason: `unsupported intrinsic '${intrinsic}' (supported: Ref, Fn::GetAtt, Fn::Sub, Fn::Join)`
|
|
70546
70937
|
};
|
|
70547
70938
|
}
|
|
70548
|
-
function
|
|
70939
|
+
function isContext(v) {
|
|
70940
|
+
if (typeof v !== "object" || v === null)
|
|
70941
|
+
return false;
|
|
70942
|
+
const r = v["resources"];
|
|
70943
|
+
if (r === void 0)
|
|
70944
|
+
return false;
|
|
70945
|
+
if (typeof r !== "object" || r === null)
|
|
70946
|
+
return false;
|
|
70947
|
+
return !("physicalId" in r);
|
|
70948
|
+
}
|
|
70949
|
+
function resolveRef(arg, context) {
|
|
70549
70950
|
if (typeof arg !== "string" || arg.length === 0) {
|
|
70550
70951
|
return { kind: "unresolved", reason: `Ref expects a non-empty logical ID, got ${typeof arg}` };
|
|
70551
70952
|
}
|
|
70552
|
-
|
|
70953
|
+
if (arg.startsWith("AWS::")) {
|
|
70954
|
+
return resolvePseudoParameter(arg, context.pseudoParameters);
|
|
70955
|
+
}
|
|
70956
|
+
const resource = context.resources[arg];
|
|
70553
70957
|
if (!resource) {
|
|
70554
70958
|
return {
|
|
70555
70959
|
kind: "unresolved",
|
|
@@ -70558,7 +70962,39 @@ function resolveRef(arg, resources) {
|
|
|
70558
70962
|
}
|
|
70559
70963
|
return { kind: "literal", value: resource.physicalId };
|
|
70560
70964
|
}
|
|
70561
|
-
function
|
|
70965
|
+
function resolvePseudoParameter(name, pseudo) {
|
|
70966
|
+
if (!pseudo) {
|
|
70967
|
+
return {
|
|
70968
|
+
kind: "unresolved",
|
|
70969
|
+
reason: `Ref '${name}': pseudo parameter not supplied (need --from-state context)`
|
|
70970
|
+
};
|
|
70971
|
+
}
|
|
70972
|
+
switch (name) {
|
|
70973
|
+
case "AWS::AccountId":
|
|
70974
|
+
if (pseudo.accountId !== void 0)
|
|
70975
|
+
return { kind: "literal", value: pseudo.accountId };
|
|
70976
|
+
break;
|
|
70977
|
+
case "AWS::Region":
|
|
70978
|
+
if (pseudo.region !== void 0)
|
|
70979
|
+
return { kind: "literal", value: pseudo.region };
|
|
70980
|
+
break;
|
|
70981
|
+
case "AWS::Partition":
|
|
70982
|
+
if (pseudo.partition !== void 0)
|
|
70983
|
+
return { kind: "literal", value: pseudo.partition };
|
|
70984
|
+
break;
|
|
70985
|
+
case "AWS::URLSuffix":
|
|
70986
|
+
if (pseudo.urlSuffix !== void 0)
|
|
70987
|
+
return { kind: "literal", value: pseudo.urlSuffix };
|
|
70988
|
+
break;
|
|
70989
|
+
default:
|
|
70990
|
+
return {
|
|
70991
|
+
kind: "unresolved",
|
|
70992
|
+
reason: `Ref '${name}': pseudo parameter not supported (supported: AWS::AccountId, AWS::Region, AWS::Partition, AWS::URLSuffix)`
|
|
70993
|
+
};
|
|
70994
|
+
}
|
|
70995
|
+
return { kind: "unresolved", reason: `Ref '${name}': pseudo parameter value not resolved` };
|
|
70996
|
+
}
|
|
70997
|
+
function resolveGetAtt(arg, context) {
|
|
70562
70998
|
let logicalId;
|
|
70563
70999
|
let attr;
|
|
70564
71000
|
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string") {
|
|
@@ -70566,7 +71002,7 @@ function resolveGetAtt(arg, resources) {
|
|
|
70566
71002
|
if (typeof arg[1] !== "string") {
|
|
70567
71003
|
return {
|
|
70568
71004
|
kind: "unresolved",
|
|
70569
|
-
reason: `Fn::GetAtt's second arg must be a string attribute name, got ${typeof arg[1]} (nested intrinsics in attribute names are not supported
|
|
71005
|
+
reason: `Fn::GetAtt's second arg must be a string attribute name, got ${typeof arg[1]} (nested intrinsics in attribute names are not supported)`
|
|
70570
71006
|
};
|
|
70571
71007
|
}
|
|
70572
71008
|
attr = arg[1];
|
|
@@ -70586,7 +71022,7 @@ function resolveGetAtt(arg, resources) {
|
|
|
70586
71022
|
reason: `Fn::GetAtt expects [LogicalId, Attribute] or 'LogicalId.Attribute', got ${Array.isArray(arg) ? `array of length ${arg.length}` : typeof arg}`
|
|
70587
71023
|
};
|
|
70588
71024
|
}
|
|
70589
|
-
const resource = resources[logicalId];
|
|
71025
|
+
const resource = context.resources[logicalId];
|
|
70590
71026
|
if (!resource) {
|
|
70591
71027
|
return {
|
|
70592
71028
|
kind: "unresolved",
|
|
@@ -70605,7 +71041,7 @@ function resolveGetAtt(arg, resources) {
|
|
|
70605
71041
|
}
|
|
70606
71042
|
return { kind: "literal", value: JSON.stringify(cached) };
|
|
70607
71043
|
}
|
|
70608
|
-
function resolveSub(arg,
|
|
71044
|
+
function resolveSub(arg, context) {
|
|
70609
71045
|
let template;
|
|
70610
71046
|
let bindings = {};
|
|
70611
71047
|
if (typeof arg === "string") {
|
|
@@ -70630,7 +71066,18 @@ function resolveSub(arg, resources) {
|
|
|
70630
71066
|
if (resolutions.has(placeholder))
|
|
70631
71067
|
continue;
|
|
70632
71068
|
if (placeholder in bindings) {
|
|
70633
|
-
const sub = substituteAgainstState(bindings[placeholder],
|
|
71069
|
+
const sub = substituteAgainstState(bindings[placeholder], context);
|
|
71070
|
+
if (sub.kind !== "literal") {
|
|
71071
|
+
return {
|
|
71072
|
+
kind: "unresolved",
|
|
71073
|
+
reason: `Fn::Sub placeholder '\${${placeholder}}': ${sub.reason}`
|
|
71074
|
+
};
|
|
71075
|
+
}
|
|
71076
|
+
resolutions.set(placeholder, String(sub.value));
|
|
71077
|
+
continue;
|
|
71078
|
+
}
|
|
71079
|
+
if (placeholder.startsWith("AWS::")) {
|
|
71080
|
+
const sub = resolvePseudoParameter(placeholder, context.pseudoParameters);
|
|
70634
71081
|
if (sub.kind !== "literal") {
|
|
70635
71082
|
return {
|
|
70636
71083
|
kind: "unresolved",
|
|
@@ -70642,7 +71089,7 @@ function resolveSub(arg, resources) {
|
|
|
70642
71089
|
}
|
|
70643
71090
|
const dot = placeholder.indexOf(".");
|
|
70644
71091
|
if (dot === -1) {
|
|
70645
|
-
const sub = resolveRef(placeholder,
|
|
71092
|
+
const sub = resolveRef(placeholder, context);
|
|
70646
71093
|
if (sub.kind !== "literal") {
|
|
70647
71094
|
return {
|
|
70648
71095
|
kind: "unresolved",
|
|
@@ -70651,7 +71098,7 @@ function resolveSub(arg, resources) {
|
|
|
70651
71098
|
}
|
|
70652
71099
|
resolutions.set(placeholder, String(sub.value));
|
|
70653
71100
|
} else {
|
|
70654
|
-
const sub = resolveGetAtt(placeholder,
|
|
71101
|
+
const sub = resolveGetAtt(placeholder, context);
|
|
70655
71102
|
if (sub.kind !== "literal") {
|
|
70656
71103
|
return {
|
|
70657
71104
|
kind: "unresolved",
|
|
@@ -70666,17 +71113,45 @@ function resolveSub(arg, resources) {
|
|
|
70666
71113
|
});
|
|
70667
71114
|
return { kind: "literal", value: out };
|
|
70668
71115
|
}
|
|
70669
|
-
function
|
|
71116
|
+
function resolveJoin(arg, context) {
|
|
71117
|
+
if (!Array.isArray(arg) || arg.length !== 2 || !Array.isArray(arg[1])) {
|
|
71118
|
+
return {
|
|
71119
|
+
kind: "unresolved",
|
|
71120
|
+
reason: `Fn::Join expects [delimiter, [elements]], got ${Array.isArray(arg) ? `array of length ${arg.length}` : typeof arg}`
|
|
71121
|
+
};
|
|
71122
|
+
}
|
|
71123
|
+
const [delimiterRaw, elements] = arg;
|
|
71124
|
+
if (typeof delimiterRaw !== "string") {
|
|
71125
|
+
return {
|
|
71126
|
+
kind: "unresolved",
|
|
71127
|
+
reason: `Fn::Join delimiter must be a string, got ${typeof delimiterRaw}`
|
|
71128
|
+
};
|
|
71129
|
+
}
|
|
71130
|
+
const parts = [];
|
|
71131
|
+
for (let i = 0; i < elements.length; i += 1) {
|
|
71132
|
+
const sub = substituteAgainstState(elements[i], context);
|
|
71133
|
+
if (sub.kind !== "literal") {
|
|
71134
|
+
return {
|
|
71135
|
+
kind: "unresolved",
|
|
71136
|
+
reason: `Fn::Join element [${i}]: ${sub.reason}`
|
|
71137
|
+
};
|
|
71138
|
+
}
|
|
71139
|
+
parts.push(String(sub.value));
|
|
71140
|
+
}
|
|
71141
|
+
return { kind: "literal", value: parts.join(delimiterRaw) };
|
|
71142
|
+
}
|
|
71143
|
+
function substituteEnvVarsFromState(templateEnv, contextOrResources) {
|
|
70670
71144
|
const env = {};
|
|
70671
71145
|
const audit = { resolvedKeys: [], unresolved: [] };
|
|
70672
71146
|
if (!templateEnv)
|
|
70673
71147
|
return { env, audit };
|
|
71148
|
+
const context = isContext(contextOrResources) ? contextOrResources : { resources: contextOrResources };
|
|
70674
71149
|
for (const [key, value] of Object.entries(templateEnv)) {
|
|
70675
71150
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
70676
71151
|
env[key] = value;
|
|
70677
71152
|
continue;
|
|
70678
71153
|
}
|
|
70679
|
-
const result = substituteAgainstState(value,
|
|
71154
|
+
const result = substituteAgainstState(value, context);
|
|
70680
71155
|
if (result.kind === "literal") {
|
|
70681
71156
|
env[key] = result.value;
|
|
70682
71157
|
audit.resolvedKeys.push(key);
|
|
@@ -76783,6 +77258,7 @@ function detectEcsImageResolutionNeeds(stack) {
|
|
|
76783
77258
|
const resources = stack.template.Resources ?? {};
|
|
76784
77259
|
let needsPseudoParameters = false;
|
|
76785
77260
|
let needsStateResources = false;
|
|
77261
|
+
let needsEnvOrSecretSubstitution = false;
|
|
76786
77262
|
for (const res of Object.values(resources)) {
|
|
76787
77263
|
if (res.Type !== "AWS::ECS::TaskDefinition")
|
|
76788
77264
|
continue;
|
|
@@ -76791,19 +77267,42 @@ function detectEcsImageResolutionNeeds(stack) {
|
|
|
76791
77267
|
for (const c of containers) {
|
|
76792
77268
|
if (!c || typeof c !== "object")
|
|
76793
77269
|
continue;
|
|
76794
|
-
const
|
|
77270
|
+
const co = c;
|
|
77271
|
+
const image = co["Image"];
|
|
76795
77272
|
const need = inspectImageForSubstitutions(image, resources);
|
|
76796
77273
|
if (need.pseudo)
|
|
76797
77274
|
needsPseudoParameters = true;
|
|
76798
77275
|
if (need.state)
|
|
76799
77276
|
needsStateResources = true;
|
|
76800
|
-
if (
|
|
76801
|
-
|
|
77277
|
+
if (containerHasIntrinsicEnvOrSecret(co))
|
|
77278
|
+
needsEnvOrSecretSubstitution = true;
|
|
76802
77279
|
}
|
|
76803
|
-
if (needsPseudoParameters && needsStateResources)
|
|
76804
|
-
break;
|
|
76805
77280
|
}
|
|
76806
|
-
return { needsPseudoParameters, needsStateResources };
|
|
77281
|
+
return { needsPseudoParameters, needsStateResources, needsEnvOrSecretSubstitution };
|
|
77282
|
+
}
|
|
77283
|
+
function containerHasIntrinsicEnvOrSecret(c) {
|
|
77284
|
+
const env = c["Environment"];
|
|
77285
|
+
if (Array.isArray(env)) {
|
|
77286
|
+
for (const entry of env) {
|
|
77287
|
+
if (!entry || typeof entry !== "object")
|
|
77288
|
+
continue;
|
|
77289
|
+
const v = entry["Value"];
|
|
77290
|
+
if (v !== void 0 && typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean") {
|
|
77291
|
+
return true;
|
|
77292
|
+
}
|
|
77293
|
+
}
|
|
77294
|
+
}
|
|
77295
|
+
const secrets = c["Secrets"];
|
|
77296
|
+
if (Array.isArray(secrets)) {
|
|
77297
|
+
for (const entry of secrets) {
|
|
77298
|
+
if (!entry || typeof entry !== "object")
|
|
77299
|
+
continue;
|
|
77300
|
+
const v = entry["ValueFrom"];
|
|
77301
|
+
if (v !== void 0 && typeof v !== "string")
|
|
77302
|
+
return true;
|
|
77303
|
+
}
|
|
77304
|
+
}
|
|
77305
|
+
return false;
|
|
76807
77306
|
}
|
|
76808
77307
|
function inspectImageForSubstitutions(image, resources) {
|
|
76809
77308
|
if (!image || typeof image !== "object")
|
|
@@ -77004,6 +77503,11 @@ function extractTaskDefinitionProperties(stack, logicalId, resource, context) {
|
|
|
77004
77503
|
const containers = rawContainers.map(
|
|
77005
77504
|
(c, idx) => parseContainerDefinition(c, idx, logicalId, resources, stack, context)
|
|
77006
77505
|
);
|
|
77506
|
+
for (const ctr of containers) {
|
|
77507
|
+
for (const w of ctr.warnings) {
|
|
77508
|
+
warnings.push(`Container '${ctr.name}': ${w}`);
|
|
77509
|
+
}
|
|
77510
|
+
}
|
|
77007
77511
|
const rawVolumes = props["Volumes"];
|
|
77008
77512
|
const volumes = Array.isArray(rawVolumes) ? rawVolumes.map((v, idx) => parseVolume(v, idx, logicalId)) : [];
|
|
77009
77513
|
const containerNames = new Set(containers.map((c) => c.name));
|
|
@@ -77065,7 +77569,9 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, con
|
|
|
77065
77569
|
const command = pickStringArray2(c["Command"]);
|
|
77066
77570
|
const entryPoint = pickStringArray2(c["EntryPoint"]);
|
|
77067
77571
|
const workingDirectory = pickString(c["WorkingDirectory"]);
|
|
77572
|
+
const subContext = buildSubstitutionContextFromImageContext(context);
|
|
77068
77573
|
const environment = {};
|
|
77574
|
+
const droppedEnvKeys = [];
|
|
77069
77575
|
if (Array.isArray(c["Environment"])) {
|
|
77070
77576
|
for (const entry of c["Environment"]) {
|
|
77071
77577
|
if (!entry || typeof entry !== "object")
|
|
@@ -77077,19 +77583,54 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, con
|
|
|
77077
77583
|
continue;
|
|
77078
77584
|
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
77079
77585
|
environment[key] = String(value);
|
|
77586
|
+
continue;
|
|
77587
|
+
}
|
|
77588
|
+
if (subContext) {
|
|
77589
|
+
const sub = substituteAgainstState(value, subContext);
|
|
77590
|
+
if (sub.kind === "literal") {
|
|
77591
|
+
environment[key] = String(sub.value);
|
|
77592
|
+
continue;
|
|
77593
|
+
}
|
|
77594
|
+
droppedEnvKeys.push({ key, reason: sub.reason });
|
|
77595
|
+
} else {
|
|
77596
|
+
droppedEnvKeys.push({
|
|
77597
|
+
key,
|
|
77598
|
+
reason: "intrinsic-valued; pass --from-state to substitute against deployed state"
|
|
77599
|
+
});
|
|
77080
77600
|
}
|
|
77081
77601
|
}
|
|
77082
77602
|
}
|
|
77083
77603
|
const secrets = [];
|
|
77604
|
+
const droppedSecretKeys = [];
|
|
77084
77605
|
if (Array.isArray(c["Secrets"])) {
|
|
77085
77606
|
for (const entry of c["Secrets"]) {
|
|
77086
77607
|
if (!entry || typeof entry !== "object")
|
|
77087
77608
|
continue;
|
|
77088
77609
|
const e = entry;
|
|
77089
77610
|
const sName = pickString(e["Name"]);
|
|
77090
|
-
const
|
|
77091
|
-
if (sName
|
|
77092
|
-
|
|
77611
|
+
const valueFromRaw = e["ValueFrom"];
|
|
77612
|
+
if (!sName)
|
|
77613
|
+
continue;
|
|
77614
|
+
if (typeof valueFromRaw === "string" && valueFromRaw.length > 0) {
|
|
77615
|
+
secrets.push({ name: sName, valueFrom: valueFromRaw });
|
|
77616
|
+
continue;
|
|
77617
|
+
}
|
|
77618
|
+
if (subContext) {
|
|
77619
|
+
const sub = substituteAgainstState(valueFromRaw, subContext);
|
|
77620
|
+
if (sub.kind === "literal" && typeof sub.value === "string" && sub.value.length > 0) {
|
|
77621
|
+
secrets.push({ name: sName, valueFrom: sub.value });
|
|
77622
|
+
continue;
|
|
77623
|
+
}
|
|
77624
|
+
droppedSecretKeys.push({
|
|
77625
|
+
key: sName,
|
|
77626
|
+
reason: sub.kind === "literal" ? "resolved to non-string / empty value" : sub.reason
|
|
77627
|
+
});
|
|
77628
|
+
} else {
|
|
77629
|
+
droppedSecretKeys.push({
|
|
77630
|
+
key: sName,
|
|
77631
|
+
reason: "intrinsic-valued ValueFrom; pass --from-state to resolve the deployed ARN"
|
|
77632
|
+
});
|
|
77633
|
+
}
|
|
77093
77634
|
}
|
|
77094
77635
|
}
|
|
77095
77636
|
const portMappings = [];
|
|
@@ -77179,6 +77720,13 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, con
|
|
|
77179
77720
|
ulimits.push({ name: uName, softLimit: soft, hardLimit: hard });
|
|
77180
77721
|
}
|
|
77181
77722
|
}
|
|
77723
|
+
const warnings = [];
|
|
77724
|
+
for (const d of droppedEnvKeys) {
|
|
77725
|
+
warnings.push(`Environment '${d.key}' dropped: ${d.reason}`);
|
|
77726
|
+
}
|
|
77727
|
+
for (const d of droppedSecretKeys) {
|
|
77728
|
+
warnings.push(`Secret '${d.key}' dropped: ${d.reason}`);
|
|
77729
|
+
}
|
|
77182
77730
|
const out = {
|
|
77183
77731
|
name,
|
|
77184
77732
|
image,
|
|
@@ -77189,7 +77737,8 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, con
|
|
|
77189
77737
|
dependsOn,
|
|
77190
77738
|
links,
|
|
77191
77739
|
essential,
|
|
77192
|
-
ulimits
|
|
77740
|
+
ulimits,
|
|
77741
|
+
warnings
|
|
77193
77742
|
};
|
|
77194
77743
|
if (command !== void 0)
|
|
77195
77744
|
out.command = command;
|
|
@@ -77207,6 +77756,15 @@ function parseContainerDefinition(raw, idx, taskLogicalId, resources, stack, con
|
|
|
77207
77756
|
out.readonlyRootFilesystem = readonlyRootFilesystem;
|
|
77208
77757
|
return out;
|
|
77209
77758
|
}
|
|
77759
|
+
function buildSubstitutionContextFromImageContext(context) {
|
|
77760
|
+
if (!context?.stateResources)
|
|
77761
|
+
return void 0;
|
|
77762
|
+
const subContext = { resources: context.stateResources };
|
|
77763
|
+
if (context.pseudoParameters) {
|
|
77764
|
+
subContext.pseudoParameters = { ...context.pseudoParameters };
|
|
77765
|
+
}
|
|
77766
|
+
return subContext;
|
|
77767
|
+
}
|
|
77210
77768
|
function parseContainerImage(raw, containerName, taskLogicalId, resources, _stack, context) {
|
|
77211
77769
|
const getAttImage = tryResolveImageGetAtt(raw, resources, context);
|
|
77212
77770
|
if (getAttImage) {
|
|
@@ -77258,73 +77816,37 @@ function parseContainerImage(raw, containerName, taskLogicalId, resources, _stac
|
|
|
77258
77816
|
}
|
|
77259
77817
|
const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(substituted);
|
|
77260
77818
|
if (ecrMatch) {
|
|
77261
|
-
return { kind: "ecr", uri: substituted, account: ecrMatch[1], region: ecrMatch[2] };
|
|
77262
|
-
}
|
|
77263
|
-
return { kind: "public", uri: substituted };
|
|
77264
|
-
}
|
|
77265
|
-
function findUnresolvedEcrRepositoryRef(substituted, resources) {
|
|
77266
|
-
const placeholderRegex = /\$\{([^}]+)\}/g;
|
|
77267
|
-
let m;
|
|
77268
|
-
while ((m = placeholderRegex.exec(substituted)) !== null) {
|
|
77269
|
-
const key = m[1];
|
|
77270
|
-
if (key.startsWith("AWS::"))
|
|
77271
|
-
continue;
|
|
77272
|
-
const dot = key.indexOf(".");
|
|
77273
|
-
const lid = dot === -1 ? key : key.slice(0, dot);
|
|
77274
|
-
if (resources[lid]?.Type === "AWS::ECR::Repository")
|
|
77275
|
-
return lid;
|
|
77276
|
-
}
|
|
77277
|
-
return void 0;
|
|
77278
|
-
}
|
|
77279
|
-
function classifyResolvedImage(uri) {
|
|
77280
|
-
if (uri.includes("cdk-hnb659fds-container-assets-")) {
|
|
77281
|
-
const hashMatch = /:([a-f0-9]{8,})$/.exec(uri);
|
|
77282
|
-
const out = { kind: "cdk-asset" };
|
|
77283
|
-
if (hashMatch)
|
|
77284
|
-
out.assetHash = hashMatch[1];
|
|
77285
|
-
return out;
|
|
77286
|
-
}
|
|
77287
|
-
const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(uri);
|
|
77288
|
-
if (ecrMatch) {
|
|
77289
|
-
return { kind: "ecr", uri, account: ecrMatch[1], region: ecrMatch[2] };
|
|
77290
|
-
}
|
|
77291
|
-
return { kind: "public", uri };
|
|
77292
|
-
}
|
|
77293
|
-
function substituteImagePlaceholders(flat, resources, context) {
|
|
77294
|
-
if (!flat.includes("${"))
|
|
77295
|
-
return flat;
|
|
77296
|
-
return flat.replace(/\$\{([^}]+)\}/g, (full, key) => {
|
|
77297
|
-
if (context?.pseudoParameters) {
|
|
77298
|
-
if (key === "AWS::AccountId" && context.pseudoParameters.accountId) {
|
|
77299
|
-
return context.pseudoParameters.accountId;
|
|
77300
|
-
}
|
|
77301
|
-
if (key === "AWS::Region" && context.pseudoParameters.region) {
|
|
77302
|
-
return context.pseudoParameters.region;
|
|
77303
|
-
}
|
|
77304
|
-
if (key === "AWS::Partition" && context.pseudoParameters.partition) {
|
|
77305
|
-
return context.pseudoParameters.partition;
|
|
77306
|
-
}
|
|
77307
|
-
if (key === "AWS::URLSuffix" && context.pseudoParameters.urlSuffix) {
|
|
77308
|
-
return context.pseudoParameters.urlSuffix;
|
|
77309
|
-
}
|
|
77310
|
-
}
|
|
77311
|
-
if (context?.stateResources) {
|
|
77312
|
-
const dot = key.indexOf(".");
|
|
77313
|
-
const logicalId = dot === -1 ? key : key.slice(0, dot);
|
|
77314
|
-
const refResource = resources[logicalId];
|
|
77315
|
-
const stateEntry = context.stateResources[logicalId];
|
|
77316
|
-
if (refResource?.Type === "AWS::ECR::Repository" && stateEntry) {
|
|
77317
|
-
if (dot === -1) {
|
|
77318
|
-
return stateEntry.physicalId;
|
|
77319
|
-
}
|
|
77320
|
-
const attr = key.slice(dot + 1);
|
|
77321
|
-
const cached = stateEntry.attributes?.[attr];
|
|
77322
|
-
if (typeof cached === "string")
|
|
77323
|
-
return cached;
|
|
77324
|
-
}
|
|
77325
|
-
}
|
|
77326
|
-
return full;
|
|
77327
|
-
});
|
|
77819
|
+
return { kind: "ecr", uri: substituted, account: ecrMatch[1], region: ecrMatch[2] };
|
|
77820
|
+
}
|
|
77821
|
+
return { kind: "public", uri: substituted };
|
|
77822
|
+
}
|
|
77823
|
+
function findUnresolvedEcrRepositoryRef(substituted, resources) {
|
|
77824
|
+
const placeholderRegex = /\$\{([^}]+)\}/g;
|
|
77825
|
+
let m;
|
|
77826
|
+
while ((m = placeholderRegex.exec(substituted)) !== null) {
|
|
77827
|
+
const key = m[1];
|
|
77828
|
+
if (key.startsWith("AWS::"))
|
|
77829
|
+
continue;
|
|
77830
|
+
const dot = key.indexOf(".");
|
|
77831
|
+
const lid = dot === -1 ? key : key.slice(0, dot);
|
|
77832
|
+
if (resources[lid]?.Type === "AWS::ECR::Repository")
|
|
77833
|
+
return lid;
|
|
77834
|
+
}
|
|
77835
|
+
return void 0;
|
|
77836
|
+
}
|
|
77837
|
+
function classifyResolvedImage(uri) {
|
|
77838
|
+
if (uri.includes("cdk-hnb659fds-container-assets-")) {
|
|
77839
|
+
const hashMatch = /:([a-f0-9]{8,})$/.exec(uri);
|
|
77840
|
+
const out = { kind: "cdk-asset" };
|
|
77841
|
+
if (hashMatch)
|
|
77842
|
+
out.assetHash = hashMatch[1];
|
|
77843
|
+
return out;
|
|
77844
|
+
}
|
|
77845
|
+
const ecrMatch = /^(\d{12})\.dkr\.ecr\.([^.]+)\.amazonaws\.com(?:\.cn)?\//.exec(uri);
|
|
77846
|
+
if (ecrMatch) {
|
|
77847
|
+
return { kind: "ecr", uri, account: ecrMatch[1], region: ecrMatch[2] };
|
|
77848
|
+
}
|
|
77849
|
+
return { kind: "public", uri };
|
|
77328
77850
|
}
|
|
77329
77851
|
function tryResolveImageGetAtt(raw, resources, context) {
|
|
77330
77852
|
if (!raw || typeof raw !== "object")
|
|
@@ -77377,228 +77899,6 @@ function extractImageString(value) {
|
|
|
77377
77899
|
}
|
|
77378
77900
|
return void 0;
|
|
77379
77901
|
}
|
|
77380
|
-
function tryResolveImageFnJoin(raw, resources, context) {
|
|
77381
|
-
if (!raw || typeof raw !== "object")
|
|
77382
|
-
return { kind: "not-applicable" };
|
|
77383
|
-
const obj = raw;
|
|
77384
|
-
const arg = obj["Fn::Join"];
|
|
77385
|
-
if (arg === void 0)
|
|
77386
|
-
return { kind: "not-applicable" };
|
|
77387
|
-
if (!Array.isArray(arg) || arg.length !== 2 || !Array.isArray(arg[1])) {
|
|
77388
|
-
return { kind: "unsupported-join", reason: "Fn::Join must be [delimiter, [elements]]" };
|
|
77389
|
-
}
|
|
77390
|
-
const [delimiter, elements] = arg;
|
|
77391
|
-
if (typeof delimiter !== "string") {
|
|
77392
|
-
return {
|
|
77393
|
-
kind: "unsupported-join",
|
|
77394
|
-
reason: `Fn::Join delimiter must be a string, got ${typeof delimiter}`
|
|
77395
|
-
};
|
|
77396
|
-
}
|
|
77397
|
-
const repoLogicalId = findEcrRepositoryRefInTree(elements, resources);
|
|
77398
|
-
const stateResources = context?.stateResources;
|
|
77399
|
-
if (repoLogicalId && !stateResources) {
|
|
77400
|
-
return { kind: "needs-state", repoLogicalId };
|
|
77401
|
-
}
|
|
77402
|
-
const parts = [];
|
|
77403
|
-
for (const element of elements) {
|
|
77404
|
-
const r = resolveImageIntrinsic(element, resources, context);
|
|
77405
|
-
if (r === void 0) {
|
|
77406
|
-
if (!repoLogicalId)
|
|
77407
|
-
return { kind: "not-applicable" };
|
|
77408
|
-
return {
|
|
77409
|
-
kind: "unsupported-join",
|
|
77410
|
-
reason: "one or more Fn::Join elements could not be resolved"
|
|
77411
|
-
};
|
|
77412
|
-
}
|
|
77413
|
-
parts.push(r);
|
|
77414
|
-
}
|
|
77415
|
-
return { kind: "resolved", uri: parts.join(delimiter) };
|
|
77416
|
-
}
|
|
77417
|
-
function findEcrRepositoryRefInTree(node, resources) {
|
|
77418
|
-
if (node === null || node === void 0)
|
|
77419
|
-
return void 0;
|
|
77420
|
-
if (typeof node === "string" || typeof node === "number" || typeof node === "boolean") {
|
|
77421
|
-
return void 0;
|
|
77422
|
-
}
|
|
77423
|
-
if (Array.isArray(node)) {
|
|
77424
|
-
for (const item of node) {
|
|
77425
|
-
const hit = findEcrRepositoryRefInTree(item, resources);
|
|
77426
|
-
if (hit)
|
|
77427
|
-
return hit;
|
|
77428
|
-
}
|
|
77429
|
-
return void 0;
|
|
77430
|
-
}
|
|
77431
|
-
if (typeof node !== "object")
|
|
77432
|
-
return void 0;
|
|
77433
|
-
const obj = node;
|
|
77434
|
-
if (typeof obj["Ref"] === "string") {
|
|
77435
|
-
const target = obj["Ref"];
|
|
77436
|
-
if (resources[target]?.Type === "AWS::ECR::Repository")
|
|
77437
|
-
return target;
|
|
77438
|
-
return void 0;
|
|
77439
|
-
}
|
|
77440
|
-
const getAtt = obj["Fn::GetAtt"];
|
|
77441
|
-
if (getAtt !== void 0) {
|
|
77442
|
-
let lid;
|
|
77443
|
-
if (Array.isArray(getAtt) && typeof getAtt[0] === "string")
|
|
77444
|
-
lid = getAtt[0];
|
|
77445
|
-
else if (typeof getAtt === "string")
|
|
77446
|
-
lid = getAtt.split(".")[0];
|
|
77447
|
-
if (lid && resources[lid]?.Type === "AWS::ECR::Repository")
|
|
77448
|
-
return lid;
|
|
77449
|
-
return void 0;
|
|
77450
|
-
}
|
|
77451
|
-
for (const value of Object.values(obj)) {
|
|
77452
|
-
const hit = findEcrRepositoryRefInTree(value, resources);
|
|
77453
|
-
if (hit)
|
|
77454
|
-
return hit;
|
|
77455
|
-
}
|
|
77456
|
-
return void 0;
|
|
77457
|
-
}
|
|
77458
|
-
function resolveImageIntrinsic(node, resources, context) {
|
|
77459
|
-
const v = resolveImageIntrinsicAny(node, resources, context);
|
|
77460
|
-
if (typeof v === "string")
|
|
77461
|
-
return v;
|
|
77462
|
-
if (typeof v === "number" || typeof v === "boolean")
|
|
77463
|
-
return String(v);
|
|
77464
|
-
return void 0;
|
|
77465
|
-
}
|
|
77466
|
-
function resolveImageIntrinsicAny(node, resources, context) {
|
|
77467
|
-
if (node === null || node === void 0)
|
|
77468
|
-
return void 0;
|
|
77469
|
-
if (typeof node === "string" || typeof node === "number" || typeof node === "boolean") {
|
|
77470
|
-
return node;
|
|
77471
|
-
}
|
|
77472
|
-
if (Array.isArray(node)) {
|
|
77473
|
-
return void 0;
|
|
77474
|
-
}
|
|
77475
|
-
if (typeof node !== "object")
|
|
77476
|
-
return void 0;
|
|
77477
|
-
const obj = node;
|
|
77478
|
-
const keys = Object.keys(obj);
|
|
77479
|
-
if (keys.length !== 1)
|
|
77480
|
-
return void 0;
|
|
77481
|
-
const intrinsic = keys[0];
|
|
77482
|
-
const arg = obj[intrinsic];
|
|
77483
|
-
if (intrinsic === "Ref") {
|
|
77484
|
-
if (typeof arg !== "string")
|
|
77485
|
-
return void 0;
|
|
77486
|
-
if (arg.startsWith("AWS::")) {
|
|
77487
|
-
const p = context?.pseudoParameters;
|
|
77488
|
-
if (!p)
|
|
77489
|
-
return void 0;
|
|
77490
|
-
if (arg === "AWS::URLSuffix")
|
|
77491
|
-
return p.urlSuffix;
|
|
77492
|
-
if (arg === "AWS::Partition")
|
|
77493
|
-
return p.partition;
|
|
77494
|
-
if (arg === "AWS::Region")
|
|
77495
|
-
return p.region;
|
|
77496
|
-
if (arg === "AWS::AccountId")
|
|
77497
|
-
return p.accountId;
|
|
77498
|
-
return void 0;
|
|
77499
|
-
}
|
|
77500
|
-
const refResource = resources[arg];
|
|
77501
|
-
if (refResource?.Type !== "AWS::ECR::Repository")
|
|
77502
|
-
return void 0;
|
|
77503
|
-
const stateEntry = context?.stateResources?.[arg];
|
|
77504
|
-
if (!stateEntry)
|
|
77505
|
-
return void 0;
|
|
77506
|
-
return stateEntry.physicalId;
|
|
77507
|
-
}
|
|
77508
|
-
if (intrinsic === "Fn::GetAtt") {
|
|
77509
|
-
let logicalId;
|
|
77510
|
-
let attr;
|
|
77511
|
-
if (Array.isArray(arg) && arg.length === 2 && typeof arg[0] === "string" && typeof arg[1] === "string") {
|
|
77512
|
-
logicalId = arg[0];
|
|
77513
|
-
attr = arg[1];
|
|
77514
|
-
} else if (typeof arg === "string") {
|
|
77515
|
-
const dot = arg.indexOf(".");
|
|
77516
|
-
if (dot > 0 && dot < arg.length - 1) {
|
|
77517
|
-
logicalId = arg.slice(0, dot);
|
|
77518
|
-
attr = arg.slice(dot + 1);
|
|
77519
|
-
}
|
|
77520
|
-
}
|
|
77521
|
-
if (!logicalId || !attr)
|
|
77522
|
-
return void 0;
|
|
77523
|
-
if (resources[logicalId]?.Type !== "AWS::ECR::Repository")
|
|
77524
|
-
return void 0;
|
|
77525
|
-
const cached = context?.stateResources?.[logicalId]?.attributes?.[attr];
|
|
77526
|
-
if (typeof cached === "string" && cached.length > 0)
|
|
77527
|
-
return cached;
|
|
77528
|
-
return void 0;
|
|
77529
|
-
}
|
|
77530
|
-
if (intrinsic === "Fn::Split") {
|
|
77531
|
-
if (!Array.isArray(arg) || arg.length !== 2)
|
|
77532
|
-
return void 0;
|
|
77533
|
-
const argArr = arg;
|
|
77534
|
-
const delim = argArr[0];
|
|
77535
|
-
if (typeof delim !== "string")
|
|
77536
|
-
return void 0;
|
|
77537
|
-
const src = resolveImageIntrinsicAny(argArr[1], resources, context);
|
|
77538
|
-
if (typeof src !== "string")
|
|
77539
|
-
return void 0;
|
|
77540
|
-
return src.split(delim);
|
|
77541
|
-
}
|
|
77542
|
-
if (intrinsic === "Fn::Select") {
|
|
77543
|
-
if (!Array.isArray(arg) || arg.length !== 2)
|
|
77544
|
-
return void 0;
|
|
77545
|
-
const argArr = arg;
|
|
77546
|
-
const rawIndex = argArr[0];
|
|
77547
|
-
let index;
|
|
77548
|
-
if (typeof rawIndex === "number") {
|
|
77549
|
-
index = rawIndex;
|
|
77550
|
-
} else if (typeof rawIndex === "string" && /^-?\d+$/.test(rawIndex)) {
|
|
77551
|
-
index = Number.parseInt(rawIndex, 10);
|
|
77552
|
-
}
|
|
77553
|
-
if (index === void 0 || !Number.isFinite(index))
|
|
77554
|
-
return void 0;
|
|
77555
|
-
const list = resolveImageIntrinsicAny(argArr[1], resources, context);
|
|
77556
|
-
if (Array.isArray(list)) {
|
|
77557
|
-
if (index < 0 || index >= list.length)
|
|
77558
|
-
return void 0;
|
|
77559
|
-
const picked = list[index];
|
|
77560
|
-
if (typeof picked === "string")
|
|
77561
|
-
return picked;
|
|
77562
|
-
return void 0;
|
|
77563
|
-
}
|
|
77564
|
-
if (Array.isArray(argArr[1])) {
|
|
77565
|
-
const listLiteral = argArr[1];
|
|
77566
|
-
if (index < 0 || index >= listLiteral.length)
|
|
77567
|
-
return void 0;
|
|
77568
|
-
return resolveImageIntrinsic(listLiteral[index], resources, context);
|
|
77569
|
-
}
|
|
77570
|
-
return void 0;
|
|
77571
|
-
}
|
|
77572
|
-
if (intrinsic === "Fn::Join") {
|
|
77573
|
-
if (!Array.isArray(arg) || arg.length !== 2)
|
|
77574
|
-
return void 0;
|
|
77575
|
-
const [delim, parts] = arg;
|
|
77576
|
-
if (typeof delim !== "string" || !Array.isArray(parts))
|
|
77577
|
-
return void 0;
|
|
77578
|
-
const resolved = [];
|
|
77579
|
-
for (const part of parts) {
|
|
77580
|
-
const r = resolveImageIntrinsic(part, resources, context);
|
|
77581
|
-
if (r === void 0)
|
|
77582
|
-
return void 0;
|
|
77583
|
-
resolved.push(r);
|
|
77584
|
-
}
|
|
77585
|
-
return resolved.join(delim);
|
|
77586
|
-
}
|
|
77587
|
-
if (intrinsic === "Fn::Sub") {
|
|
77588
|
-
let template;
|
|
77589
|
-
if (typeof arg === "string")
|
|
77590
|
-
template = arg;
|
|
77591
|
-
else if (Array.isArray(arg) && typeof arg[0] === "string")
|
|
77592
|
-
template = arg[0];
|
|
77593
|
-
if (template === void 0)
|
|
77594
|
-
return void 0;
|
|
77595
|
-
const out = substituteImagePlaceholders(template, resources, context);
|
|
77596
|
-
if (out.includes("${"))
|
|
77597
|
-
return void 0;
|
|
77598
|
-
return out;
|
|
77599
|
-
}
|
|
77600
|
-
return void 0;
|
|
77601
|
-
}
|
|
77602
77902
|
function parseVolume(raw, idx, taskLogicalId) {
|
|
77603
77903
|
if (!raw || typeof raw !== "object") {
|
|
77604
77904
|
throw new EcsTaskResolutionError(`Task '${taskLogicalId}' Volumes[${idx}] is not an object.`);
|
|
@@ -78659,14 +78959,16 @@ async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
|
78659
78959
|
if (!candidate)
|
|
78660
78960
|
return void 0;
|
|
78661
78961
|
const needs = detectEcsImageResolutionNeeds(candidate);
|
|
78662
|
-
if (!needs.needsPseudoParameters && !needs.needsStateResources)
|
|
78962
|
+
if (!needs.needsPseudoParameters && !needs.needsStateResources && !needs.needsEnvOrSecretSubstitution) {
|
|
78663
78963
|
return void 0;
|
|
78964
|
+
}
|
|
78664
78965
|
const ctx = {};
|
|
78665
|
-
|
|
78966
|
+
const wantsPseudoForEnvOrSecret = options.fromState && needs.needsEnvOrSecretSubstitution;
|
|
78967
|
+
if (needs.needsPseudoParameters || wantsPseudoForEnvOrSecret) {
|
|
78666
78968
|
const region = options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"] ?? candidate.region;
|
|
78667
78969
|
if (!region) {
|
|
78668
78970
|
logger.warn(
|
|
78669
|
-
"
|
|
78971
|
+
"Resolver references ${AWS::Region} but cdkd could not determine the target region. Pass --region, set AWS_REGION, or declare env.region on the CDK stack."
|
|
78670
78972
|
);
|
|
78671
78973
|
}
|
|
78672
78974
|
let accountId;
|
|
@@ -78674,7 +78976,7 @@ async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
|
78674
78976
|
accountId = await resolveCallerAccountId(region);
|
|
78675
78977
|
} catch (err) {
|
|
78676
78978
|
logger.warn(
|
|
78677
|
-
`
|
|
78979
|
+
`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.`
|
|
78678
78980
|
);
|
|
78679
78981
|
}
|
|
78680
78982
|
const partitionAndSuffix = region ? derivePartitionAndUrlSuffix(region) : void 0;
|
|
@@ -78687,7 +78989,8 @@ async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
|
78687
78989
|
}
|
|
78688
78990
|
};
|
|
78689
78991
|
}
|
|
78690
|
-
|
|
78992
|
+
const wantsState = needs.needsStateResources || needs.needsEnvOrSecretSubstitution;
|
|
78993
|
+
if (options.fromState && wantsState) {
|
|
78691
78994
|
const loaded = await loadStateForStack(candidate.stackName, candidate.region, {
|
|
78692
78995
|
...options.stackRegion !== void 0 && { stackRegion: options.stackRegion },
|
|
78693
78996
|
...options.stateBucket !== void 0 && { stateBucket: options.stateBucket },
|
|
@@ -78702,6 +79005,10 @@ async function buildEcsImageResolutionContext(target, stacks, options) {
|
|
|
78702
79005
|
logger.warn(
|
|
78703
79006
|
"Container Image references a same-stack AWS::ECR::Repository. Pass --from-state to substitute the deployed repository URI (requires the stack to have been deployed via cdkd deploy). Otherwise the resolver will surface its existing error."
|
|
78704
79007
|
);
|
|
79008
|
+
} else if (!options.fromState && needs.needsEnvOrSecretSubstitution) {
|
|
79009
|
+
logger.warn(
|
|
79010
|
+
"Container Environment / Secrets entries contain CloudFormation intrinsics (Ref / Fn::GetAtt / Fn::Sub / Fn::Join). Pass --from-state to substitute them against the deployed cdkd state. Without --from-state these entries are dropped (per-key warnings will follow)."
|
|
79011
|
+
);
|
|
78705
79012
|
}
|
|
78706
79013
|
return ctx;
|
|
78707
79014
|
}
|
|
@@ -80286,7 +80593,7 @@ function reorderArgs(argv) {
|
|
|
80286
80593
|
}
|
|
80287
80594
|
async function main() {
|
|
80288
80595
|
const program = new Command18();
|
|
80289
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.91.
|
|
80596
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.91.3");
|
|
80290
80597
|
program.addCommand(createBootstrapCommand());
|
|
80291
80598
|
program.addCommand(createSynthCommand());
|
|
80292
80599
|
program.addCommand(createListCommand());
|