@go-to-k/cdkd 0.167.0 → 0.167.2

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 CHANGED
@@ -62,7 +62,7 @@ import { CreateVectorBucketCommand, DeleteIndexCommand, DeleteVectorBucketComman
62
62
  import { CreateNamespaceCommand, CreateTableBucketCommand, CreateTableCommand as CreateTableCommand$2, DeleteNamespaceCommand as DeleteNamespaceCommand$1, DeleteTableBucketCommand, DeleteTableCommand as DeleteTableCommand$2, GetTableBucketCommand, GetTableCommand as GetTableCommand$1, ListNamespacesCommand as ListNamespacesCommand$1, ListTableBucketsCommand, ListTablesCommand as ListTablesCommand$1, ListTagsForResourceCommand as ListTagsForResourceCommand$19, NotFoundException as NotFoundException$5, S3TablesClient } from "@aws-sdk/client-s3tables";
63
63
  import { AttachLoadBalancerTargetGroupsCommand, AttachLoadBalancersCommand, AttachTrafficSourcesCommand, AutoScalingClient, CreateAutoScalingGroupCommand, CreateOrUpdateTagsCommand, DeleteAutoScalingGroupCommand, DeleteLifecycleHookCommand, DeleteNotificationConfigurationCommand, DeleteTagsCommand as DeleteTagsCommand$1, DescribeAutoScalingGroupsCommand, DescribeLifecycleHooksCommand, DescribeNotificationConfigurationsCommand, DescribeTrafficSourcesCommand, DetachLoadBalancerTargetGroupsCommand, DetachLoadBalancersCommand, DetachTrafficSourcesCommand, DisableMetricsCollectionCommand, EnableMetricsCollectionCommand, PutLifecycleHookCommand, PutNotificationConfigurationCommand, UpdateAutoScalingGroupCommand } from "@aws-sdk/client-auto-scaling";
64
64
  import { Document, Pair, Scalar, YAMLMap, YAMLSeq, parse as parse$1, stringify } from "yaml";
65
- import { mkdir, mkdtemp } from "node:fs/promises";
65
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
66
66
  import { Readable } from "node:stream";
67
67
  import { pipeline } from "node:stream/promises";
68
68
  import { createServer } from "node:net";
@@ -46251,12 +46251,22 @@ async function invokeRieStreaming(host, port, event, timeoutMs) {
46251
46251
  throw new Error(`RIE streaming response did not emit the prelude/body separator within ${STREAM_PRELUDE_MAX_BYTES} bytes. The handler likely did not call awslambda.HttpResponseStream.from(stream, metadata).`);
46252
46252
  }
46253
46253
  }
46254
+ let preludeSynthesized = false;
46254
46255
  if (separatorIdx < 0) {
46255
- clearTimeout(timer);
46256
- throw new Error(`RIE streaming response ended before the prelude/body separator (got ${preludeBytes.length} bytes). The handler likely threw before streaming the prelude — check container logs.`);
46256
+ if (preludeBytes.length === 0) {
46257
+ clearTimeout(timer);
46258
+ throw new Error(`RIE streaming response ended with zero bytes. The handler likely threw before any write — check container logs.`);
46259
+ }
46260
+ preludeSynthesized = true;
46261
+ bodyTail = preludeBytes;
46262
+ preludeBytes = Buffer.alloc(0);
46257
46263
  }
46258
46264
  let prelude;
