@go-to-k/cdkd 0.51.10 → 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
@@ -17059,11 +17059,15 @@ var DynamoDBTableProvider = class {
17059
17059
  // src/provisioning/providers/logs-loggroup-provider.ts
17060
17060
  import {
17061
17061
  CreateLogGroupCommand,
17062
+ DeleteIndexPolicyCommand,
17062
17063
  DeleteLogGroupCommand,
17063
17064
  DescribeIndexPoliciesCommand,
17064
17065
  DescribeLogGroupsCommand,
17065
17066
  GetDataProtectionPolicyCommand,
17066
17067
  ListTagsForResourceCommand as ListTagsForResourceCommand2,
17068
+ PutBearerTokenAuthenticationCommand,
17069
+ PutIndexPolicyCommand,
17070
+ PutLogGroupDeletionProtectionCommand,
17067
17071
  PutRetentionPolicyCommand,
17068
17072
  DeleteRetentionPolicyCommand,
17069
17073
  TagResourceCommand as TagResourceCommand5,
@@ -17116,6 +17120,9 @@ var LogsLogGroupProvider = class {
17116
17120
  if (properties["LogGroupClass"]) {
17117
17121
  createParams.logGroupClass = properties["LogGroupClass"];
17118
17122
  }
17123
+ if (properties["DeletionProtectionEnabled"] !== void 0) {
17124
+ createParams.deletionProtectionEnabled = properties["DeletionProtectionEnabled"];
17125
+ }
17119
17126
  if (properties["Tags"]) {
17120
17127
  const cfnTags = properties["Tags"];
17121
17128
  createParams.tags = Object.fromEntries(cfnTags.map((t) => [t.Key, t.Value]));
@@ -17139,6 +17146,30 @@ var LogsLogGroupProvider = class {
17139
17146
  })
17140
17147
  );
17141
17148
  }
17149
+ const fieldIndexPolicies = properties["FieldIndexPolicies"];
17150
+ if (fieldIndexPolicies && fieldIndexPolicies.length > 0) {
17151
+ if (fieldIndexPolicies.length > 1) {
17152
+ this.logger.debug(
17153
+ `Log group ${logicalId} declares ${fieldIndexPolicies.length} FieldIndexPolicies; AWS only supports one log-group-level field index policy. Applying the first.`
17154
+ );
17155
+ }
17156
+ const first = fieldIndexPolicies[0];
17157
+ const policyDocument = typeof first === "string" ? first : JSON.stringify(first);
17158
+ await this.logsClient.send(
17159
+ new PutIndexPolicyCommand({
17160
+ logGroupIdentifier: logGroupName,
17161
+ policyDocument
17162
+ })
17163
+ );
17164
+ }
17165
+ if (properties["BearerTokenAuthenticationEnabled"] !== void 0) {
17166
+ await this.logsClient.send(
17167
+ new PutBearerTokenAuthenticationCommand({
17168
+ logGroupIdentifier: logGroupName,
17169
+ bearerTokenAuthenticationEnabled: properties["BearerTokenAuthenticationEnabled"]
17170
+ })
17171
+ );
17172
+ }
17142
17173
  this.logger.debug(`Successfully created log group ${logicalId}: ${logGroupName}`);
17143
17174
  const arn = await this.buildArn(logGroupName);
17144
17175
  return {
@@ -17171,7 +17202,10 @@ var LogsLogGroupProvider = class {
17171
17202
  /**
17172
17203
  * Update a CloudWatch Logs log group
17173
17204
  *
17174
- * Only RetentionInDays can be updated. LogGroupName is immutable (requires replacement).
17205
+ * Mutable: `RetentionInDays`, `DataProtectionPolicy`, `Tags`,
17206
+ * `DeletionProtectionEnabled`, `BearerTokenAuthenticationEnabled`,
17207
+ * `FieldIndexPolicies`. `LogGroupName` / `KmsKeyId` / `LogGroupClass`
17208
+ * are immutable on AWS-side and require replacement.
17175
17209
  */
17176
17210
  async update(logicalId, physicalId, _resourceType, properties, previousProperties) {
17177
17211
  this.logger.debug(`Updating log group ${logicalId}: ${physicalId}`);
@@ -17210,6 +17244,70 @@ var LogsLogGroupProvider = class {
17210
17244
  );
17211
17245
  }
17212
17246
  }
17247
+ if (properties["DeletionProtectionEnabled"] !== previousProperties["DeletionProtectionEnabled"]) {
17248
+ const next = properties["DeletionProtectionEnabled"];
17249
+ if (next !== void 0) {
17250
+ await this.logsClient.send(
17251
+ new PutLogGroupDeletionProtectionCommand({
17252
+ logGroupIdentifier: physicalId,
17253
+ deletionProtectionEnabled: next
17254
+ })
17255
+ );
17256
+ } else {
17257
+ await this.logsClient.send(
17258
+ new PutLogGroupDeletionProtectionCommand({
17259
+ logGroupIdentifier: physicalId,
17260
+ deletionProtectionEnabled: false
17261
+ })
17262
+ );
17263
+ }
17264
+ }
17265
+ if (properties["BearerTokenAuthenticationEnabled"] !== previousProperties["BearerTokenAuthenticationEnabled"]) {
17266
+ const next = properties["BearerTokenAuthenticationEnabled"];
17267
+ if (next !== void 0) {
17268
+ await this.logsClient.send(
17269
+ new PutBearerTokenAuthenticationCommand({
17270
+ logGroupIdentifier: physicalId,
17271
+ bearerTokenAuthenticationEnabled: next
17272
+ })
17273
+ );
17274
+ } else {
17275
+ await this.logsClient.send(
17276
+ new PutBearerTokenAuthenticationCommand({
17277
+ logGroupIdentifier: physicalId,
17278
+ bearerTokenAuthenticationEnabled: false
17279
+ })
17280
+ );
17281
+ }
17282
+ }
17283
+ const newFieldIndex = properties["FieldIndexPolicies"];
17284
+ const oldFieldIndex = previousProperties["FieldIndexPolicies"];
17285
+ if (JSON.stringify(newFieldIndex) !== JSON.stringify(oldFieldIndex)) {
17286
+ if (newFieldIndex && newFieldIndex.length > 0) {
17287
+ if (newFieldIndex.length > 1) {
17288
+ this.logger.debug(
17289
+ `Log group ${physicalId} declares ${newFieldIndex.length} FieldIndexPolicies; AWS only supports one log-group-level field index policy. Applying the first.`
17290
+ );
17291
+ }
17292
+ const first = newFieldIndex[0];
17293
+ const policyDocument = typeof first === "string" ? first : JSON.stringify(first);
17294
+ await this.logsClient.send(
17295
+ new PutIndexPolicyCommand({
17296
+ logGroupIdentifier: physicalId,
17297
+ policyDocument
17298
+ })
17299
+ );
17300
+ } else {
17301
+ try {
17302
+ await this.logsClient.send(
17303
+ new DeleteIndexPolicyCommand({ logGroupIdentifier: physicalId })
17304
+ );
17305
+ } catch (err) {
17306
+ if (!(err instanceof ResourceNotFoundException7))
17307
+ throw err;
17308
+ }
17309
+ }
17310
+ }
17213
17311
  const newTags = properties["Tags"];
17214
17312
  const oldTags = previousProperties["Tags"];
17215
17313
  if (JSON.stringify(newTags) !== JSON.stringify(oldTags)) {
@@ -17326,14 +17424,15 @@ var LogsLogGroupProvider = class {
17326
17424
  * `AWS::Logs::ResourcePolicy` resource type — account-wide, not
17327
17425
  * per-log-group).
17328
17426
  *
17329
- * Known limitation: cdkd's `create()` / `update()` flows do NOT yet
17330
- * apply `FieldIndexPolicies` / `DeletionProtectionEnabled` /
17331
- * `BearerTokenAuthenticationEnabled` they're in `handledProperties`
17332
- * to prevent CC API fallback but no actual `PutIndexPolicy` /
17333
- * `PutLogGroupDeletionProtection` / `PutBearerTokenAuthentication`
17334
- * calls fire. Surfacing these in `readCurrentState` means a user
17335
- * who templates them will see drift on the first run; a follow-up
17336
- * needs to wire the create/update flow.
17427
+ * Write-side coverage: `FieldIndexPolicies` is applied via
17428
+ * `PutIndexPolicy` (CloudWatch Logs allows at most one log-group-level
17429
+ * field index policy at a time, so the CFn array is effectively 0-or-1
17430
+ * the first entry is applied and a debug log notes any additional
17431
+ * entries are ignored). `DeletionProtectionEnabled` is forwarded as
17432
+ * part of `CreateLogGroup` and updated via
17433
+ * `PutLogGroupDeletionProtection`. `BearerTokenAuthenticationEnabled`
17434
+ * is applied via `PutBearerTokenAuthentication` after the log group
17435
+ * exists (it is not part of `CreateLogGroupRequest`).
17337
17436
  *
17338
17437
  * Tags are read via `ListTagsForResource` (using the log-group ARN from
17339
17438
  * the same `DescribeLogGroups` response). CDK's `aws:*` auto-tags are
@@ -31574,8 +31673,10 @@ var ElastiCacheProvider = class {
31574
31673
  import {
31575
31674
  ServiceDiscoveryClient,
31576
31675
  CreatePrivateDnsNamespaceCommand,
31676
+ UpdatePrivateDnsNamespaceCommand,
31577
31677
  DeleteNamespaceCommand,
31578
31678
  CreateServiceCommand as CreateServiceCommand2,
31679
+ UpdateServiceCommand as UpdateServiceCommand2,
31579
31680
  DeleteServiceCommand as DeleteServiceCommand2,
31580
31681
  GetNamespaceCommand,
31581
31682
  GetOperationCommand,
@@ -31637,12 +31738,18 @@ var ServiceDiscoveryProvider = class {
31637
31738
  );
31638
31739
  }
31639
31740
  }
31640
- update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
31741
+ update(logicalId, physicalId, resourceType, properties, previousProperties) {
31641
31742
  switch (resourceType) {
31642
31743
  case "AWS::ServiceDiscovery::PrivateDnsNamespace":
31643
- return this.updateNamespace(logicalId, physicalId);
31744
+ return this.updateNamespace(logicalId, physicalId, resourceType, properties);
31644
31745
  case "AWS::ServiceDiscovery::Service":
31645
- return this.updateService(logicalId, physicalId);
31746
+ return this.updateService(
31747
+ logicalId,
31748
+ physicalId,
31749
+ resourceType,
31750
+ properties,
31751
+ previousProperties
31752
+ );
31646
31753
  default:
31647
31754
  throw new ProvisioningError(
31648
31755
  `Unsupported resource type: ${resourceType}`,
@@ -31725,14 +31832,66 @@ var ServiceDiscoveryProvider = class {
31725
31832
  );
31726
31833
  }
31727
31834
  }
31728
- updateNamespace(logicalId, _physicalId) {
31729
- return Promise.reject(
31730
- new ResourceUpdateNotSupportedError(
31731
- "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,
31732
31890
  logicalId,
31733
- "PrivateDnsNamespace updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
31734
- )
31735
- );
31891
+ physicalId,
31892
+ cause
31893
+ );
31894
+ }
31736
31895
  }
31737
31896
  async deleteNamespace(logicalId, physicalId, resourceType, context) {
31738
31897
  this.logger.debug(`Deleting private DNS namespace ${logicalId}: ${physicalId}`);
@@ -31827,14 +31986,69 @@ var ServiceDiscoveryProvider = class {
31827
31986
  );
31828
31987
  }
31829
31988
  }
31830
- updateService(logicalId, _physicalId) {
31831
- return Promise.reject(
31832
- new ResourceUpdateNotSupportedError(
31833
- "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,
31834
32047
  logicalId,
31835
- "ServiceDiscovery Service updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
31836
- )
31837
- );
32048
+ physicalId,
32049
+ cause
32050
+ );
32051
+ }
31838
32052
  }
