@go-to-k/cdkd 0.124.0 → 0.125.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +327 -23
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -19,9 +19,9 @@ import { CloudFrontClient, CreateCloudFrontOriginAccessIdentityCommand, CreateDi
|
|
|
19
19
|
import { CloudWatchClient, DeleteAlarmsCommand, DescribeAlarmsCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$4, PutMetricAlarmCommand, TagResourceCommand as TagResourceCommand$6, UntagResourceCommand as UntagResourceCommand$6 } from "@aws-sdk/client-cloudwatch";
|
|
20
20
|
import { CloudWatchLogsClient, CreateLogGroupCommand, DeleteDataProtectionPolicyCommand, DeleteIndexPolicyCommand, DeleteLogGroupCommand, DeleteRetentionPolicyCommand, DescribeIndexPoliciesCommand, DescribeLogGroupsCommand, GetDataProtectionPolicyCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$5, PutBearerTokenAuthenticationCommand, PutDataProtectionPolicyCommand, PutIndexPolicyCommand, PutLogGroupDeletionProtectionCommand, PutRetentionPolicyCommand, ResourceAlreadyExistsException, ResourceNotFoundException as ResourceNotFoundException$4, TagResourceCommand as TagResourceCommand$7, UntagResourceCommand as UntagResourceCommand$7 } from "@aws-sdk/client-cloudwatch-logs";
|
|
21
21
|
import { BedrockAgentCoreControlClient, CreateAgentRuntimeCommand, DeleteAgentRuntimeCommand, GetAgentRuntimeCommand, ResourceNotFoundException as ResourceNotFoundException$5, UpdateAgentRuntimeCommand } from "@aws-sdk/client-bedrock-agentcore-control";
|
|
22
|
-
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { cpSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
23
23
|
import * as path from "node:path";
|
|
24
|
-
import { dirname, isAbsolute, resolve } from "node:path";
|
|
24
|
+
import { dirname, isAbsolute, join, normalize, resolve } from "node:path";
|
|
25
25
|
import { execFile, spawn } from "node:child_process";
|
|
26
26
|
import { tmpdir } from "node:os";
|
|
27
27
|
import { AssociateVPCWithHostedZoneCommand, ChangeResourceRecordSetsCommand, ChangeTagsForResourceCommand, CreateHostedZoneCommand, CreateQueryLoggingConfigCommand, DeleteHostedZoneCommand, DeleteQueryLoggingConfigCommand, DisassociateVPCFromHostedZoneCommand, GetHostedZoneCommand, ListHostedZonesByNameCommand, ListHostedZonesCommand, ListQueryLoggingConfigsCommand, ListResourceRecordSetsCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$6, Route53Client, UpdateHostedZoneCommentCommand } from "@aws-sdk/client-route-53";
|
|
@@ -57,6 +57,9 @@ import { CreateNamespaceCommand, CreateTableBucketCommand, CreateTableCommand as
|
|
|
57
57
|
import { AttachTrafficSourcesCommand, AutoScalingClient, CreateAutoScalingGroupCommand, DeleteAutoScalingGroupCommand, DeleteLifecycleHookCommand, DeleteNotificationConfigurationCommand, DescribeAutoScalingGroupsCommand, DescribeLifecycleHooksCommand, DescribeNotificationConfigurationsCommand, DescribeTrafficSourcesCommand, DetachTrafficSourcesCommand, DisableMetricsCollectionCommand, EnableMetricsCollectionCommand, PutLifecycleHookCommand, PutNotificationConfigurationCommand, UpdateAutoScalingGroupCommand } from "@aws-sdk/client-auto-scaling";
|
|
58
58
|
import * as readline from "node:readline/promises";
|
|
59
59
|
import { Document, Pair, Scalar, YAMLMap, YAMLSeq, parse as parse$1, stringify } from "yaml";
|
|
60
|
+
import { mkdir, mkdtemp } from "node:fs/promises";
|
|
61
|
+
import { Readable } from "node:stream";
|
|
62
|
+
import { pipeline } from "node:stream/promises";
|
|
60
63
|
import { createServer } from "node:net";
|
|
61
64
|
import { promisify } from "node:util";
|
|
62
65
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
@@ -36687,16 +36690,23 @@ function resolveAssetCodePath$1(stack, logicalId, resource) {
|
|
|
36687
36690
|
* bind mounts at the same target so cdkd cannot rely on overlay
|
|
36688
36691
|
* layering.
|
|
36689
36692
|
*
|
|
36690
|
-
* **
|
|
36693
|
+
* **Same-stack handling** (`{Ref: <Id>}` / `{Fn::GetAtt: [<Id>, 'Ref']}`):
|
|
36691
36694
|
*
|
|
36692
|
-
* -
|
|
36693
|
-
*
|
|
36694
|
-
*
|
|
36695
|
-
*
|
|
36696
|
-
*
|
|
36697
|
-
*
|
|
36698
|
-
*
|
|
36699
|
-
*
|
|
36695
|
+
* - Refs that don't point at an `AWS::Lambda::LayerVersion` resource
|
|
36696
|
+
* hard-error — almost always a typo'd logical ID.
|
|
36697
|
+
* - Refs to a `LayerVersion` whose `Metadata['aws:asset:path']` is
|
|
36698
|
+
* missing hard-error — the layer's content is `S3Bucket` / `S3Key`
|
|
36699
|
+
* from outside cdk.out and there's no local directory to bind-mount.
|
|
36700
|
+
*
|
|
36701
|
+
* **Literal-ARN handling** (issue #448): entries shaped like the string
|
|
36702
|
+
* `arn:aws:lambda:<region>:<account>:layer:<name>:<version>` are parsed
|
|
36703
|
+
* into a `{kind: 'arn', ...}` resolved layer. The actual
|
|
36704
|
+
* `lambda:GetLayerVersion` + presigned-URL download + unzip happens
|
|
36705
|
+
* later in the CLI (`materializeLayerFromArn(...)`), which can optionally
|
|
36706
|
+
* `sts:AssumeRole` into the layer's account when the dev's default
|
|
36707
|
+
* credentials cannot read it. Covers AWS-published public layers (Lambda
|
|
36708
|
+
* Powertools, Datadog Extension, etc.) and cross-account / cross-region
|
|
36709
|
+
* shared layers.
|
|
36700
36710
|
*/
|
|
36701
36711
|
function resolveLambdaLayers(stack, logicalId, props) {
|
|
36702
36712
|
const layers = props["Layers"];
|
|
@@ -36707,13 +36717,24 @@ function resolveLambdaLayers(stack, logicalId, props) {
|
|
|
36707
36717
|
const out = [];
|
|
36708
36718
|
for (let i = 0; i < layers.length; i++) {
|
|
36709
36719
|
const entry = layers[i];
|
|
36720
|
+
if (typeof entry === "string") {
|
|
36721
|
+
const parsed = parseLayerVersionArn(entry);
|
|
36722
|
+
if (!parsed) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has a Layers entry [${i}] cdkd cannot resolve locally: literal string '${entry}'. Expected a same-stack Ref / Fn::GetAtt to an AWS::Lambda::LayerVersion OR a literal layer-version ARN of the form arn:aws:lambda:<region>:<account>:layer:<name>:<version>.`);
|
|
36723
|
+
out.push({
|
|
36724
|
+
kind: "arn",
|
|
36725
|
+
logicalId: parsed.arn,
|
|
36726
|
+
...parsed
|
|
36727
|
+
});
|
|
36728
|
+
continue;
|
|
36729
|
+
}
|
|
36710
36730
|
const layerLogicalId = pickLayerLogicalId(entry);
|
|
36711
|
-
if (!layerLogicalId) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has a Layers entry [${i}] cdkd cannot resolve locally: ${describeLayerEntry(entry)}.
|
|
36731
|
+
if (!layerLogicalId) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' has a Layers entry [${i}] cdkd cannot resolve locally: ${describeLayerEntry(entry)}. Expected a same-stack Ref / Fn::GetAtt to an AWS::Lambda::LayerVersion OR a literal layer-version ARN of the form arn:aws:lambda:<region>:<account>:layer:<name>:<version>.`);
|
|
36712
36732
|
const layerResource = resources[layerLogicalId];
|
|
36713
36733
|
if (!layerResource) throw new LocalInvokeResolutionError(`Lambda '${logicalId}' Layers entry [${i}] references '${layerLogicalId}', but no resource with that logical ID exists in stack '${stack.stackName}'.`);
|
|
36714
36734
|
if (layerResource.Type !== "AWS::Lambda::LayerVersion") throw new LocalInvokeResolutionError(`Lambda '${logicalId}' Layers entry [${i}] references '${layerLogicalId}' (${layerResource.Type}), which is not an AWS::Lambda::LayerVersion.`);
|
|
36715
36735
|
const assetPath = resolveAssetCodePath$1(stack, layerLogicalId, layerResource);
|
|
36716
36736
|
out.push({
|
|
36737
|
+
kind: "asset",
|
|
36717
36738
|
logicalId: layerLogicalId,
|
|
36718
36739
|
assetPath
|
|
36719
36740
|
});
|
|
@@ -36721,6 +36742,29 @@ function resolveLambdaLayers(stack, logicalId, props) {
|
|
|
36721
36742
|
return out;
|
|
36722
36743
|
}
|
|
36723
36744
|
/**
|
|
36745
|
+
* Parse a Lambda layer-version ARN string into its segments.
|
|
36746
|
+
*
|
|
36747
|
+
* Returns `undefined` for anything that does not match the strict
|
|
36748
|
+
* `arn:aws:lambda:<region>:<account>:layer:<name>:<version>` shape so
|
|
36749
|
+
* the caller can produce a clearer error than a silent
|
|
36750
|
+
* misinterpretation of hand-edited templates. The partition segment
|
|
36751
|
+
* accepts `aws` / `aws-cn` / `aws-us-gov` so GovCloud / China-region
|
|
36752
|
+
* ARNs work without code changes.
|
|
36753
|
+
*
|
|
36754
|
+
* Exported for unit testing.
|
|
36755
|
+
*/
|
|
36756
|
+
function parseLayerVersionArn(input) {
|
|
36757
|
+
const m = /^arn:(aws|aws-cn|aws-us-gov):lambda:([a-z]{2}-(?:[a-z]+-){1,2}\d+):(\d{12}):layer:([A-Za-z0-9_-]+):(\d+)$/.exec(input);
|
|
36758
|
+
if (!m) return void 0;
|
|
36759
|
+
return {
|
|
36760
|
+
arn: input,
|
|
36761
|
+
region: m[2],
|
|
36762
|
+
accountId: m[3],
|
|
36763
|
+
name: m[4],
|
|
36764
|
+
version: m[5]
|
|
36765
|
+
};
|
|
36766
|
+
}
|
|
36767
|
+
/**
|
|
36724
36768
|
* Walk a single Layers-array entry and return the referenced layer's
|
|
36725
36769
|
* logical ID — or `undefined` for shapes we don't try to resolve in v1.
|
|
36726
36770
|
*
|
|
@@ -36789,6 +36833,195 @@ function notFoundError$1(target, stack, resources) {
|
|
|
36789
36833
|
return new LocalInvokeResolutionError(msg.trimEnd());
|
|
36790
36834
|
}
|
|
36791
36835
|
|
|
36836
|
+
//#endregion
|
|
36837
|
+
//#region src/local/layer-arn-materializer.ts
|
|
36838
|
+
var LayerMaterializationError = class LayerMaterializationError extends Error {
|
|
36839
|
+
constructor(message) {
|
|
36840
|
+
super(message);
|
|
36841
|
+
this.name = "LayerMaterializationError";
|
|
36842
|
+
Object.setPrototypeOf(this, LayerMaterializationError.prototype);
|
|
36843
|
+
}
|
|
36844
|
+
};
|
|
36845
|
+
async function materializeLayerFromArn(layer, options = {}) {
|
|
36846
|
+
const logger = getLogger();
|
|
36847
|
+
let credentials;
|
|
36848
|
+
if (options.roleArn) try {
|
|
36849
|
+
credentials = await assumeRoleForLayer(options.roleArn, layer.region, options);
|
|
36850
|
+
logger.debug(`Layer ${layer.arn}: assumed role ${options.roleArn} for GetLayerVersion`);
|
|
36851
|
+
} catch (err) {
|
|
36852
|
+
throw new LayerMaterializationError(`Layer ${layer.arn}: STS AssumeRole(${options.roleArn}) failed: ${errMsg(err)}. Check the role trust policy permits your principal and sts:AssumeRole is allowed.`);
|
|
36853
|
+
}
|
|
36854
|
+
let presignedUrl;
|
|
36855
|
+
try {
|
|
36856
|
+
presignedUrl = await fetchLayerContentUrl(layer, credentials, options);
|
|
36857
|
+
} catch (err) {
|
|
36858
|
+
const hint = looksLikeAccessDenied(err) ? " GetLayerVersion access denied; check the credentials / role can read the layer (grant lambda:GetLayerVersion on the layer ARN, or pass --layer-role-arn <arn> to assume a role in the layer account)." : "";
|
|
36859
|
+
throw new LayerMaterializationError(`Layer ${layer.arn}: GetLayerVersion failed in region ${layer.region}: ${errMsg(err)}.${hint}`);
|
|
36860
|
+
}
|
|
36861
|
+
let zipBytes;
|
|
36862
|
+
try {
|
|
36863
|
+
zipBytes = await downloadPresignedZip(presignedUrl, options);
|
|
36864
|
+
} catch (err) {
|
|
36865
|
+
throw new LayerMaterializationError(`Layer ${layer.arn}: failed to download layer ZIP from the presigned URL: ${errMsg(err)}.`);
|
|
36866
|
+
}
|
|
36867
|
+
const dir = await mkdtemp(join(tmpdir(), `cdkd-local-arn-layer-${layer.name}-${layer.version}-`));
|
|
36868
|
+
try {
|
|
36869
|
+
await unzipBufferToDirectory(zipBytes, dir);
|
|
36870
|
+
} catch (err) {
|
|
36871
|
+
try {
|
|
36872
|
+
rmSync(dir, {
|
|
36873
|
+
recursive: true,
|
|
36874
|
+
force: true
|
|
36875
|
+
});
|
|
36876
|
+
} catch {}
|
|
36877
|
+
throw new LayerMaterializationError(`Layer ${layer.arn}: failed to unzip layer contents into '${dir}': ${errMsg(err)}.`);
|
|
36878
|
+
}
|
|
36879
|
+
return dir;
|
|
36880
|
+
}
|
|
36881
|
+
async function fetchLayerContentUrl(layer, credentials, options) {
|
|
36882
|
+
const client = (options.lambdaClientFactory ?? await defaultLambdaClientFactory())(layer.region, credentials);
|
|
36883
|
+
try {
|
|
36884
|
+
const command = await buildGetLayerVersionCommand(`arn:aws:lambda:${layer.region}:${layer.accountId}:layer:${layer.name}`, Number(layer.version));
|
|
36885
|
+
const url = (await client.send(command))?.Content?.Location;
|
|
36886
|
+
if (!url || typeof url !== "string") throw new Error("GetLayerVersion response did not include Content.Location (presigned ZIP URL)");
|
|
36887
|
+
return url;
|
|
36888
|
+
} finally {
|
|
36889
|
+
client.destroy?.();
|
|
36890
|
+
}
|
|
36891
|
+
}
|
|
36892
|
+
async function assumeRoleForLayer(roleArn, region, options) {
|
|
36893
|
+
const client = (options.stsClientFactory ?? await defaultStsClientFactory())(region);
|
|
36894
|
+
try {
|
|
36895
|
+
const command = await buildAssumeRoleCommand(roleArn);
|
|
36896
|
+
const creds = (await client.send(command))?.Credentials;
|
|
36897
|
+
if (!creds?.AccessKeyId || !creds.SecretAccessKey) throw new Error("AssumeRole returned no Credentials");
|
|
36898
|
+
return {
|
|
36899
|
+
accessKeyId: creds.AccessKeyId,
|
|
36900
|
+
secretAccessKey: creds.SecretAccessKey,
|
|
36901
|
+
...creds.SessionToken !== void 0 && { sessionToken: creds.SessionToken }
|
|
36902
|
+
};
|
|
36903
|
+
} finally {
|
|
36904
|
+
client.destroy?.();
|
|
36905
|
+
}
|
|
36906
|
+
}
|
|
36907
|
+
async function defaultLambdaClientFactory() {
|
|
36908
|
+
const { LambdaClient } = await import("@aws-sdk/client-lambda");
|
|
36909
|
+
return (region, credentials) => new LambdaClient({
|
|
36910
|
+
region,
|
|
36911
|
+
...credentials && { credentials: {
|
|
36912
|
+
accessKeyId: credentials.accessKeyId,
|
|
36913
|
+
secretAccessKey: credentials.secretAccessKey,
|
|
36914
|
+
...credentials.sessionToken !== void 0 && { sessionToken: credentials.sessionToken }
|
|
36915
|
+
} }
|
|
36916
|
+
});
|
|
36917
|
+
}
|
|
36918
|
+
async function defaultStsClientFactory() {
|
|
36919
|
+
const { STSClient } = await import("@aws-sdk/client-sts");
|
|
36920
|
+
return (region) => new STSClient({ region });
|
|
36921
|
+
}
|
|
36922
|
+
async function buildGetLayerVersionCommand(layerArn, versionNumber) {
|
|
36923
|
+
const { GetLayerVersionCommand } = await import("@aws-sdk/client-lambda");
|
|
36924
|
+
return new GetLayerVersionCommand({
|
|
36925
|
+
LayerName: layerArn,
|
|
36926
|
+
VersionNumber: versionNumber
|
|
36927
|
+
});
|
|
36928
|
+
}
|
|
36929
|
+
async function buildAssumeRoleCommand(roleArn) {
|
|
36930
|
+
const { AssumeRoleCommand } = await import("@aws-sdk/client-sts");
|
|
36931
|
+
return new AssumeRoleCommand({
|
|
36932
|
+
RoleArn: roleArn,
|
|
36933
|
+
RoleSessionName: `cdkd-local-layer-${Date.now()}`,
|
|
36934
|
+
DurationSeconds: 3600
|
|
36935
|
+
});
|
|
36936
|
+
}
|
|
36937
|
+
async function downloadPresignedZip(presignedUrl, options) {
|
|
36938
|
+
if (options.fetchZip) return options.fetchZip(presignedUrl);
|
|
36939
|
+
const response = await fetch(presignedUrl);
|
|
36940
|
+
if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText} from layer Content.Location URL`);
|
|
36941
|
+
const buf = await response.arrayBuffer();
|
|
36942
|
+
return new Uint8Array(buf);
|
|
36943
|
+
}
|
|
36944
|
+
/**
|
|
36945
|
+
* Minimal ZIP unzipper that handles the subset of the ZIP format Lambda
|
|
36946
|
+
* layer ZIPs ever use (DEFLATE compression method 8, STORE method 0).
|
|
36947
|
+
* Avoids bringing in a heavyweight dep for a 50-line task.
|
|
36948
|
+
*
|
|
36949
|
+
* Path-traversal guard: every entry's relative path is `normalize()`d
|
|
36950
|
+
* and rejected if the resulting absolute path escapes `destDir` (the
|
|
36951
|
+
* "Zip Slip" CVE class). Symlinks inside the ZIP are also rejected for
|
|
36952
|
+
* the same reason — they could point at arbitrary host paths.
|
|
36953
|
+
*/
|
|
36954
|
+
async function unzipBufferToDirectory(zipBytes, destDir) {
|
|
36955
|
+
const view = new DataView(zipBytes.buffer, zipBytes.byteOffset, zipBytes.byteLength);
|
|
36956
|
+
const eocdSig = 101010256;
|
|
36957
|
+
const minScan = Math.max(0, zipBytes.byteLength - 65535 - 22);
|
|
36958
|
+
let eocdOffset = -1;
|
|
36959
|
+
for (let i = zipBytes.byteLength - 22; i >= minScan; i--) if (view.getUint32(i, true) === eocdSig) {
|
|
36960
|
+
eocdOffset = i;
|
|
36961
|
+
break;
|
|
36962
|
+
}
|
|
36963
|
+
if (eocdOffset < 0) throw new Error("Not a ZIP file (no End of Central Directory record found)");
|
|
36964
|
+
const totalEntries = view.getUint16(eocdOffset + 10, true);
|
|
36965
|
+
const cdSize = view.getUint32(eocdOffset + 12, true);
|
|
36966
|
+
const cdOffset = view.getUint32(eocdOffset + 16, true);
|
|
36967
|
+
const destAbsolute = resolve(destDir);
|
|
36968
|
+
let cursor = cdOffset;
|
|
36969
|
+
const cdEnd = cdOffset + cdSize;
|
|
36970
|
+
let parsed = 0;
|
|
36971
|
+
while (cursor < cdEnd && parsed < totalEntries) {
|
|
36972
|
+
if (view.getUint32(cursor, true) !== 33639248) throw new Error(`Corrupt ZIP: missing Central Directory header at offset ${cursor}`);
|
|
36973
|
+
const compressionMethod = view.getUint16(cursor + 10, true);
|
|
36974
|
+
const compressedSize = view.getUint32(cursor + 20, true);
|
|
36975
|
+
const uncompressedSize = view.getUint32(cursor + 24, true);
|
|
36976
|
+
const fileNameLength = view.getUint16(cursor + 28, true);
|
|
36977
|
+
const extraFieldLength = view.getUint16(cursor + 30, true);
|
|
36978
|
+
const fileCommentLength = view.getUint16(cursor + 32, true);
|
|
36979
|
+
const externalAttrs = view.getUint32(cursor + 38, true);
|
|
36980
|
+
const localHeaderOffset = view.getUint32(cursor + 42, true);
|
|
36981
|
+
const fileName = new TextDecoder("utf-8").decode(zipBytes.subarray(cursor + 46, cursor + 46 + fileNameLength));
|
|
36982
|
+
cursor += 46 + fileNameLength + extraFieldLength + fileCommentLength;
|
|
36983
|
+
parsed++;
|
|
36984
|
+
const targetPath = resolve(destAbsolute, normalize(fileName));
|
|
36985
|
+
if (!targetPath.startsWith(destAbsolute + (destAbsolute.endsWith("/") ? "" : "/"))) throw new Error(`Refusing to extract entry '${fileName}' — path escapes the destination directory`);
|
|
36986
|
+
if ((externalAttrs >>> 16 & 61440) === 40960) throw new Error(`Refusing to extract symlink entry '${fileName}' from layer ZIP (security)`);
|
|
36987
|
+
if (fileName.endsWith("/")) {
|
|
36988
|
+
await mkdir(targetPath, { recursive: true });
|
|
36989
|
+
continue;
|
|
36990
|
+
}
|
|
36991
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
36992
|
+
if (view.getUint32(localHeaderOffset, true) !== 67324752) throw new Error(`Corrupt ZIP: missing Local File Header for '${fileName}'`);
|
|
36993
|
+
const lfhFileNameLength = view.getUint16(localHeaderOffset + 26, true);
|
|
36994
|
+
const lfhExtraFieldLength = view.getUint16(localHeaderOffset + 28, true);
|
|
36995
|
+
const dataOffset = localHeaderOffset + 30 + lfhFileNameLength + lfhExtraFieldLength;
|
|
36996
|
+
const compressedData = zipBytes.subarray(dataOffset, dataOffset + compressedSize);
|
|
36997
|
+
let payload;
|
|
36998
|
+
if (compressionMethod === 0) payload = compressedData;
|
|
36999
|
+
else if (compressionMethod === 8) payload = await inflateRaw(compressedData);
|
|
37000
|
+
else throw new Error(`Unsupported ZIP compression method ${compressionMethod} for entry '${fileName}' (only STORE and DEFLATE supported)`);
|
|
37001
|
+
if (payload.length !== uncompressedSize && compressionMethod !== 0) throw new Error(`ZIP entry '${fileName}': inflate produced ${payload.length} bytes, expected ${uncompressedSize}`);
|
|
37002
|
+
await pipeline(Readable.from(payload), createWriteStream(targetPath));
|
|
37003
|
+
}
|
|
37004
|
+
}
|
|
37005
|
+
async function inflateRaw(data) {
|
|
37006
|
+
const { inflateRaw: inflate } = await import("node:zlib");
|
|
37007
|
+
return new Promise((resolveP, rejectP) => {
|
|
37008
|
+
inflate(data, (err, out) => {
|
|
37009
|
+
if (err) rejectP(err);
|
|
37010
|
+
else resolveP(out);
|
|
37011
|
+
});
|
|
37012
|
+
});
|
|
37013
|
+
}
|
|
37014
|
+
function errMsg(err) {
|
|
37015
|
+
return err instanceof Error ? err.message : String(err);
|
|
37016
|
+
}
|
|
37017
|
+
function looksLikeAccessDenied(err) {
|
|
37018
|
+
if (!(err instanceof Error)) return false;
|
|
37019
|
+
const name = err.name ?? "";
|
|
37020
|
+
const code = err.Code ?? "";
|
|
37021
|
+
const message = err.message ?? "";
|
|
37022
|
+
return name === "AccessDeniedException" || code === "AccessDeniedException" || /access denied/i.test(message) || /not authorized/i.test(message);
|
|
37023
|
+
}
|
|
37024
|
+
|
|
36792
37025
|
//#endregion
|
|
36793
37026
|
//#region src/local/env-resolver.ts
|
|
36794
37027
|
/**
|
|
@@ -43667,7 +43900,8 @@ async function localStartApiCommand(target, options) {
|
|
|
43667
43900
|
stsRegion: options.region ?? process.env["AWS_REGION"] ?? process.env["AWS_DEFAULT_REGION"],
|
|
43668
43901
|
inlineTmpDirs,
|
|
43669
43902
|
layerTmpDirs,
|
|
43670
|
-
stateByStack
|
|
43903
|
+
stateByStack,
|
|
43904
|
+
...options.layerRoleArn !== void 0 && { layerRoleArn: options.layerRoleArn }
|
|
43671
43905
|
});
|
|
43672
43906
|
specs.set(logicalId, spec);
|
|
43673
43907
|
}
|
|
@@ -43977,10 +44211,10 @@ function warnIamRoutes(routesWithAuth) {
|
|
|
43977
44211
|
* missing, runtime not supported).
|
|
43978
44212
|
*/
|
|
43979
44213
|
async function buildContainerSpec(args) {
|
|
43980
|
-
const { logicalId, stacks, overrides, assumeRole, containerHost, debugPort, stsRegion, inlineTmpDirs, layerTmpDirs, stateByStack } = args;
|
|
44214
|
+
const { logicalId, stacks, overrides, assumeRole, containerHost, debugPort, stsRegion, inlineTmpDirs, layerTmpDirs, stateByStack, layerRoleArn } = args;
|
|
43981
44215
|
const lambda = resolveLambdaByLogicalId(logicalId, stacks);
|
|
43982
44216
|
const codeDir = lambda.codePath ?? materializeInlineCode$1(lambda.handler, lambda.inlineCode ?? "", resolveRuntimeFileExtension(lambda.runtime), inlineTmpDirs);
|
|
43983
|
-
const optDir = materializeLambdaLayers$1(lambda.layers, layerTmpDirs);
|
|
44217
|
+
const optDir = await materializeLambdaLayers$1(lambda.layers, layerTmpDirs, layerRoleArn);
|
|
43984
44218
|
let templateEnv = getTemplateEnv$1(lambda.resource);
|
|
43985
44219
|
const stateBundle = stateByStack.get(lambda.stack.stackName);
|
|
43986
44220
|
let stateAudit;
|
|
@@ -44039,22 +44273,46 @@ async function buildContainerSpec(args) {
|
|
|
44039
44273
|
*
|
|
44040
44274
|
* Three branches:
|
|
44041
44275
|
* - 0 layers → `undefined` (no `/opt` mount).
|
|
44042
|
-
* - 1 layer → bind-mount the layer's asset dir directly (no copy)
|
|
44276
|
+
* - 1 layer → bind-mount the layer's asset dir directly (no copy)
|
|
44277
|
+
* when the entry is a same-stack asset. Literal-ARN entries always
|
|
44278
|
+
* pre-materialize first.
|
|
44043
44279
|
* - 2+ layers → copy each into a fresh tmpdir IN ORDER (later
|
|
44044
44280
|
* layers overwrite earlier files via `cpSync({force: true})`),
|
|
44045
44281
|
* bind-mount the tmpdir at `/opt`. Records the tmpdir in
|
|
44046
44282
|
* `layerTmpDirs` so `shutdown(...)` removes it.
|
|
44047
44283
|
*
|
|
44284
|
+
* Issue #448: literal-ARN entries (`{kind: 'arn', ...}`) are downloaded
|
|
44285
|
+
* + unzipped via `lambda:GetLayerVersion` BEFORE the cpSync-merge
|
|
44286
|
+
* branches run. Every per-ARN tmpdir is also recorded in `layerTmpDirs`
|
|
44287
|
+
* so the same shutdown path cleans it up — even for the single-layer
|
|
44288
|
+
* fast path that bind-mounts the dir directly.
|
|
44289
|
+
*
|
|
44048
44290
|
* AWS Lambda's actual runtime extracts every layer ZIP into `/opt`
|
|
44049
44291
|
* in template order — the merge mirrors that. Docker rejects multiple
|
|
44050
44292
|
* `-v ...:/opt:ro` entries at the same target, so cdkd can't rely on
|
|
44051
44293
|
* overlay layering and must produce a single merged dir on the host.
|
|
44052
44294
|
*/
|
|
44053
|
-
function materializeLambdaLayers$1(layers, layerTmpDirs) {
|
|
44295
|
+
async function materializeLambdaLayers$1(layers, layerTmpDirs, layerRoleArn) {
|
|
44054
44296
|
if (layers.length === 0) return void 0;
|
|
44055
|
-
|
|
44297
|
+
const flat = [];
|
|
44298
|
+
for (const layer of layers) {
|
|
44299
|
+
if (layer.kind === "asset") {
|
|
44300
|
+
flat.push({
|
|
44301
|
+
logicalId: layer.logicalId,
|
|
44302
|
+
assetPath: layer.assetPath
|
|
44303
|
+
});
|
|
44304
|
+
continue;
|
|
44305
|
+
}
|
|
44306
|
+
const dir = await materializeLayerFromArn(layer, { ...layerRoleArn !== void 0 && { roleArn: layerRoleArn } });
|
|
44307
|
+
layerTmpDirs.add(dir);
|
|
44308
|
+
flat.push({
|
|
44309
|
+
logicalId: layer.arn,
|
|
44310
|
+
assetPath: dir
|
|
44311
|
+
});
|
|
44312
|
+
}
|
|
44313
|
+
if (flat.length === 1) return flat[0].assetPath;
|
|
44056
44314
|
const dir = mkdtempSync(path.join(tmpdir(), "cdkd-local-start-api-layers-"));
|
|
44057
|
-
for (const layer of
|
|
44315
|
+
for (const layer of flat) cpSync(layer.assetPath, dir, {
|
|
44058
44316
|
recursive: true,
|
|
44059
44317
|
force: true
|
|
44060
44318
|
});
|
|
@@ -44459,7 +44717,7 @@ function parseDebugPort(raw) {
|
|
|
44459
44717
|
* Builder for the `start-api` subcommand. Wired up by `local.ts`.
|
|
44460
44718
|
*/
|
|
44461
44719
|
function createLocalStartApiCommand() {
|
|
44462
|
-
const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and REST v1 AWS_IAM (SigV4 signature verification only — IAM policy evaluation is NOT emulated; see docs/local-emulation.md). When JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line — local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail.").argument("[target]", "Optional API filter. Accepts the bare CDK logical id ('MyHttpApi'; single-stack apps only), stack-qualified logical id ('MyStack:MyHttpApi'), full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). When omitted, every discovered API gets its own server. Mirrors `cdkd local invoke` / `cdkd local run-task` target syntax.").addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when cdk.out/ or asset directories change. Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <target> argument instead. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--from-state", "Read cdkd S3 state for every routed stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::Join (and AWS pseudo parameters) in Lambda env vars with the deployed physical IDs / attributes. Off by default — pre-PR warn-and-drop semantics are preserved. Turn on for stacks already deployed via cdkd deploy. Mirrors `cdkd local invoke --from-state` / `cdkd local run-task --from-state`. Re-runs against fresh state on every hot-reload firing (--watch).").default(false)).addOption(new Option("--stack-region <region>", "Region of the cdkd state record to read (used with --from-state when the same stack name has state in multiple regions).")).addOption(new Option("--allow-unverified-sigv4", "Opt-in: allow AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id, OR no local AWS credentials configured) to pass through with a placeholder principalId. DEFAULT off — fail-closed so unauthenticated bypass is impossible against `event.requestContext.identity.accessKey`-trusting handler code. Use only in dev loops where you understand the risk.").default(false)).action(withErrorHandling(localStartApiCommand));
|
|
44720
|
+
const startApi = new Command("start-api").description("Run a long-running local HTTP server that maps API Gateway routes (REST v1, HTTP API, Function URL) to Lambda invocations against the AWS Lambda Runtime Interface Emulator (Docker required). Supports Lambda TOKEN/REQUEST authorizers, Cognito User Pool / HTTP v2 JWT authorizers, and REST v1 AWS_IAM (SigV4 signature verification only — IAM policy evaluation is NOT emulated; see docs/local-emulation.md). When JWKS is unreachable, JWT authorizers fall back to pass-through (every token accepted) with a warn line — local dev fallback. VPC-config Lambdas run locally and surface a warn line at startup; their containers do NOT get attached to the deployed VPC subnets, so calls to private RDS / ElastiCache will fail.").argument("[target]", "Optional API filter. Accepts the bare CDK logical id ('MyHttpApi'; single-stack apps only), stack-qualified logical id ('MyStack:MyHttpApi'), full CDK Construct path ('MyStack/MyHttpApi/Resource'), or an ancestor Construct path that prefix-matches ('MyStack/MyHttpApi'). When omitted, every discovered API gets its own server. Mirrors `cdkd local invoke` / `cdkd local run-task` target syntax.").addOption(new Option("--port <port>", "HTTP server port (default: auto-allocate)").default("0")).addOption(new Option("--host <host>", "Bind address").default("127.0.0.1")).addOption(new Option("--stack <name>", "Stack to start (single-stack apps auto-detect)")).addOption(new Option("--warm", "Pre-start one container per Lambda at server boot").default(false)).addOption(new Option("--per-lambda-concurrency <n>", "Pool size cap per Lambda (default 2, max 4)").default("2")).addOption(new Option("--no-pull", "Skip docker pull (cached image)")).addOption(new Option("--container-host <host>", "IP the host uses to bind/probe the RIE port (must be a numeric IP — `docker run -p <ip>:<port>:8080` rejects hostnames). Defaults to 127.0.0.1.").default("127.0.0.1")).addOption(new Option("--debug-port-base <port>", "Reserve a contiguous --debug-port range (one per Lambda)")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}, \"Parameters\": {...}})")).addOption(new Option("--assume-role <arn-or-pair>", "Assume the Lambda's execution role and forward STS-issued temp creds. Bare <arn> = global default; <LogicalId>=<arn> = per-Lambda override (repeatable). Per-Lambda > global > unset (developer creds passed through).").argParser((raw, prev) => parseAssumeRoleToken(raw, prev))).addOption(new Option("--watch", "Hot-reload: re-synth + re-discover routes when cdk.out/ or asset directories change. Off by default; the server keeps the previous version serving when synth fails mid-reload.").default(false)).addOption(new Option("--stage <name>", "Select an API Gateway Stage by its 'StageName'. Default: the first Stage attached to each API. Drives event.stageVariables for both REST v1 and HTTP API v2. NOTE: For HTTP API v2 routes, requestContext.stage is always '$default' regardless of this flag (AWS-side limitation — HTTP API only exposes one stage to the integration event); only event.stageVariables is affected for v2 routes. For REST v1 routes the selected StageName is also threaded into requestContext.stage.")).addOption(new Option("--api <id>", "DEPRECATED — use the positional <target> argument instead. Same accepted forms (bare logical id, stack-qualified, Construct path, ancestor prefix). Will be removed in a future major release.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--from-state", "Read cdkd S3 state for every routed stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::Join (and AWS pseudo parameters) in Lambda env vars with the deployed physical IDs / attributes. Off by default — pre-PR warn-and-drop semantics are preserved. Turn on for stacks already deployed via cdkd deploy. Mirrors `cdkd local invoke --from-state` / `cdkd local run-task --from-state`. Re-runs against fresh state on every hot-reload firing (--watch).").default(false)).addOption(new Option("--stack-region <region>", "Region of the cdkd state record to read (used with --from-state when the same stack name has state in multiple regions).")).addOption(new Option("--allow-unverified-sigv4", "Opt-in: allow AWS_IAM SigV4 requests that cannot be cryptographically verified (foreign access-key-id, OR no local AWS credentials configured) to pass through with a placeholder principalId. DEFAULT off — fail-closed so unauthenticated bypass is impossible against `event.requestContext.identity.accessKey`-trusting handler code. Use only in dev loops where you understand the risk.").default(false)).action(withErrorHandling(localStartApiCommand));
|
|
44463
44721
|
[
|
|
44464
44722
|
...commonOptions,
|
|
44465
44723
|
...appOptions,
|
|
@@ -45562,6 +45820,14 @@ async function localInvokeCommand(target, options) {
|
|
|
45562
45820
|
} catch (err) {
|
|
45563
45821
|
getLogger().debug(`Failed to remove merged-layers tmpdir ${imagePlan.layersTmpDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
45564
45822
|
}
|
|
45823
|
+
if (imagePlan?.layerArnTmpDirs) for (const dir of imagePlan.layerArnTmpDirs) try {
|
|
45824
|
+
rmSync(dir, {
|
|
45825
|
+
recursive: true,
|
|
45826
|
+
force: true
|
|
45827
|
+
});
|
|
45828
|
+
} catch (err) {
|
|
45829
|
+
getLogger().debug(`Failed to remove ARN-layer tmpdir ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
45830
|
+
}
|
|
45565
45831
|
}, (err) => {
|
|
45566
45832
|
getLogger().debug(`cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
45567
45833
|
});
|
|
@@ -45747,7 +46013,7 @@ async function resolveZipImagePlan(lambda, options) {
|
|
|
45747
46013
|
}
|
|
45748
46014
|
const image = resolveRuntimeImage(lambda.runtime);
|
|
45749
46015
|
await pullImage(image, options.pull === false);
|
|
45750
|
-
const layerPlan =
|
|
46016
|
+
const layerPlan = await materializeLambdaLayersIncludingArns(lambda.layers, options);
|
|
45751
46017
|
const containerCodePath = resolveRuntimeCodeMountPath(lambda.runtime);
|
|
45752
46018
|
const tmpfs = resolveTmpfsForLambda(lambda);
|
|
45753
46019
|
return {
|
|
@@ -45761,10 +46027,48 @@ async function resolveZipImagePlan(lambda, options) {
|
|
|
45761
46027
|
cmd: [lambda.handler],
|
|
45762
46028
|
...inlineTmpDir !== void 0 && { inlineTmpDir },
|
|
45763
46029
|
...layerPlan.tmpDir !== void 0 && { layersTmpDir: layerPlan.tmpDir },
|
|
46030
|
+
...layerPlan.extraTmpDirs.length > 0 && { layerArnTmpDirs: layerPlan.extraTmpDirs },
|
|
45764
46031
|
...tmpfs !== void 0 && { tmpfs }
|
|
45765
46032
|
};
|
|
45766
46033
|
}
|
|
45767
46034
|
/**
|
|
46035
|
+
* Two-stage layer materialization (issue #448).
|
|
46036
|
+
*
|
|
46037
|
+
* - Stage 1: every `{kind: 'arn'}` entry is downloaded + unzipped
|
|
46038
|
+
* into its own tmpdir via `materializeLayerFromArn`. The per-ARN
|
|
46039
|
+
* tmpdirs are tracked in `extraTmpDirs` so the outer cleanup can
|
|
46040
|
+
* remove them.
|
|
46041
|
+
* - Stage 2: the resulting `{logicalId, assetPath}[]` list (in
|
|
46042
|
+
* template order — ARN entries surface their `arn` as the
|
|
46043
|
+
* `logicalId` for log lines) is handed to the existing
|
|
46044
|
+
* `materializeLambdaLayers` `cpSync`-merge path. AWS's "last layer
|
|
46045
|
+
* wins" file-collision semantic is preserved across both layer
|
|
46046
|
+
* kinds because the merge step is unchanged.
|
|
46047
|
+
*/
|
|
46048
|
+
async function materializeLambdaLayersIncludingArns(layers, options) {
|
|
46049
|
+
const extraTmpDirs = [];
|
|
46050
|
+
const flat = [];
|
|
46051
|
+
for (const layer of layers) {
|
|
46052
|
+
if (layer.kind === "asset") {
|
|
46053
|
+
flat.push({
|
|
46054
|
+
logicalId: layer.logicalId,
|
|
46055
|
+
assetPath: layer.assetPath
|
|
46056
|
+
});
|
|
46057
|
+
continue;
|
|
46058
|
+
}
|
|
46059
|
+
const dir = await materializeLayerFromArn(layer, { ...options.layerRoleArn !== void 0 && { roleArn: options.layerRoleArn } });
|
|
46060
|
+
extraTmpDirs.push(dir);
|
|
46061
|
+
flat.push({
|
|
46062
|
+
logicalId: layer.arn,
|
|
46063
|
+
assetPath: dir
|
|
46064
|
+
});
|
|
46065
|
+
}
|
|
46066
|
+
return {
|
|
46067
|
+
...materializeLambdaLayers(flat),
|
|
46068
|
+
extraTmpDirs
|
|
46069
|
+
};
|
|
46070
|
+
}
|
|
46071
|
+
/**
|
|
45768
46072
|
* Build the `--tmpfs /tmp:rw,size=<N>m` plan for a Lambda (issue #440).
|
|
45769
46073
|
*
|
|
45770
46074
|
* The shape is identical for ZIP and IMAGE Lambdas — `--tmpfs` overlays
|
|
@@ -46172,7 +46476,7 @@ function pickReferencedLogicalId(intrinsic) {
|
|
|
46172
46476
|
*/
|
|
46173
46477
|
function createLocalCommand() {
|
|
46174
46478
|
const local = new Command("local").description("Local execution of Lambda functions (RIE) and ECS task definitions (Docker required)");
|
|
46175
|
-
const invoke = new Command("invoke").description("Run a Lambda function locally in a Docker container (RIE-backed). Target accepts a CDK display path (MyStack/MyApi/Handler) or stack-qualified logical ID (MyStack:MyApiHandler1234ABCD). Single-stack apps may omit the stack prefix.").argument("<target>", "CDK display path or stack-qualified logical ID of the Lambda to invoke").addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}})")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for IMAGE local-build path; `docker build` does not pull base layers by default")).addOption(new Option("--no-build", "Skip docker build on the IMAGE local-build path (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ZIP Lambdas and the IMAGE ECR-pull path. Compatible with --no-pull.")).addOption(new Option("--debug-port <port>", "Node --inspect-brk port (default: off)")).addOption(new Option("--container-host <host>", "Host to bind the RIE port to").default("127.0.0.1")).addOption(new Option("--assume-role [arn]", "Assume the Lambda's deployed execution role and forward STS-issued temp credentials to the container so the handler runs with the deployed function's narrow permissions (closes the \"developer admin / function narrow\" skew). Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) auto-resolves the function's execution role ARN from cdkd state (requires --from-state); (3) `--no-assume-role` explicitly opts out (forces dev creds even with --from-state). Off by default — when omitted, the developer's shell credentials are forwarded unchanged (SAM-compatible default). STS failures degrade to a warn + dev-creds fallback.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries (#455). Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-state", "Read cdkd S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub in env vars with the deployed physical IDs / attributes. Off by default — keep PR 1 warn-and-drop semantics; turn on for stacks already deployed via cdkd deploy.").default(false)).addOption(new Option("--stack-region <region>", "Region of the cdkd state record to read (used with --from-state when the same stack name has state in multiple regions).")).action(withErrorHandling(localInvokeCommand));
|
|
46479
|
+
const invoke = new Command("invoke").description("Run a Lambda function locally in a Docker container (RIE-backed). Target accepts a CDK display path (MyStack/MyApi/Handler) or stack-qualified logical ID (MyStack:MyApiHandler1234ABCD). Single-stack apps may omit the stack prefix.").argument("<target>", "CDK display path or stack-qualified logical ID of the Lambda to invoke").addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}})")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for IMAGE local-build path; `docker build` does not pull base layers by default")).addOption(new Option("--no-build", "Skip docker build on the IMAGE local-build path (use the previously-built tag). Requires the deterministic tag to already be in the local registry; errors with an actionable message when missing. No-op for ZIP Lambdas and the IMAGE ECR-pull path. Compatible with --no-pull.")).addOption(new Option("--debug-port <port>", "Node --inspect-brk port (default: off)")).addOption(new Option("--container-host <host>", "Host to bind the RIE port to").default("127.0.0.1")).addOption(new Option("--assume-role [arn]", "Assume the Lambda's deployed execution role and forward STS-issued temp credentials to the container so the handler runs with the deployed function's narrow permissions (closes the \"developer admin / function narrow\" skew). Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) auto-resolves the function's execution role ARN from cdkd state (requires --from-state); (3) `--no-assume-role` explicitly opts out (forces dev creds even with --from-state). Off by default — when omitted, the developer's shell credentials are forwarded unchanged (SAM-compatible default). STS failures degrade to a warn + dev-creds fallback.")).addOption(new Option("--layer-role-arn <arn>", "Role to sts:AssumeRole before calling lambda:GetLayerVersion on every literal-ARN entry in Properties.Layers (issue #448). Use only when the dev credentials cannot read the layer — typically cross-account layers. AWS-published public layers (e.g. Lambda Powertools) are readable from every account and need no role.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries (#455). Issues sts:AssumeRole via the default credential chain and uses the temporary credentials for ecr:GetAuthorizationToken + docker pull. Required when the caller does not have direct cross-account access to the target repository. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-state", "Read cdkd S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub in env vars with the deployed physical IDs / attributes. Off by default — keep PR 1 warn-and-drop semantics; turn on for stacks already deployed via cdkd deploy.").default(false)).addOption(new Option("--stack-region <region>", "Region of the cdkd state record to read (used with --from-state when the same stack name has state in multiple regions).")).action(withErrorHandling(localInvokeCommand));
|
|
46176
46480
|
[
|
|
46177
46481
|
...commonOptions,
|
|
46178
46482
|
...appOptions,
|
|
@@ -47464,7 +47768,7 @@ function reorderArgs(argv) {
|
|
|
47464
47768
|
*/
|
|
47465
47769
|
async function main() {
|
|
47466
47770
|
const program = new Command();
|
|
47467
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
47771
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.125.0");
|
|
47468
47772
|
program.addCommand(createBootstrapCommand());
|
|
47469
47773
|
program.addCommand(createSynthCommand());
|
|
47470
47774
|
program.addCommand(createListCommand());
|