46259
- try {
46265
+ if (preludeSynthesized) prelude = {
46266
+ statusCode: 200,
46267
+ headers: { "Content-Type": "application/octet-stream" }
46268
+ };
46269
+ else try {
46260
46270
  prelude = parseStreamingPrelude(preludeBytes.toString("utf8"));
46261
46271
  } catch (err) {
46262
46272
  clearTimeout(timer);
@@ -46510,6 +46520,62 @@ function singleFlight(fn, onError) {
46510
46520
  };
46511
46521
  }
46512
46522
 
46523
+ //#endregion
46524
+ //#region src/cli/commands/local-profile-credentials-file.ts
46525
+ /**
46526
+ * Path inside the container where the credentials file is mounted. Fixed
46527
+ * (not user-configurable) so the env-var injection is stable. `/cdkd-aws/`
46528
+ * is outside `/var/task` (the Lambda code mount) and outside `/root/`
46529
+ * (which the user's handler may bind-mount or modify), so there is no
46530
+ * collision risk with the user's payload.
46531
+ */
46532
+ const CONTAINER_AWS_CREDENTIALS_PATH = "/cdkd-aws/credentials";
46533
+ /**
46534
+ * Write a temporary AWS shared-credentials file containing the resolved
46535
+ * `--profile <name>` credentials, ready to bind-mount into a Lambda
46536
+ * container at {@link CONTAINER_AWS_CREDENTIALS_PATH}.
46537
+ *
46538
+ * The file content is the standard `[profile-name]` INI shape:
46539
+ *
46540
+ * [<profileName>]
46541
+ * aws_access_key_id = <accessKeyId>
46542
+ * aws_secret_access_key = <secretAccessKey>
46543
+ * aws_session_token = <sessionToken> ← only when present
46544
+ *
46545
+ * `aws_session_token` is omitted when the resolved profile produced
46546
+ * long-lived (non-STS) credentials, mirroring the same logic
46547
+ * `applyProfileCredentialsOverlay` uses for env-var injection.
46548
+ *
46549
+ * Caller is responsible for invoking `dispose()` when the container pool
46550
+ * tears down (e.g., on `SIGINT` via `singleFlight` cleanup). Leaving the
46551
+ * file behind in `/tmp` is a security smell (temp credentials live on
46552
+ * disk).
46553
+ */
46554
+ async function writeProfileCredentialsFile(profileName, creds) {
46555
+ if (profileName === "") throw new Error("writeProfileCredentialsFile: profile name must not be empty.");
46556
+ if (/[\r\n[\]]/.test(profileName)) throw new Error(`writeProfileCredentialsFile: profile name '${profileName}' contains a forbidden character (any of CR, LF, '[', ']' would corrupt the INI file or the docker -e env var).`);
46557
+ const dir = await mkdtemp(path.join(tmpdir(), "cdkd-profile-creds-"));
46558
+ const hostPath = path.join(dir, "credentials");
46559
+ const lines = [
46560
+ `[${profileName}]`,
46561
+ `aws_access_key_id = ${creds.accessKeyId}`,
46562
+ `aws_secret_access_key = ${creds.secretAccessKey}`
46563
+ ];
46564
+ if (creds.sessionToken) lines.push(`aws_session_token = ${creds.sessionToken}`);
46565
+ await writeFile(hostPath, lines.join("\n") + "\n", { mode: 384 });
46566
+ return {
46567
+ hostPath,
46568
+ containerPath: CONTAINER_AWS_CREDENTIALS_PATH,
46569
+ profileName,
46570
+ dispose: async () => {
46571
+ await rm(dir, {
46572
+ recursive: true,
46573
+ force: true
46574
+ });
46575
+ }
46576
+ };
46577
+ }
46578
+
46513
46579
  //#endregion
46514
46580
  //#region src/local/intrinsic-lambda-arn.ts
46515
46581
  /**
@@ -50500,6 +50566,11 @@ function createContainerPool(specs, options) {
50500
50566
  containerPath: "/opt",
50501
50567
  readOnly: true
50502
50568
  }] : [];
50569
+ const extraMounts = spec.profileCredentialsFile ? [...optMount, {
50570
+ hostPath: spec.profileCredentialsFile.hostPath,
50571
+ containerPath: spec.profileCredentialsFile.containerPath,
50572
+ readOnly: true
50573
+ }] : optMount;
50503
50574
  const containerCodePath = resolveRuntimeCodeMountPath(spec.lambda.runtime);
50504
50575
  containerId = await runDetached({
50505
50576
  image: resolveRuntimeImage(spec.lambda.runtime),
@@ -50508,7 +50579,7 @@ function createContainerPool(specs, options) {
50508
50579
  containerPath: containerCodePath,
50509
50580
  readOnly: true
50510
50581
  }],
50511
- extraMounts: optMount,
50582
+ extraMounts,
50512
50583
  env: spec.env,
50513
50584
  cmd: [spec.lambda.handler],
50514
50585
  hostPort,
@@ -50521,6 +50592,11 @@ function createContainerPool(specs, options) {
50521
50592
  } else containerId = await runDetached({
50522
50593
  image: spec.image,
50523
50594
  mounts: [],
50595
+ ...spec.profileCredentialsFile && { extraMounts: [{
50596
+ hostPath: spec.profileCredentialsFile.hostPath,
50597
+ containerPath: spec.profileCredentialsFile.containerPath,
50598
+ readOnly: true
50599
+ }] },
50524
50600
  env: spec.env,
50525
50601
  cmd: spec.command,
50526
50602
  hostPort,
@@ -55013,6 +55089,7 @@ async function localStartApiCommand(target, options) {
55013
55089
  const perLambdaConcurrency = parsePerLambdaConcurrency(options.perLambdaConcurrency);
55014
55090
  const inlineTmpDirs = /* @__PURE__ */ new Set();
55015
55091
  const layerTmpDirs = /* @__PURE__ */ new Set();
55092
+ let profileCredsFile;
55016
55093
  const lastAssetPaths = { value: [] };
55017
55094
  const authorizerCache = createAuthorizerCache();
55018
55095
  const jwksCache = createJwksCache();
@@ -55084,6 +55161,7 @@ async function localStartApiCommand(target, options) {
55084
55161
  }
55085
55162
  const stateByStack = options.fromState || isCfnFlagPresent(options) ? await loadStateForRoutedStacks(targetStacks, routes, routesWithAuth, options) : /* @__PURE__ */ new Map();
55086
55163
  const profileCredentials = options.profile ? await resolveProfileCredentials(options.profile) : void 0;
55164
+ if (options.profile && profileCredentials && !profileCredsFile) profileCredsFile = await writeProfileCredentialsFile(options.profile, profileCredentials);
55087
55165
  const lambdaIds = uniqueLambdaIds(routes, routesWithAuth, webSocketApis);
55088
55166
  const specs = /* @__PURE__ */ new Map();
55089
55167
  for (let i = 0; i < lambdaIds.length; i++) {
@@ -55101,7 +55179,8 @@ async function localStartApiCommand(target, options) {
55101
55179
  stateByStack,
55102
55180
  skipPull: options.pull === false,
55103
55181
  ...options.layerRoleArn !== void 0 && { layerRoleArn: options.layerRoleArn },
55104
- ...profileCredentials && { profileCredentials }
55182
+ ...profileCredentials && { profileCredentials },
55183
+ ...profileCredsFile && { profileCredsFile }
55105
55184
  });
55106
55185
  specs.set(logicalId, spec);
55107
55186
  }