31839
32053
  async deleteService(logicalId, physicalId, resourceType, context) {
31840
32054
  this.logger.debug(`Deleting service discovery service ${logicalId}: ${physicalId}`);
@@ -32921,6 +33135,7 @@ var AppSyncProvider = class {
32921
33135
  import {
32922
33136
  GlueClient,
32923
33137
  CreateDatabaseCommand,
33138
+ UpdateDatabaseCommand,
32924
33139
  DeleteDatabaseCommand,
32925
33140
  CreateTableCommand as CreateTableCommand2,
32926
33141
  UpdateTableCommand,
@@ -32967,11 +33182,7 @@ var GlueProvider = class {
32967
33182
  async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
32968
33183
  switch (resourceType) {
32969
33184
  case "AWS::Glue::Database":
32970
- throw new ResourceUpdateNotSupportedError(
32971
- resourceType,
32972
- logicalId,
32973
- "Glue Database updates are not yet implemented in cdkd; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
32974
- );
33185
+ return this.updateDatabase(logicalId, physicalId, resourceType, properties);
32975
33186
  case "AWS::Glue::Table":
32976
33187
  return this.updateTable(logicalId, physicalId, resourceType, properties);
32977
33188
  default:
@@ -33022,12 +33233,7 @@ var GlueProvider = class {
33022
33233
  await this.getClient().send(
33023
33234
  new CreateDatabaseCommand({
33024
33235
  CatalogId: catalogId,
33025
- DatabaseInput: {
33026
- Name: databaseName,
33027
- Description: databaseInput["Description"],
33028
- LocationUri: databaseInput["LocationUri"],
33029
- Parameters: databaseInput["Parameters"]
33030
- }
33236
+ DatabaseInput: this.buildDatabaseInput(databaseInput, databaseName)
33031
33237
  })
33032
33238
  );
33033
33239
  this.logger.debug(`Successfully created Glue Database ${logicalId}: ${databaseName}`);
@@ -33046,6 +33252,42 @@ var GlueProvider = class {
33046
33252
  );
33047
33253
  }
33048
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
+ }
33049
33291
  async deleteDatabase(logicalId, physicalId, resourceType, properties, context) {
33050
33292
  this.logger.debug(`Deleting Glue Database ${logicalId}: ${physicalId}`);
33051
33293
  try {
@@ -33217,6 +33459,31 @@ var GlueProvider = class {
33217
33459
  }
33218
33460
  }
33219
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
+ }
33220
33487
  /**
33221
33488
  * Build TableInput for Glue API from CFn template properties
33222
33489
  */
@@ -33991,10 +34258,10 @@ var KMSProvider = class {
33991
34258
  * Dispatches by resource type:
33992
34259
  * - `AWS::KMS::Key` → `DescribeKey`. Surfaces `Description`, `KeySpec`,
33993
34260
  * `KeyUsage`, `Enabled`, `MultiRegion`, `Origin`. `KeyPolicy` is
33994
- * intentionally NOT retrieved `GetKeyPolicy` is a separate call
33995
- * and the policy body needs JSON parsing for comparison; deferred
33996
- * to a follow-up. `EnableKeyRotation` / `RotationPeriodInDays`
33997
- * would require `GetKeyRotationStatus`; also deferred.
34261
+ * additionally retrieved via `GetKeyPolicy` (URL-decoded JSON-parsed)
34262
+ * and `EnableKeyRotation` / `RotationPeriodInDays` via
34263
+ * `GetKeyRotationStatus` (Class 1 discriminator-gated on `KeySpec`
34264
+ * since asymmetric keys reject the call).
33998
34265
  * - `AWS::KMS::Alias` → `ListAliases` filtered to the alias name.
33999
34266
  * Surfaces `AliasName`, `TargetKeyId`. `ListAliases` is paginated
34000
34267
  * since there's no direct "describe one alias" API.
@@ -34682,10 +34949,12 @@ var KinesisStreamProvider = class {
34682
34949
  import {
34683
34950
  EFSClient,
34684
34951
  CreateFileSystemCommand,
34952
+ UpdateFileSystemCommand,
34685
34953
  DeleteFileSystemCommand,
34686
34954
  CreateMountTargetCommand,
34687
34955
  DeleteMountTargetCommand,
34688
34956
  DescribeMountTargetsCommand,
34957
+ ModifyMountTargetSecurityGroupsCommand,
34689
34958
  CreateAccessPointCommand,
34690
34959
  DeleteAccessPointCommand,
34691
34960
  DescribeFileSystemsCommand,
@@ -34743,29 +35012,122 @@ var EFSProvider = class {
34743
35012
  }
34744
35013
  }
34745
35014
  /**
34746
- * EFS resources are treated as immutable by cdkd's `update()`. The deploy
34747
- * engine recreates them on property changes via immutable-property
34748
- * detection. (AWS does expose `UpdateFileSystem` for ThroughputMode and
34749
- * `ModifyMountTargetSecurityGroups` for mount-target SGs those are
34750
- * deferred to a follow-up PR.) `cdkd drift --revert` surfaces a clear
34751
- * "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.
34752
35027
  */
34753
- update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
34754
- 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;
34755
35096
  throw new ProvisioningError(
34756
- `Unsupported resource type: ${resourceType}`,
35097
+ `Failed to update EFS FileSystem ${logicalId}: ${error instanceof Error ? error.message : String(error)}`,
34757
35098
  resourceType,
34758
35099
  logicalId,
34759
- physicalId
35100
+ physicalId,
35101
+ cause
34760
35102
  );
34761
35103
  }
34762
- return Promise.reject(
34763
- 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)}`,
34764
35125
  resourceType,
34765
35126
  logicalId,
34766
- "EFS resources are recreated on property changes; re-deploy with cdkd deploy --replace, or destroy + redeploy the stack"
34767
- )
34768
- );
35127
+ physicalId,
35128
+ cause
35129
+ );
35130
+ }
34769
35131
  }
34770
35132
  async delete(logicalId, physicalId, resourceType, _properties, context) {
34771
35133
  switch (resourceType) {
@@ -45240,7 +45602,7 @@ function reorderArgs(argv) {
45240
45602
  }
45241
45603
  async function main() {
45242
45604
  const program = new Command14();
45243
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.51.10");
45605
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.53.0");
45244
45606
  program.addCommand(createBootstrapCommand());
45245
45607
  program.addCommand(createSynthCommand());
45246
45608
  program.addCommand(createListCommand());