@go-to-k/cdkd 0.52.0 → 0.53.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
@@ -31673,8 +31673,10 @@ var ElastiCacheProvider = class {
31673
31673
  import {
31674
31674
  ServiceDiscoveryClient,
31675
31675
  CreatePrivateDnsNamespaceCommand,
31676
+ UpdatePrivateDnsNamespaceCommand,
31676
31677
  DeleteNamespaceCommand,
31677
31678
  CreateServiceCommand as CreateServiceCommand2,
31679
+ UpdateServiceCommand as UpdateServiceCommand2,
31678
31680
  DeleteServiceCommand as DeleteServiceCommand2,
31679
31681
  GetNamespaceCommand,
31680
31682
  GetOperationCommand,
@@ -31736,12 +31738,18 @@ var ServiceDiscoveryProvider = class {
31736
31738
  );
31737
31739
  }
31738
31740
  }
31739
- update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
31741
+ update(logicalId, physicalId, resourceType, properties, previousProperties) {
31740
31742
  switch (resourceType) {
31741
31743
  case "AWS::ServiceDiscovery::PrivateDnsNamespace":
31742
- return this.updateNamespace(logicalId, physicalId);
31744
+ return this.updateNamespace(logicalId, physicalId, resourceType, properties);
31743
31745
  case "AWS::ServiceDiscovery::Service":
31744
- return this.updateService(logicalId, physicalId);
31746
+ return this.updateService(
31747
+ logicalId,
31748
+ physicalId,
31749
+ resourceType,
31750
+ properties,
31751
+ previousProperties
31752
+ );
31745
31753
  default:
31746
31754
  throw new ProvisioningError(
31747
31755
  `Unsupported resource type: ${resourceType}`,
@@ -31824,14 +31832,66 @@ var ServiceDiscoveryProvider = class {
31824
31832
  );
31825
31833
  }
31826
31834
  }
31827
- updateNamespace(logicalId, _physicalId) {
31828
- return Promise.reject(
31829
- new ResourceUpdateNotSupportedError(
31830
- "AWS::ServiceDiscovery::PrivateDnsNamespace",
31835
+ /**
31836
+ * Update a private DNS namespace.
31837
+ *
31838
+ * AWS exposes `UpdatePrivateDnsNamespace` for two mutable surfaces:
31839
+ * - `Description`
31840
+ * - `Properties.DnsProperties.SOA.TTL`
31841
+ *
31842
+ * `Name` and `Vpc` are immutable; the deploy engine's
31843
+ * replacement-detection layer routes those through DELETE+CREATE
31844
+ * before this method is ever called, so we do not validate them here.
31845
+ *
31846
+ * Empty-string Description is intentionally allowed through (`!== undefined`
31847
+ * gate, not truthy) so `cdkd drift --revert` can clear a console-side ADD.
31848
+ */
31849
+ async updateNamespace(logicalId, physicalId, resourceType, properties) {
31850
+ this.logger.debug(`Updating private DNS namespace ${logicalId}: ${physicalId}`);
31851
+ const client = this.getClient();
31852
+ const namespaceChange = {};
31853
+ if (properties["Description"] !== void 0) {
31854
+ namespaceChange.Description = properties["Description"];
31855
+ }
31856
+ const propsBag = properties["Properties"];
31857
+ const dnsProps = propsBag?.["DnsProperties"];
31858
+ const soa = dnsProps?.["SOA"];
31859
+ if (soa?.TTL !== void 0) {
31860
+ namespaceChange.Properties = {
31861
+ DnsProperties: {
31862
+ SOA: { TTL: Number(soa.TTL) }
31863
+ }
31864
+ };
31865
+ }
31866
+ if (Object.keys(namespaceChange).length === 0) {
31867
+ this.logger.debug(`No mutable diff for PrivateDnsNamespace ${logicalId}, skipping update`);
31868
+ return { physicalId, wasReplaced: false };
31869
+ }
31870
+ try {
31871
+ const response = await client.send(
31872
+ new UpdatePrivateDnsNamespaceCommand({
31873
+ Id: physicalId,
31874
+ Namespace: namespaceChange
31875
+ })
31876
+ );
31877
+ const operationId = response.OperationId;
31878
+ if (operationId) {
31879
+ await this.pollOperation(operationId, logicalId, resourceType);
31880
+ }
31881
+ this.logger.debug(`Successfully updated private DNS namespace ${logicalId}`);
31882
+ return { physicalId, wasReplaced: false };
31883
+ } catch (error) {
31884
+ if (error instanceof ProvisioningError)
31885
+ throw error;
31886
+ const cause = error instanceof Error ? error : void 0;
31887
+ throw new ProvisioningError(
31888
+ `Failed to update private DNS namespace ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
31889
+ resourceType,
31831
31890
  logicalId,
31832
- "PrivateDnsNamespace updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
31833
- )
31834
- );
31891
+ physicalId,
31892
+ cause
31893
+ );
31894
+ }
31835
31895
  }
31836
31896
  async deleteNamespace(logicalId, physicalId, resourceType, context) {
31837
31897
  this.logger.debug(`Deleting private DNS namespace ${logicalId}: ${physicalId}`);
@@ -31926,14 +31986,69 @@ var ServiceDiscoveryProvider = class {
31926
31986
  );
31927
31987
  }
31928
31988
  }
31929
- updateService(logicalId, _physicalId) {
31930
- return Promise.reject(
31931
- new ResourceUpdateNotSupportedError(
31932
- "AWS::ServiceDiscovery::Service",
31989
+ /**
31990
+ * Update a service discovery service.
31991
+ *
31992
+ * Per AWS docs, `UpdateService` accepts a `ServiceChange` body with
31993
+ * `Description`, `DnsConfig.DnsRecords` (TTLs etc. — `NamespaceId` /
31994
+ * `RoutingPolicy` are not part of the change shape and are immutable
31995
+ * here), and `HealthCheckConfig`. `Name` / `NamespaceId` /
31996
+ * `HealthCheckCustomConfig` are immutable on UpdateService — the
31997
+ * replacement-detection layer routes those through DELETE+CREATE.
31998
+ *
31999
+ * Per AWS docs, omitting `DnsRecords` / `HealthCheckConfig` from the
32000
+ * request DELETES that configuration. To preserve fields cdkd is not
32001
+ * actively reverting, we always echo the AWS-current value when the
32002
+ * caller did not supply a change. `cdkd drift --revert` passes the
32003
+ * full AWS-current snapshot as `properties`, so the round-trip is
32004
+ * value-preserving.
32005
+ */
32006
+ async updateService(logicalId, physicalId, resourceType, properties, _previousProperties) {
32007
+ this.logger.debug(`Updating service discovery service ${logicalId}: ${physicalId}`);
32008
+ const client = this.getClient();
32009
+ const serviceChange = {};
32010
+ if (properties["Description"] !== void 0) {
32011
+ serviceChange.Description = properties["Description"];
32012
+ }
32013
+ const dnsConfig = properties["DnsConfig"];
32014
+ if (dnsConfig?.DnsRecords !== void 0) {
32015
+ const change = { DnsRecords: dnsConfig.DnsRecords };
32016
+ serviceChange.DnsConfig = change;
32017
+ }
32018
+ if (properties["HealthCheckConfig"] !== void 0) {
32019
+ serviceChange.HealthCheckConfig = properties["HealthCheckConfig"];
32020
+ }
32021
+ if (Object.keys(serviceChange).length === 0) {
32022
+ this.logger.debug(
32023
+ `No mutable diff for ServiceDiscovery Service ${logicalId}, skipping update`
32024
+ );
32025
+ return { physicalId, wasReplaced: false };
32026
+ }
32027
+ try {
32028
+ const response = await client.send(
32029
+ new UpdateServiceCommand2({
32030
+ Id: physicalId,
32031
+ Service: serviceChange
32032
+ })
32033
+ );
32034
+ const operationId = response.OperationId;
32035
+ if (operationId) {
32036
+ await this.pollOperation(operationId, logicalId, resourceType);
32037
+ }
32038
+ this.logger.debug(`Successfully updated service discovery service ${logicalId}`);
32039
+ return { physicalId, wasReplaced: false };
32040
+ } catch (error) {
32041
+ if (error instanceof ProvisioningError)
32042
+ throw error;
32043
+ const cause = error instanceof Error ? error : void 0;
32044
+ throw new ProvisioningError(
32045
+ `Failed to update service discovery service ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
32046
+ resourceType,
31933
32047
  logicalId,
31934
- "ServiceDiscovery Service updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
31935
- )
31936
- );
32048
+ physicalId,
32049
+ cause
32050
+ );
32051
+ }
31937
32052
  }
31938
32053
  async deleteService(logicalId, physicalId, resourceType, context) {
31939
32054
  this.logger.debug(`Deleting service discovery service ${logicalId}: ${physicalId}`);
@@ -33020,6 +33135,7 @@ var AppSyncProvider = class {
33020
33135
  import {
33021
33136
  GlueClient,
33022
33137
  CreateDatabaseCommand,
33138
+ UpdateDatabaseCommand,
33023
33139
  DeleteDatabaseCommand,
33024
33140
  CreateTableCommand as CreateTableCommand2,
33025
33141
  UpdateTableCommand,
@@ -33066,11 +33182,7 @@ var GlueProvider = class {
33066
33182
  async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
33067
33183
  switch (resourceType) {
33068
33184
  case "AWS::Glue::Database":
33069
- throw new ResourceUpdateNotSupportedError(
33070
- resourceType,
33071
- logicalId,
33072
- "Glue Database updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
33073
- );
33185
+ return this.updateDatabase(logicalId, physicalId, resourceType, properties);
33074
33186
  case "AWS::Glue::Table":
33075
33187
  return this.updateTable(logicalId, physicalId, resourceType, properties);
33076
33188
  default:
@@ -33121,12 +33233,7 @@ var GlueProvider = class {
33121
33233
  await this.getClient().send(
33122
33234
  new CreateDatabaseCommand({
33123
33235
  CatalogId: catalogId,
33124
- DatabaseInput: {
33125
- Name: databaseName,
33126
- Description: databaseInput["Description"],
33127
- LocationUri: databaseInput["LocationUri"],
33128
- Parameters: databaseInput["Parameters"]
33129
- }
33236
+ DatabaseInput: this.buildDatabaseInput(databaseInput, databaseName)
33130
33237
  })
33131
33238
  );
33132
33239
  this.logger.debug(`Successfully created Glue Database ${logicalId}: ${databaseName}`);
@@ -33145,6 +33252,42 @@ var GlueProvider = class {
33145
33252
  );
33146
33253
  }
33147
33254
  }
33255
+ async updateDatabase(logicalId, physicalId, resourceType, properties) {
33256
+ this.logger.debug(`Updating Glue Database ${logicalId}: ${physicalId}`);
33257
+ const databaseInput = properties["DatabaseInput"];
33258
+ if (!databaseInput) {
33259
+ throw new ProvisioningError(
33260
+ `DatabaseInput is required for Glue Database update ${logicalId}`,
33261
+ resourceType,
33262
+ logicalId,
33263
+ physicalId
33264
+ );
33265
+ }
33266
+ const catalogId = properties["CatalogId"];
33267
+ try {
33268
+ await this.getClient().send(
33269
+ new UpdateDatabaseCommand({
33270
+ ...catalogId !== void 0 && { CatalogId: catalogId },
33271
+ Name: physicalId,
33272
+ DatabaseInput: this.buildDatabaseInput(databaseInput, physicalId)
33273
+ })
33274
+ );
33275
+ this.logger.debug(`Successfully updated Glue Database ${logicalId}`);
33276
+ return {
33277
+ physicalId,
33278
+ wasReplaced: false
33279
+ };
33280
+ } catch (error) {
33281
+ const cause = error instanceof Error ? error : void 0;
33282
+ throw new ProvisioningError(
33283
+ `Failed to update Glue Database ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
33284
+ resourceType,
33285
+ logicalId,
33286
+ physicalId,
33287
+ cause
33288
+ );
33289
+ }
33290
+ }
33148
33291
  async deleteDatabase(logicalId, physicalId, resourceType, properties, context) {
33149
33292
  this.logger.debug(`Deleting Glue Database ${logicalId}: ${physicalId}`);
33150
33293
  try {
@@ -33316,6 +33459,31 @@ var GlueProvider = class {
33316
33459
  }
33317
33460
  }
33318
33461
  // ─── Helpers ───────────────────────────────────────────────────────
33462
+ /**
33463
+ * Build DatabaseInput for Glue API from CFn template properties.
33464
+ *
33465
+ * Used by both `createDatabase` and `updateDatabase` so the same
33466
+ * field-by-field shape is sent on both paths. Optional fields use
33467
+ * `!== undefined` gates (per `feedback_update_optional_field_undefined_check.md`)
33468
+ * so empty-string Description, empty Parameters map, etc. reach AWS
33469
+ * intact — `cdkd drift --revert` relies on this to clear console-side
33470
+ * additions.
33471
+ */
33472
+ buildDatabaseInput(databaseInput, fallbackName) {
33473
+ const result = {
33474
+ Name: databaseInput["Name"] ?? fallbackName
33475
+ };
33476
+ if (databaseInput["Description"] !== void 0) {
33477
+ result.Description = databaseInput["Description"];
33478
+ }
33479
+ if (databaseInput["LocationUri"] !== void 0) {
33480
+ result.LocationUri = databaseInput["LocationUri"];
33481
+ }
33482
+ if (databaseInput["Parameters"] !== void 0) {
33483
+ result.Parameters = databaseInput["Parameters"];
33484
+ }
33485
+ return result;
33486
+ }
33319
33487
  /**
33320
33488
  * Build TableInput for Glue API from CFn template properties
33321
33489
  */
@@ -34781,10 +34949,12 @@ var KinesisStreamProvider = class {
34781
34949
  import {
34782
34950
  EFSClient,
34783
34951
  CreateFileSystemCommand,
34952
+ UpdateFileSystemCommand,
34784
34953
  DeleteFileSystemCommand,
34785
34954
  CreateMountTargetCommand,
34786
34955
  DeleteMountTargetCommand,
34787
34956
  DescribeMountTargetsCommand,
34957
+ ModifyMountTargetSecurityGroupsCommand,
34788
34958
  CreateAccessPointCommand,
34789
34959
  DeleteAccessPointCommand,
34790
34960
  DescribeFileSystemsCommand,
@@ -34842,29 +35012,122 @@ var EFSProvider = class {
34842
35012
  }
34843
35013
  }
34844
35014
  /**
34845
- * EFS resources are treated as immutable by cdkd's `update()`. The deploy
34846
- * engine recreates them on property changes via immutable-property
34847
- * detection. (AWS does expose `UpdateFileSystem` for ThroughputMode and
34848
- * `ModifyMountTargetSecurityGroups` for mount-target SGs those are
34849
- * deferred to a follow-up PR.) `cdkd drift --revert` surfaces a clear
34850
- * "use --replace or re-deploy" message instead of silently no-op'ing.
35015
+ * Mutable surfaces by resource type:
35016
+ * - `AWS::EFS::FileSystem` `UpdateFileSystem` (ThroughputMode,
35017
+ * ProvisionedThroughputInMibps). Other property changes
35018
+ * (Encrypted / KmsKeyId / PerformanceMode / etc.) are routed
35019
+ * through DELETE+CREATE by the replacement-detection layer; if a
35020
+ * diff somehow includes them, defensively reject.
35021
+ * - `AWS::EFS::MountTarget` → `ModifyMountTargetSecurityGroups`
35022
+ * (SecurityGroups only). IpAddress / SubnetId / FileSystemId are
35023
+ * immutable.
35024
+ * - `AWS::EFS::AccessPoint` → no mutable surface; AWS recreates on
35025
+ * every change. Reject so `cdkd drift --revert` surfaces a clear
35026
+ * "use --replace" hint.
34851
35027
  */
34852
- update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
34853
- if (resourceType !== "AWS::EFS::FileSystem" && resourceType !== "AWS::EFS::MountTarget" && resourceType !== "AWS::EFS::AccessPoint") {
35028
+ update(logicalId, physicalId, resourceType, properties, previousProperties) {
35029
+ switch (resourceType) {
35030
+ case "AWS::EFS::FileSystem":
35031
+ return this.updateFileSystem(
35032
+ logicalId,
35033
+ physicalId,
35034
+ resourceType,
35035
+ properties,
35036
+ previousProperties
35037
+ );
35038
+ case "AWS::EFS::MountTarget":
35039
+ return this.updateMountTarget(logicalId, physicalId, resourceType, properties);
35040
+ case "AWS::EFS::AccessPoint":
35041
+ return Promise.reject(
35042
+ new ResourceUpdateNotSupportedError(
35043
+ resourceType,
35044
+ logicalId,
35045
+ "EFS AccessPoint is recreated on property changes; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
35046
+ )
35047
+ );
35048
+ default:
35049
+ throw new ProvisioningError(
35050
+ `Unsupported resource type: ${resourceType}`,
35051
+ resourceType,
35052
+ logicalId,
35053
+ physicalId
35054
+ );
35055
+ }
35056
+ }
35057
+ async updateFileSystem(logicalId, physicalId, resourceType, properties, previousProperties) {
35058
+ const immutableKeys = ["Encrypted", "KmsKeyId", "PerformanceMode"];
35059
+ for (const key of immutableKeys) {
35060
+ const next = properties[key];
35061
+ const prev = previousProperties[key];
35062
+ if (next !== void 0 && prev !== void 0 && JSON.stringify(next) !== JSON.stringify(prev)) {
35063
+ throw new ResourceUpdateNotSupportedError(
35064
+ resourceType,
35065
+ logicalId,
35066
+ `EFS FileSystem ${key} is immutable; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack`
35067
+ );
35068
+ }
35069
+ }
35070
+ const newThroughputMode = properties["ThroughputMode"];
35071
+ const newProvisioned = properties["ProvisionedThroughputInMibps"];
35072
+ const oldThroughputMode = previousProperties["ThroughputMode"];
35073
+ const oldProvisioned = previousProperties["ProvisionedThroughputInMibps"];
35074
+ const throughputModeChanged = newThroughputMode !== void 0 && newThroughputMode !== oldThroughputMode;
35075
+ const provisionedChanged = newProvisioned !== void 0 && newProvisioned !== oldProvisioned;
35076
+ if (!throughputModeChanged && !provisionedChanged) {
35077
+ this.logger.debug(`No mutable diff for EFS FileSystem ${logicalId}, skipping update`);
35078
+ return { physicalId, wasReplaced: false };
35079
+ }
35080
+ this.logger.debug(`Updating EFS FileSystem ${logicalId}: ${physicalId}`);
35081
+ try {
35082
+ await this.getClient().send(
35083
+ new UpdateFileSystemCommand({
35084
+ FileSystemId: physicalId,
35085
+ ...throughputModeChanged && { ThroughputMode: newThroughputMode },
35086
+ ...provisionedChanged && { ProvisionedThroughputInMibps: newProvisioned }
35087
+ })
35088
+ );
35089
+ await this.waitForFileSystemAvailable(physicalId, logicalId, resourceType);
35090
+ this.logger.debug(`Successfully updated EFS FileSystem ${logicalId}`);
35091
+ return { physicalId, wasReplaced: false };
35092
+ } catch (error) {
35093
+ if (error instanceof ProvisioningError)
35094
+ throw error;
35095
+ const cause = error instanceof Error ? error : void 0;
34854
35096
  throw new ProvisioningError(
34855
- `Unsupported resource type: ${resourceType}`,
35097
+ `Failed to update EFS FileSystem ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
34856
35098
  resourceType,
34857
35099
  logicalId,
34858
- physicalId
35100
+ physicalId,
35101
+ cause
34859
35102
  );
34860
35103
  }
34861
- return Promise.reject(
34862
- new ResourceUpdateNotSupportedError(
35104
+ }
35105
+ async updateMountTarget(logicalId, physicalId, resourceType, properties) {
35106
+ this.logger.debug(`Updating EFS MountTarget ${logicalId}: ${physicalId}`);
35107
+ const securityGroups = properties["SecurityGroups"];
35108
+ if (securityGroups === void 0) {
35109
+ this.logger.debug(`No mutable diff for EFS MountTarget ${logicalId}, skipping update`);
35110
+ return { physicalId, wasReplaced: false };
35111
+ }
35112
+ try {
35113
+ await this.getClient().send(
35114
+ new ModifyMountTargetSecurityGroupsCommand({
35115
+ MountTargetId: physicalId,
35116
+ SecurityGroups: securityGroups
35117
+ })
35118
+ );
35119
+ this.logger.debug(`Successfully updated EFS MountTarget ${logicalId}`);
35120
+ return { physicalId, wasReplaced: false };
35121
+ } catch (error) {
35122
+ const cause = error instanceof Error ? error : void 0;
35123
+ throw new ProvisioningError(
35124
+ `Failed to update EFS MountTarget ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
34863
35125
  resourceType,
34864
35126
  logicalId,
34865
- "EFS resources are recreated on property changes; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
34866
- )
34867
- );
35127
+ physicalId,
35128
+ cause
35129
+ );
35130
+ }
34868
35131
  }
34869
35132
  async delete(logicalId, physicalId, resourceType, _properties, context) {
34870
35133
  switch (resourceType) {
@@ -45339,7 +45602,7 @@ function reorderArgs(argv) {
45339
45602
  }
45340
45603
  async function main() {
45341
45604
  const program = new Command14();
45342
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.52.0");
45605
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.53.0");
45343
45606
  program.addCommand(createBootstrapCommand());
45344
45607
  program.addCommand(createSynthCommand());
45345
45608
  program.addCommand(createListCommand());