@@ -55377,6 +55456,11 @@ async function localStartApiCommand(target, options) {
55377
55456
  } catch (err) {
55378
55457
  logger.warn(`Failed to remove merged-layers tmpdir ${dir}: ${err instanceof Error ? err.message : String(err)}`);
55379
55458
  }
55459
+ if (profileCredsFile) try {
55460
+ await profileCredsFile.dispose();
55461
+ } catch (err) {
55462
+ logger.warn(`Failed to remove profile credentials tmpdir ${profileCredsFile.hostPath}: ${err instanceof Error ? err.message : String(err)}`);
55463
+ }
55380
55464
  });
55381
55465
  const shutdown = async (signal, exitCode) => {
55382
55466
  if (shutdownStarted) {
@@ -55535,7 +55619,7 @@ function warnIamRoutes(routesWithAuth) {
55535
55619
  * missing, runtime not supported).
55536
55620
  */
55537
55621
  async function buildContainerSpec(args) {
55538
- const { logicalId, stacks, overrides, assumeRole, containerHost, debugPort, stsRegion, inlineTmpDirs, layerTmpDirs, stateByStack, skipPull, layerRoleArn, profileCredentials } = args;
55622
+ const { logicalId, stacks, overrides, assumeRole, containerHost, debugPort, stsRegion, inlineTmpDirs, layerTmpDirs, stateByStack, skipPull, layerRoleArn, profileCredentials, profileCredsFile } = args;
55539
55623
  const lambda = resolveLambdaByLogicalId(logicalId, stacks);
55540
55624
  let codeDir;
55541
55625
  let optDir;
@@ -55589,6 +55673,10 @@ async function buildContainerSpec(args) {
55589
55673
  if (profileCredentials.sessionToken) dockerEnv["AWS_SESSION_TOKEN"] = profileCredentials.sessionToken;
55590
55674
  else delete dockerEnv["AWS_SESSION_TOKEN"];
55591
55675
  }
55676
+ if (profileCredsFile) {
55677
+ dockerEnv["AWS_SHARED_CREDENTIALS_FILE"] = profileCredsFile.containerPath;
55678
+ dockerEnv["AWS_PROFILE"] = profileCredsFile.profileName;
55679
+ }
55592
55680
  }
55593
55681
  if (debugPort !== void 0) dockerEnv["NODE_OPTIONS"] = `--inspect-brk=0.0.0.0:${debugPort}`;
55594
55682
  const tmpfs = lambda.ephemeralStorageMb !== void 0 ? {
@@ -55603,7 +55691,11 @@ async function buildContainerSpec(args) {
55603
55691
  containerHost,
55604
55692
  ...optDir !== void 0 && { optDir },
55605
55693
  ...debugPort !== void 0 && { debugPort },
55606
- ...tmpfs !== void 0 && { tmpfs }
55694
+ ...tmpfs !== void 0 && { tmpfs },
55695
+ ...profileCredsFile && { profileCredentialsFile: {
55696
+ hostPath: profileCredsFile.hostPath,
55697
+ containerPath: profileCredsFile.containerPath
55698
+ } }
55607
55699
  };
55608
55700
  return {
55609
55701
  kind: "image",
@@ -55616,7 +55708,11 @@ async function buildContainerSpec(args) {
55616
55708
  env: dockerEnv,
55617
55709
  containerHost,
55618
55710
  ...debugPort !== void 0 && { debugPort },
55619
- ...tmpfs !== void 0 && { tmpfs }
55711
+ ...tmpfs !== void 0 && { tmpfs },
55712
+ ...profileCredsFile && { profileCredentialsFile: {
55713
+ hostPath: profileCredsFile.hostPath,
55714
+ containerPath: profileCredsFile.containerPath
55715
+ } }
55620
55716
  };
55621
55717
  }
55622
55718
  /**
@@ -58918,6 +59014,7 @@ async function localInvokeCommand(target, options) {
58918
59014
  let containerId;
58919
59015
  let stopLogs;
58920
59016
  let sigintHandler;
59017
+ let profileCredsFile;
58921
59018
  /**
58922
59019
  * Unified cleanup for both the success / failure unwind path AND the
58923
59020
  * SIGINT handler. Idempotent — every step guards on its own undefined
@@ -58966,6 +59063,11 @@ async function localInvokeCommand(target, options) {
58966
59063
  } catch (err) {
58967
59064
  getLogger().debug(`Failed to remove ARN-layer tmpdir ${dir}: ${err instanceof Error ? err.message : String(err)}`);
58968
59065
  }
59066
+ if (profileCredsFile) try {
59067
+ await profileCredsFile.dispose();
59068
+ } catch (err) {
59069
+ getLogger().debug(`Failed to remove profile credentials tmpdir ${profileCredsFile.hostPath}: ${err instanceof Error ? err.message : String(err)}`);
59070
+ }
58969
59071
  }, (err) => {
58970
59072
  getLogger().debug(`cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
58971
59073
  });
@@ -58976,6 +59078,7 @@ async function localInvokeCommand(target, options) {
58976
59078
  });
58977
59079
  await ensureDockerAvailable();
58978
59080
  const profileCredentials = options.profile ? await resolveProfileCredentials(options.profile) : void 0;
59081
+ if (options.profile && profileCredentials) profileCredsFile = await writeProfileCredentialsFile(options.profile, profileCredentials);
58979
59082
  const appCmd = resolveApp(options.app);
58980
59083
  if (!appCmd) throw new Error("No CDK app specified. Pass --app, set CDKD_APP, or add \"app\" to cdk.json.");
58981
59084
  logger.info("Synthesizing CDK app...");
@@ -59076,6 +59179,10 @@ async function localInvokeCommand(target, options) {
59076
59179
  if (!assumeSucceeded) {
59077
59180
  forwardAwsEnv(dockerEnv);
59078
59181
  applyProfileCredentialsOverlay(dockerEnv, profileCredentials, false);
59182
+ if (profileCredsFile) {
59183
+ dockerEnv["AWS_SHARED_CREDENTIALS_FILE"] = profileCredsFile.containerPath;
59184
+ dockerEnv["AWS_PROFILE"] = profileCredsFile.profileName;
59185
+ }
59079
59186
  }
59080
59187
  let debugPort;
59081
59188
  if (options.debugPort) {
@@ -59088,10 +59195,15 @@ async function localInvokeCommand(target, options) {
59088
59195
  const containerHost = options.containerHost;
59089
59196
  if (lambda.layers.length > 0) logger.info(`Mounting ${lambda.layers.length} Lambda layer${lambda.layers.length === 1 ? "" : "s"} at /opt`);
59090
59197
  logger.info(`Starting container (image=${imagePlan.image}, port=${hostPort})...`);
59198
+ const extraMountsWithProfile = profileCredsFile ? [...imagePlan.extraMounts ?? [], {
59199
+ hostPath: profileCredsFile.hostPath,
59200
+ containerPath: profileCredsFile.containerPath,
59201
+ readOnly: true
59202
+ }] : imagePlan.extraMounts;
59091
59203
  containerId = await runDetached({
59092
59204
  image: imagePlan.image,
59093
59205
  mounts: imagePlan.mounts,
59094
- extraMounts: imagePlan.extraMounts,
59206
+ extraMounts: extraMountsWithProfile,
59095
59207
  env: dockerEnv,
59096
59208
  cmd: imagePlan.cmd,
59097
59209
  hostPort,
@@ -60770,7 +60882,7 @@ function reorderArgs(argv) {
60770
60882
  */
60771
60883
  async function main() {
60772
60884
  const program = new Command();
60773
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.167.0");
60885
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.167.2");
60774
60886
  program.addCommand(createBootstrapCommand());
60775
60887
  program.addCommand(createSynthCommand());
60776
60888
  program.addCommand(createListCommand());