@go-to-k/cdkd 0.166.0 → 0.167.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 CHANGED
@@ -1123,6 +1123,7 @@ function validateRecreateTargets(input) {
1123
1123
  const blockedStatefulTargets = [];
1124
1124
  const blockedMultiRegionTargets = [];
1125
1125
  const blockedAlreadySdk = [];
1126
+ const blockedAlreadyCcApi = [];
1126
1127
  const blockedNoSdkProvider = [];
1127
1128
  const conflictSet = new Set(conflictingDirections);
1128
1129
  const namedTargets = [...input.recreateViaCcApi.map((id) => ({
@@ -1166,6 +1167,7 @@ function validateRecreateTargets(input) {
1166
1167
  property
1167
1168
  });
1168
1169
  }
1170
+ if (recordedResource.provisionedBy === "cc-api") blockedAlreadyCcApi.push(target);
1169
1171
  } else {
1170
1172
  const actionableDrops = findActionableSilentDrops(resourceType, templateResource.Properties, input.allowUnsupportedProperties);
1171
1173
  for (const { property } of actionableDrops) ambiguousIntentSdk.push({
@@ -1187,6 +1189,7 @@ function validateRecreateTargets(input) {
1187
1189
  blockedStatefulTargets,
1188
1190
  blockedMultiRegionTargets,
1189
1191
  blockedAlreadySdk,
1192
+ blockedAlreadyCcApi,
1190
1193
  blockedNoSdkProvider,
1191
1194
  conflictingDirections
1192
1195
  };
@@ -1241,6 +1244,12 @@ function renderRecreateTargetsErrors(validation) {
1241
1244
  for (const blocked of validation.blockedAlreadySdk) lines.push(` - ${blocked.logicalId} (${blocked.resourceType})`);
1242
1245
  lines.push(" Fix: remove --recreate-via-sdk-provider <id> for these resources. They are already SDK-managed (or pre-v7 legacy state, treated as SDK).");
1243
1246
  }
1247
+ if (validation.blockedAlreadyCcApi.length > 0) {
1248
+ if (lines.length > 0) lines.push("");
1249
+ lines.push(`--recreate-via-cc-api named ${validation.blockedAlreadyCcApi.length} resource(s) that are ALREADY sticky on Cloud Control API (the migration is a no-op):`);
1250
+ for (const blocked of validation.blockedAlreadyCcApi) lines.push(` - ${blocked.logicalId} (${blocked.resourceType})`);
1251
+ lines.push(" Fix: remove --recreate-via-cc-api <id> for these resources. They are already CC-managed; a destroy + recreate cycle would produce the same end state at the cost of unnecessary downtime.");
1252
+ }
1244
1253
  if (validation.blockedNoSdkProvider.length > 0) {
1245
1254
  if (lines.length > 0) lines.push("");
1246
1255
  lines.push(`--recreate-via-sdk-provider named ${validation.blockedNoSdkProvider.length} resource(s) of types cdkd has no SDK provider for (Tier 2 CC-only):`);
@@ -57289,6 +57298,7 @@ async function localRunTaskCommand(target, options) {
57289
57298
  resolvedRoleArn = options.assumeTaskRole;
57290
57299
  assumedCredentials = await assumeTaskRole$1(resolvedRoleArn, options.region);
57291
57300
  }
57301
+ const sidecarCredentials = await resolveSidecarCredentials(options, assumedCredentials);
57292
57302
  const envOverrides = readEnvOverridesFile$2(options.envVars);
57293
57303
  const runOpts = {
57294
57304
  cluster: options.cluster,
@@ -57298,7 +57308,7 @@ async function localRunTaskCommand(target, options) {
57298
57308
  detach: options.detach
57299
57309
  };
57300
57310
  if (envOverrides) runOpts.envOverrides = envOverrides;
57301
- if (assumedCredentials) runOpts.taskCredentials = assumedCredentials;
57311
+ if (sidecarCredentials) runOpts.taskCredentials = sidecarCredentials;
57302
57312
  if (resolvedRoleArn) runOpts.taskRoleArn = resolvedRoleArn;
57303
57313
  if (options.platform) runOpts.platformOverride = options.platform;
57304
57314
  if (options.region) runOpts.region = options.region;
@@ -57445,6 +57455,29 @@ function readEnvOverridesFile$2(filePath) {
57445
57455
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`--env-vars file '${filePath}' must contain a JSON object at the top level.`);
57446
57456
  return parsed;
57447
57457
  }
57458
+ /**
57459
+ * Issue #658: pick the credentials forwarded to the AWS-published
57460
+ * `amazon-ecs-local-container-endpoints` sidecar. Precedence:
57461
+ * 1. `--assume-task-role <arn>` (or bare `--assume-task-role` against
57462
+ * a resolvable `TaskRoleArn`) → STS-assumed temp creds. Highest
57463
+ * priority — when the user opted in to IAM emulation, those creds
57464
+ * drive the sidecar regardless of `--profile`.
57465
+ * 2. `--profile <p>` → resolved via {@link resolveProfileCredentials}
57466
+ * (the SDK's default credential provider chain — SSO / IAM
57467
+ * Identity Center / fromIni / role-assumption). NEW in this PR.
57468
+ * 3. Neither set → `undefined`; the sidecar runs with its own
57469
+ * default credential chain (typically empty inside a fresh
57470
+ * container — user containers will get 4xx from the credentials
57471
+ * endpoint, mimicking IAM-misconfigured prod).
57472
+ *
57473
+ * Extracted as an exported helper so a unit test can exercise every
57474
+ * branch without having to mock the full Synth + Docker + AWS pipeline
57475
+ * (the strategy PR #655 used for the Lambda container path).
57476
+ */
57477
+ async function resolveSidecarCredentials(options, assumedCredentials) {
57478
+ if (assumedCredentials) return assumedCredentials;
57479
+ if (options.profile) return resolveProfileCredentials(options.profile);
57480
+ }
57448
57481
  function createLocalRunTaskCommand() {
57449
57482
  const cmd = new Command("run-task").description("Run an AWS::ECS::TaskDefinition locally — pulls/builds images, sets up a per-task docker network with the AWS-published metadata-endpoints sidecar, and starts every container in dependsOn order. Target accepts a CDK display path (MyStack/MyService/TaskDef) or stack-qualified logical ID (MyStack:MyServiceTaskDefXYZ1234). Single-stack apps may omit the stack prefix.").argument("<target>", "CDK display path or stack-qualified logical ID of the AWS::ECS::TaskDefinition to run").addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default("cdkd-local")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed function role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries (#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("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--keep-running", "Don't docker rm -f the user containers on task exit (network + sidecar are still torn down). Use when you want to docker exec into a stopped container for post-mortems.").default(false)).addOption(new Option("--detach", "Start the containers in the background and exit (skip log streaming + auto teardown). Useful in CI smoke tests; caller manages container lifecycle.").default(false)).addOption(new Option("--from-state", "Read cdkd S3 state for the target stack and substitute Fn::Sub / Fn::GetAtt references to same-stack AWS::ECR::Repository resources with the deployed URI. Off by default — only the AWS pseudo-parameter tier (${AWS::AccountId} / ${AWS::Region}) is resolved without this flag.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the cdkd stack name; pass an explicit value when the CFn stack name differs. Mutually exclusive with --from-state. Fn::GetAtt is warn-and-dropped in v1 (CFn DescribeStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-state when the same stack name has state in multiple regions, and with --from-cfn-stack as the CFn client region (cdkd does not have a separate --cfn-stack-region flag).")).action(withErrorHandling(localRunTaskCommand));
57450
57483
  [
@@ -58575,11 +58608,13 @@ async function localStartServiceCommand(targets, options) {
58575
58608
  for (const w of index.warnings) logger.warn(w);
58576
58609
  }
58577
58610
  const registry = new CloudMapRegistry();
58611
+ const sidecarCredentials = await resolveSharedSidecarCredentials(options);
58578
58612
  try {
58579
58613
  sharedNetwork = await createSharedSvcNetwork({
58580
58614
  prefix: options.cluster,
58581
58615
  skipPull,
58582
- cluster: options.cluster
58616
+ cluster: options.cluster,
58617
+ ...sidecarCredentials !== void 0 && { credentials: sidecarCredentials }
58583
58618
  });
58584
58619
  } catch (err) {
58585
58620
  throw new LocalStartServiceError(`Failed to create shared service network: ${err instanceof Error ? err.message : String(err)}`);
@@ -58819,6 +58854,34 @@ function parseRestartPolicy(raw) {
58819
58854
  if (raw === "on-failure" || raw === "always" || raw === "none") return raw;
58820
58855
  throw new LocalStartServiceError(`--restart-policy must be one of 'on-failure', 'always', or 'none' (got '${raw}').`);
58821
58856
  }
58857
+ /**
58858
+ * Issue #658: pick the credentials forwarded to the AWS-published
58859
+ * `amazon-ecs-local-container-endpoints` sidecar. `cdkd local
58860
+ * start-service`'s sidecar is SHARED across every replica boot in one
58861
+ * CLI invocation (design § 5 Option A), so this resolves ONCE at
58862
+ * startup. Precedence:
58863
+ * 1. `--profile <p>` → resolved via {@link resolveProfileCredentials}
58864
+ * (the SDK's default credential provider chain — SSO / IAM
58865
+ * Identity Center / fromIni / role-assumption). NEW in this PR.
58866
+ * 2. Not set → `undefined`; the sidecar runs with its own default
58867
+ * credential chain (typically empty inside a fresh container —
58868
+ * user containers will get 4xx from the credentials endpoint).
58869
+ *
58870
+ * Note: per-service `--assume-task-role <Service>=<arn>` overrides are
58871
+ * INTENTIONALLY NOT consulted here. The shared sidecar has no concept
58872
+ * of per-service IAM — per-service `TaskRoleArn` flows into each
58873
+ * container's env via `buildMetadataEnv` at boot time, where the
58874
+ * sidecar's `/role/<role-arn>` path resolves per-request. The shared
58875
+ * sidecar's OWN startup credentials govern only the fallback path
58876
+ * (containers that did not bind a `TaskRoleArn`).
58877
+ *
58878
+ * Extracted as an exported helper so a unit test can exercise both
58879
+ * branches without having to mock the full Synth + Docker + AWS
58880
+ * pipeline (the strategy PR #655 used for the Lambda container path).
58881
+ */
58882
+ async function resolveSharedSidecarCredentials(options) {
58883
+ if (options.profile) return resolveProfileCredentials(options.profile);
58884
+ }
58822
58885
  function createLocalStartServiceCommand() {
58823
58886
  const cmd = new Command("start-service").description("Run one or more AWS::ECS::Service resources locally as a long-running emulator. Spins up DesiredCount task replicas per service (clamped by --max-tasks) using the same per-task docker network + metadata sidecar pattern as `cdkd local run-task`, then keeps each replica running and restarts it on exit per --restart-policy. ^C tears every replica + sidecar + network down. Each <target> accepts a CDK display path (MyStack/MyService) or stack-qualified logical ID (MyStack:MyServiceXYZ); single-stack apps may omit the stack prefix. When two or more <target>s are supplied, every service is booted into a shared Cloud Map / Service Connect registry so peer services discover each other via docker --add-host overlay (Issue #460).").argument("<targets...>", "One or more CDK display paths or stack-qualified logical IDs of the AWS::ECS::Service resources to run").addOption(new Option("--cluster <name>", "Cluster name surfaced to ECS_CONTAINER_METADATA_URI_V4 and used as the docker network prefix").default("cdkd-local")).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"ContainerName\":{\"KEY\":\"VALUE\"}, \"Parameters\":{}})")).addOption(new Option("--container-host <ip>", "Host IP to bind published container ports to. Must be a numeric IP (Docker rejects hostnames here)").default("127.0.0.1")).addOption(new Option("--assume-task-role [arn]", "Assume the task definition's TaskRoleArn (or the supplied ARN) and forward STS-issued temp credentials via the metadata sidecar so containers run with the deployed task role. Bare flag uses the template's TaskRoleArn; pass an explicit ARN to override.")).addOption(new Option("--no-pull", "Skip docker pull for every container image and the metadata sidecar")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries.")).addOption(new Option("--platform <platform>", "Force docker --platform (linux/amd64 or linux/arm64). Default: inferred from task RuntimePlatform.CpuArchitecture")).addOption(new Option("--max-tasks <n>", `Hard cap on local replica count. Caps the template DesiredCount so local dev machines don't run an unbounded number of containers. Cannot exceed ${83} due to the per-replica link-local /24 subnet allocator's range.`).default(3).argParser(parseMaxTasks)).addOption(new Option("--restart-policy <policy>", "How to react when an essential container exits. 'on-failure' (default) restarts only on non-zero exit; 'always' restarts on every exit; 'none' shuts the replica down and runs the service degraded.").default("on-failure").argParser(parseRestartPolicy)).addOption(new Option("--from-state", "Read cdkd S3 state for the target stack and substitute Fn::Sub / Fn::GetAtt / Fn::ImportValue / Fn::GetStackOutput intrinsics in container images, environment variables, secrets, role ARNs, and volumes.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in container env vars / secrets / image URIs with the deployed physical IDs / exports. Use for CDK apps deployed via the upstream CDK CLI (`cdk deploy`). Bare form uses the cdkd stack name; pass an explicit value when the CFn stack name differs. Mutually exclusive with --from-state. Fn::GetAtt is warn-and-dropped in v1 (CFn DescribeStackResources does not return per-attribute values).")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-state when the same stack name has state in multiple regions, and with --from-cfn-stack as the CFn client region (cdkd does not have a separate --cfn-stack-region flag).")).action(withErrorHandling(localStartServiceCommand));
58824
58887
  [
@@ -60707,7 +60770,7 @@ function reorderArgs(argv) {
60707
60770
  */
60708
60771
  async function main() {
60709
60772
  const program = new Command();
60710
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.166.0");
60773
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.167.0");
60711
60774
  program.addCommand(createBootstrapCommand());
60712
60775
  program.addCommand(createSynthCommand());
60713
60776
  program.addCommand(createListCommand());