@go-to-k/cdkd 0.50.5 → 0.50.7

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
@@ -8705,10 +8705,10 @@ var IAMRoleProvider = class {
8705
8705
  const updateParams = {
8706
8706
  RoleName: physicalId
8707
8707
  };
8708
- if (properties["Description"]) {
8708
+ if (properties["Description"] !== void 0) {
8709
8709
  updateParams.Description = properties["Description"];
8710
8710
  }
8711
- if (properties["MaxSessionDuration"]) {
8711
+ if (properties["MaxSessionDuration"] !== void 0) {
8712
8712
  updateParams.MaxSessionDuration = properties["MaxSessionDuration"];
8713
8713
  }
8714
8714
  await this.iamClient.send(new UpdateRoleCommand(updateParams));
@@ -10038,6 +10038,7 @@ import {
10038
10038
  ListUserTagsCommand,
10039
10039
  NoSuchEntityException as NoSuchEntityException4,
10040
10040
  TagUserCommand,
10041
+ UntagUserCommand,
10041
10042
  PutUserPermissionsBoundaryCommand,
10042
10043
  DeleteUserPermissionsBoundaryCommand
10043
10044
  } from "@aws-sdk/client-iam";
@@ -10234,16 +10235,11 @@ var IAMUserGroupProvider = class {
10234
10235
  async updateUser(logicalId, physicalId, resourceType, properties, previousProperties) {
10235
10236
  this.logger.debug(`Updating IAM user ${logicalId}: ${physicalId}`);
10236
10237
  try {
10237
- const tags = properties["Tags"];
10238
- if (tags && Array.isArray(tags)) {
10239
- await this.iamClient.send(
10240
- new TagUserCommand({
10241
- UserName: physicalId,
10242
- Tags: tags
10243
- })
10244
- );
10245
- this.logger.debug(`Tagged user ${physicalId}`);
10246
- }
10238
+ await this.applyUserTagDiff(
10239
+ physicalId,
10240
+ previousProperties["Tags"],
10241
+ properties["Tags"]
10242
+ );
10247
10243
  const newPermBoundary = properties["PermissionsBoundary"];
10248
10244
  const oldPermBoundary = previousProperties["PermissionsBoundary"];
10249
10245
  if (newPermBoundary !== oldPermBoundary) {
@@ -10492,6 +10488,42 @@ var IAMUserGroupProvider = class {
10492
10488
  throw error;
10493
10489
  }
10494
10490
  }
10491
+ /**
10492
+ * Apply a diff between old and new CFn-shape Tags arrays via IAM's
10493
+ * `TagUser` / `UntagUser` APIs.
10494
+ */
10495
+ async applyUserTagDiff(userName, oldTagsRaw, newTagsRaw) {
10496
+ const toMap = (tags) => {
10497
+ const m = /* @__PURE__ */ new Map();
10498
+ for (const t of tags ?? []) {
10499
+ if (t.Key !== void 0 && t.Value !== void 0)
10500
+ m.set(t.Key, t.Value);
10501
+ }
10502
+ return m;
10503
+ };
10504
+ const oldMap = toMap(oldTagsRaw);
10505
+ const newMap = toMap(newTagsRaw);
10506
+ const tagsToAdd = [];
10507
+ for (const [k, v] of newMap) {
10508
+ if (oldMap.get(k) !== v)
10509
+ tagsToAdd.push({ Key: k, Value: v });
10510
+ }
10511
+ const tagsToRemove = [];
10512
+ for (const k of oldMap.keys()) {
10513
+ if (!newMap.has(k))
10514
+ tagsToRemove.push(k);
10515
+ }
10516
+ if (tagsToRemove.length > 0) {
10517
+ await this.iamClient.send(
10518
+ new UntagUserCommand({ UserName: userName, TagKeys: tagsToRemove })
10519
+ );
10520
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from IAM user ${userName}`);
10521
+ }
10522
+ if (tagsToAdd.length > 0) {
10523
+ await this.iamClient.send(new TagUserCommand({ UserName: userName, Tags: tagsToAdd }));
10524
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on IAM user ${userName}`);
10525
+ }
10526
+ }
10495
10527
  async updateUserManagedPolicies(userName, newPolicies, oldPolicies) {
10496
10528
  const newSet = new Set(newPolicies || []);
10497
10529
  const oldSet = new Set(oldPolicies || []);
@@ -11224,6 +11256,7 @@ import {
11224
11256
  ListBucketsCommand,
11225
11257
  PutBucketVersioningCommand as PutBucketVersioningCommand2,
11226
11258
  PutBucketTaggingCommand,
11259
+ DeleteBucketTaggingCommand,
11227
11260
  PutBucketOwnershipControlsCommand,
11228
11261
  PutBucketNotificationConfigurationCommand,
11229
11262
  PutBucketCorsCommand,
@@ -11347,6 +11380,52 @@ var S3BucketProvider = class {
11347
11380
  );
11348
11381
  this.logger.debug(`Applied ${tags.length} tags to bucket ${bucketName}`);
11349
11382
  }
11383
+ /**
11384
+ * Apply a diff between old and new CFn-shape Tags arrays via S3's
11385
+ * `PutBucketTagging` (full-replace) / `DeleteBucketTagging` APIs.
11386
+ *
11387
+ * S3's `PutBucketTagging` replaces the entire tag set in one call, so we
11388
+ * don't need separate add/remove API operations. When the new set is
11389
+ * empty, we issue `DeleteBucketTagging` to clear it. When old and new
11390
+ * are equal, we skip the call entirely.
11391
+ */
11392
+ async applyTagDiff(bucketName, oldTagsRaw, newTagsRaw) {
11393
+ const normalize = (tags) => {
11394
+ const out = [];
11395
+ for (const t of tags ?? []) {
11396
+ if (t.Key !== void 0 && t.Value !== void 0)
11397
+ out.push({ Key: t.Key, Value: t.Value });
11398
+ }
11399
+ return out;
11400
+ };
11401
+ const oldNorm = normalize(oldTagsRaw);
11402
+ const newNorm = normalize(newTagsRaw);
11403
+ if (JSON.stringify(oldNorm) === JSON.stringify(newNorm))
11404
+ return;
11405
+ if (newNorm.length === 0) {
11406
+ try {
11407
+ await this.s3Client.send(
11408
+ new DeleteBucketTaggingCommand({
11409
+ Bucket: bucketName
11410
+ })
11411
+ );
11412
+ this.logger.debug(`Cleared tags from bucket ${bucketName}`);
11413
+ } catch (err) {
11414
+ const e = err;
11415
+ if (e.name === "NoSuchTagSet")
11416
+ return;
11417
+ throw err;
11418
+ }
11419
+ return;
11420
+ }
11421
+ await this.s3Client.send(
11422
+ new PutBucketTaggingCommand({
11423
+ Bucket: bucketName,
11424
+ Tagging: { TagSet: newNorm }
11425
+ })
11426
+ );
11427
+ this.logger.debug(`Replaced tag set on bucket ${bucketName} (${newNorm.length} tags)`);
11428
+ }
11350
11429
  /**
11351
11430
  * Apply CORS configuration
11352
11431
  *
@@ -11890,13 +11969,13 @@ var S3BucketProvider = class {
11890
11969
  /**
11891
11970
  * Apply additional bucket configuration after creation
11892
11971
  */
11893
- async applyConfiguration(bucketName, properties) {
11972
+ async applyConfiguration(bucketName, properties, skipTags = false) {
11894
11973
  const versioningConfig = properties["VersioningConfiguration"];
11895
11974
  if (versioningConfig) {
11896
11975
  await this.applyVersioning(bucketName, versioningConfig);
11897
11976
  }
11898
11977
  const tags = properties["Tags"];
11899
- if (tags && Array.isArray(tags) && tags.length > 0) {
11978
+ if (!skipTags && tags && Array.isArray(tags) && tags.length > 0) {
11900
11979
  await this.applyTags(bucketName, tags);
11901
11980
  }
11902
11981
  const ownershipControls = properties["OwnershipControls"];
@@ -12035,7 +12114,7 @@ var S3BucketProvider = class {
12035
12114
  /**
12036
12115
  * Update an S3 bucket
12037
12116
  */
12038
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
12117
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
12039
12118
  this.logger.debug(`Updating S3 bucket ${logicalId}: ${physicalId}`);
12040
12119
  const newBucketName = properties["BucketName"];
12041
12120
  if (newBucketName && newBucketName !== physicalId) {
@@ -12048,7 +12127,17 @@ var S3BucketProvider = class {
12048
12127
  };
12049
12128
  }
12050
12129
  try {
12051
- await this.applyConfiguration(physicalId, properties);
12130
+ await this.applyConfiguration(
12131
+ physicalId,
12132
+ properties,
12133
+ /* skipTags */
12134
+ true
12135
+ );
12136
+ await this.applyTagDiff(
12137
+ physicalId,
12138
+ previousProperties["Tags"],
12139
+ properties["Tags"]
12140
+ );
12052
12141
  const attributes = await this.buildAttributes(physicalId);
12053
12142
  this.logger.debug(`Successfully updated S3 bucket ${logicalId}`);
12054
12143
  return {
@@ -12546,10 +12635,20 @@ import {
12546
12635
  ListQueuesCommand,
12547
12636
  ListQueueTagsCommand,
12548
12637
  SetQueueAttributesCommand,
12638
+ TagQueueCommand,
12639
+ UntagQueueCommand,
12549
12640
  QueueDoesNotExist
12550
12641
  } from "@aws-sdk/client-sqs";
12551
12642
  import { GetCallerIdentityCommand as GetCallerIdentityCommand4 } from "@aws-sdk/client-sts";
12552
12643
  init_aws_clients();
12644
+ function serializeRedrivePolicy(value) {
12645
+ if (value === null || value === void 0)
12646
+ return "";
12647
+ if (typeof value === "object" && Object.keys(value).length === 0) {
12648
+ return "";
12649
+ }
12650
+ return JSON.stringify(value);
12651
+ }
12553
12652
  var CDK_TO_SQS_ATTRIBUTES = {
12554
12653
  VisibilityTimeout: "VisibilityTimeout",
12555
12654
  MaximumMessageSize: "MaximumMessageSize",
@@ -12608,7 +12707,7 @@ var SQSQueueProvider = class {
12608
12707
  if (properties[cdkKey] !== void 0) {
12609
12708
  const value = properties[cdkKey];
12610
12709
  if (cdkKey === "RedrivePolicy" && typeof value === "object") {
12611
- attributes[sqsKey] = JSON.stringify(value);
12710
+ attributes[sqsKey] = serializeRedrivePolicy(value);
12612
12711
  } else {
12613
12712
  attributes[sqsKey] = String(value);
12614
12713
  }
@@ -12656,7 +12755,7 @@ var SQSQueueProvider = class {
12656
12755
  /**
12657
12756
  * Update an SQS queue
12658
12757
  */
12659
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
12758
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
12660
12759
  this.logger.debug(`Updating SQS queue ${logicalId}: ${physicalId}`);
12661
12760
  try {
12662
12761
  const attributes = {};
@@ -12666,7 +12765,7 @@ var SQSQueueProvider = class {
12666
12765
  if (properties[cdkKey] !== void 0) {
12667
12766
  const value = properties[cdkKey];
12668
12767
  if (cdkKey === "RedrivePolicy" && typeof value === "object") {
12669
- attributes[sqsKey] = JSON.stringify(value);
12768
+ attributes[sqsKey] = serializeRedrivePolicy(value);
12670
12769
  } else {
12671
12770
  attributes[sqsKey] = String(value);
12672
12771
  }
@@ -12681,6 +12780,11 @@ var SQSQueueProvider = class {
12681
12780
  );
12682
12781
  this.logger.debug(`Updated attributes for SQS queue ${physicalId}`);
12683
12782
  }
12783
+ await this.applyTagDiff(
12784
+ physicalId,
12785
+ previousProperties["Tags"],
12786
+ properties["Tags"]
12787
+ );
12684
12788
  const getResponse = await this.sqsClient.send(
12685
12789
  new GetQueueAttributesCommand({
12686
12790
  QueueUrl: physicalId,
@@ -12739,6 +12843,46 @@ var SQSQueueProvider = class {
12739
12843
  );
12740
12844
  }
12741
12845
  }
12846
+ /**
12847
+ * Apply a diff between old and new CFn-shape Tags arrays via SQS's
12848
+ * `TagQueue` / `UntagQueue` APIs. SQS's `TagQueue` takes a `Tags` map
12849
+ * (`{ key: value }`); `UntagQueue` takes a `TagKeys` array. cdkd state
12850
+ * holds Tags in CFn shape (`[{ Key, Value }]`).
12851
+ */
12852
+ async applyTagDiff(queueUrl, oldTagsRaw, newTagsRaw) {
12853
+ const toMap = (tags) => {
12854
+ const m = /* @__PURE__ */ new Map();
12855
+ for (const t of tags ?? []) {
12856
+ if (t.Key !== void 0 && t.Value !== void 0)
12857
+ m.set(t.Key, t.Value);
12858
+ }
12859
+ return m;
12860
+ };
12861
+ const oldMap = toMap(oldTagsRaw);
12862
+ const newMap = toMap(newTagsRaw);
12863
+ const tagsToAdd = {};
12864
+ for (const [k, v] of newMap) {
12865
+ if (oldMap.get(k) !== v)
12866
+ tagsToAdd[k] = v;
12867
+ }
12868
+ const tagsToRemove = [];
12869
+ for (const k of oldMap.keys()) {
12870
+ if (!newMap.has(k))
12871
+ tagsToRemove.push(k);
12872
+ }
12873
+ if (tagsToRemove.length > 0) {
12874
+ await this.sqsClient.send(
12875
+ new UntagQueueCommand({ QueueUrl: queueUrl, TagKeys: tagsToRemove })
12876
+ );
12877
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from SQS queue ${queueUrl}`);
12878
+ }
12879
+ if (Object.keys(tagsToAdd).length > 0) {
12880
+ await this.sqsClient.send(new TagQueueCommand({ QueueUrl: queueUrl, Tags: tagsToAdd }));
12881
+ this.logger.debug(
12882
+ `Added/updated ${Object.keys(tagsToAdd).length} tag(s) on SQS queue ${queueUrl}`
12883
+ );
12884
+ }
12885
+ }
12742
12886
  /**
12743
12887
  * Construct SQS queue ARN from account/region/queue name
12744
12888
  */
@@ -14080,6 +14224,8 @@ import {
14080
14224
  GetFunctionCommand,
14081
14225
  ListFunctionsCommand,
14082
14226
  ListTagsCommand,
14227
+ TagResourceCommand as TagResourceCommand2,
14228
+ UntagResourceCommand as UntagResourceCommand2,
14083
14229
  ResourceNotFoundException,
14084
14230
  waitUntilFunctionUpdatedV2 as waitUntilFunctionUpdatedV22
14085
14231
  } from "@aws-sdk/client-lambda";
@@ -14297,11 +14443,17 @@ var LambdaFunctionProvider = class {
14297
14443
  const getResponse = await this.lambdaClient.send(
14298
14444
  new GetFunctionCommand({ FunctionName: physicalId })
14299
14445
  );
14446
+ const functionArn = getResponse.Configuration?.FunctionArn;
14447
+ await this.applyTagDiff(
14448
+ functionArn,
14449
+ previousProperties["Tags"],
14450
+ properties["Tags"]
14451
+ );
14300
14452
  return {
14301
14453
  physicalId,
14302
14454
  wasReplaced: false,
14303
14455
  attributes: {
14304
- Arn: getResponse.Configuration?.FunctionArn,
14456
+ Arn: functionArn,
14305
14457
  FunctionName: getResponse.Configuration?.FunctionName
14306
14458
  }
14307
14459
  };
@@ -14493,6 +14645,53 @@ var LambdaFunctionProvider = class {
14493
14645
  * one resource type that actually needs it preserves the bug fix
14494
14646
  * without paying the whole-stack tax.
14495
14647
  */
14648
+ /**
14649
+ * Apply a diff between old and new CFn-shape Tags arrays via Lambda's
14650
+ * `TagResource` / `UntagResource` APIs. Without this, `cdkd deploy`
14651
+ * and `cdkd drift --revert` silently no-op tag changes — the
14652
+ * `UpdateFunctionConfiguration` command does NOT accept a Tags
14653
+ * parameter (Lambda treats tags as a separate API surface).
14654
+ */
14655
+ async applyTagDiff(functionArn, oldTagsRaw, newTagsRaw) {
14656
+ if (!functionArn)
14657
+ return;
14658
+ const toMap = (tags) => {
14659
+ const m = /* @__PURE__ */ new Map();
14660
+ for (const t of tags ?? []) {
14661
+ if (t.Key !== void 0 && t.Value !== void 0)
14662
+ m.set(t.Key, t.Value);
14663
+ }
14664
+ return m;
14665
+ };
14666
+ const oldMap = toMap(oldTagsRaw);
14667
+ const newMap = toMap(newTagsRaw);
14668
+ const tagsToAdd = {};
14669
+ for (const [k, v] of newMap) {
14670
+ if (oldMap.get(k) !== v)
14671
+ tagsToAdd[k] = v;
14672
+ }
14673
+ const tagsToRemove = [];
14674
+ for (const k of oldMap.keys()) {
14675
+ if (!newMap.has(k))
14676
+ tagsToRemove.push(k);
14677
+ }
14678
+ if (tagsToRemove.length > 0) {
14679
+ await this.lambdaClient.send(
14680
+ new UntagResourceCommand2({ Resource: functionArn, TagKeys: tagsToRemove })
14681
+ );
14682
+ this.logger.debug(
14683
+ `Removed ${tagsToRemove.length} tag(s) from Lambda function ${functionArn}`
14684
+ );
14685
+ }
14686
+ if (Object.keys(tagsToAdd).length > 0) {
14687
+ await this.lambdaClient.send(
14688
+ new TagResourceCommand2({ Resource: functionArn, Tags: tagsToAdd })
14689
+ );
14690
+ this.logger.debug(
14691
+ `Added/updated ${Object.keys(tagsToAdd).length} tag(s) on Lambda function ${functionArn}`
14692
+ );
14693
+ }
14694
+ }
14496
14695
  async waitForFunctionUpdated(logicalId, resourceType, functionName) {
14497
14696
  try {
14498
14697
  await waitUntilFunctionUpdatedV22(
@@ -14827,14 +15026,11 @@ var LambdaFunctionProvider = class {
14827
15026
  if (cfg.EphemeralStorage?.Size !== void 0) {
14828
15027
  result["EphemeralStorage"] = { Size: cfg.EphemeralStorage.Size };
14829
15028
  }
14830
- const vpc = {
15029
+ result["VpcConfig"] = {
14831
15030
  SubnetIds: cfg.VpcConfig?.SubnetIds ? [...cfg.VpcConfig.SubnetIds] : [],
14832
- SecurityGroupIds: cfg.VpcConfig?.SecurityGroupIds ? [...cfg.VpcConfig.SecurityGroupIds] : []
15031
+ SecurityGroupIds: cfg.VpcConfig?.SecurityGroupIds ? [...cfg.VpcConfig.SecurityGroupIds] : [],
15032
+ Ipv6AllowedForDualStack: cfg.VpcConfig?.Ipv6AllowedForDualStack ?? false
14833
15033
  };
14834
- if (cfg.VpcConfig?.Ipv6AllowedForDualStack !== void 0) {
14835
- vpc["Ipv6AllowedForDualStack"] = cfg.VpcConfig.Ipv6AllowedForDualStack;
14836
- }
14837
- result["VpcConfig"] = vpc;
14838
15034
  const tags = normalizeAwsTagsToCfn(resp.Tags);
14839
15035
  result["Tags"] = tags;
14840
15036
  return result;
@@ -15468,6 +15664,8 @@ import {
15468
15664
  DeleteEventSourceMappingCommand,
15469
15665
  UpdateEventSourceMappingCommand,
15470
15666
  GetEventSourceMappingCommand,
15667
+ TagResourceCommand as TagResourceCommand3,
15668
+ UntagResourceCommand as UntagResourceCommand3,
15471
15669
  ResourceNotFoundException as ResourceNotFoundException4
15472
15670
  } from "@aws-sdk/client-lambda";
15473
15671
  init_aws_clients();
@@ -15591,7 +15789,7 @@ var LambdaEventSourceMappingProvider = class {
15591
15789
  /**
15592
15790
  * Update a Lambda Event Source Mapping
15593
15791
  */
15594
- async update(logicalId, physicalId, _resourceType, properties, _previousProperties) {
15792
+ async update(logicalId, physicalId, _resourceType, properties, previousProperties) {
15595
15793
  this.logger.debug(`Updating event source mapping ${logicalId}: ${physicalId}`);
15596
15794
  const updateParams = {
15597
15795
  UUID: physicalId,
@@ -15625,7 +15823,17 @@ var LambdaEventSourceMappingProvider = class {
15625
15823
  updateParams.ScalingConfig = properties["ScalingConfig"];
15626
15824
  if (properties["DocumentDBEventSourceConfig"])
15627
15825
  updateParams.DocumentDBEventSourceConfig = properties["DocumentDBEventSourceConfig"];
15628
- await this.lambdaClient.send(new UpdateEventSourceMappingCommand(updateParams));
15826
+ const updateResp = await this.lambdaClient.send(
15827
+ new UpdateEventSourceMappingCommand(updateParams)
15828
+ );
15829
+ const eventSourceMappingArn = updateResp.EventSourceMappingArn;
15830
+ if (eventSourceMappingArn) {
15831
+ await this.applyTagDiff(
15832
+ eventSourceMappingArn,
15833
+ previousProperties["Tags"],
15834
+ properties["Tags"]
15835
+ );
15836
+ }
15629
15837
  this.logger.debug(`Successfully updated event source mapping ${logicalId}`);
15630
15838
  return {
15631
15839
  physicalId,
@@ -15635,6 +15843,46 @@ var LambdaEventSourceMappingProvider = class {
15635
15843
  }
15636
15844
  };
15637
15845
  }
15846
+ /**
15847
+ * Apply a diff between old and new CFn-shape Tags arrays via Lambda's
15848
+ * `TagResource` / `UntagResource` APIs against the EventSourceMapping
15849
+ * ARN. Lambda's `TagResource` takes `{ Resource, Tags: { key: value } }`;
15850
+ * `UntagResource` takes `{ Resource, TagKeys: [...] }`.
15851
+ */
15852
+ async applyTagDiff(arn, oldTagsRaw, newTagsRaw) {
15853
+ const toMap = (tags) => {
15854
+ const m = /* @__PURE__ */ new Map();
15855
+ for (const t of tags ?? []) {
15856
+ if (t.Key !== void 0 && t.Value !== void 0)
15857
+ m.set(t.Key, t.Value);
15858
+ }
15859
+ return m;
15860
+ };
15861
+ const oldMap = toMap(oldTagsRaw);
15862
+ const newMap = toMap(newTagsRaw);
15863
+ const tagsToAdd = {};
15864
+ for (const [k, v] of newMap) {
15865
+ if (oldMap.get(k) !== v)
15866
+ tagsToAdd[k] = v;
15867
+ }
15868
+ const tagsToRemove = [];
15869
+ for (const k of oldMap.keys()) {
15870
+ if (!newMap.has(k))
15871
+ tagsToRemove.push(k);
15872
+ }
15873
+ if (tagsToRemove.length > 0) {
15874
+ await this.lambdaClient.send(
15875
+ new UntagResourceCommand3({ Resource: arn, TagKeys: tagsToRemove })
15876
+ );
15877
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from EventSourceMapping ${arn}`);
15878
+ }
15879
+ if (Object.keys(tagsToAdd).length > 0) {
15880
+ await this.lambdaClient.send(new TagResourceCommand3({ Resource: arn, Tags: tagsToAdd }));
15881
+ this.logger.debug(
15882
+ `Added/updated ${Object.keys(tagsToAdd).length} tag(s) on EventSourceMapping ${arn}`
15883
+ );
15884
+ }
15885
+ }
15638
15886
  /**
15639
15887
  * Delete a Lambda Event Source Mapping
15640
15888
  */
@@ -16068,6 +16316,8 @@ import {
16068
16316
  DescribeTableCommand as DescribeTableCommand2,
16069
16317
  ListTablesCommand,
16070
16318
  ListTagsOfResourceCommand,
16319
+ TagResourceCommand as TagResourceCommand4,
16320
+ UntagResourceCommand as UntagResourceCommand4,
16071
16321
  ResourceNotFoundException as ResourceNotFoundException6
16072
16322
  } from "@aws-sdk/client-dynamodb";
16073
16323
  init_aws_clients();
@@ -16193,13 +16443,20 @@ var DynamoDBTableProvider = class {
16193
16443
  * For immutable property changes (KeySchema, etc.), the deployment layer
16194
16444
  * handles replacement via DELETE + CREATE.
16195
16445
  */
16196
- async update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
16446
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
16197
16447
  this.logger.debug(`Updating DynamoDB table ${logicalId}: ${physicalId}`);
16198
16448
  try {
16199
16449
  const response = await this.dynamoDBClient.send(
16200
16450
  new DescribeTableCommand2({ TableName: physicalId })
16201
16451
  );
16202
16452
  const table = response.Table;
16453
+ if (table?.TableArn) {
16454
+ await this.applyTagDiff(
16455
+ table.TableArn,
16456
+ previousProperties["Tags"],
16457
+ properties["Tags"]
16458
+ );
16459
+ }
16203
16460
  return {
16204
16461
  physicalId,
16205
16462
  wasReplaced: false,
@@ -16252,6 +16509,45 @@ var DynamoDBTableProvider = class {
16252
16509
  );
16253
16510
  }
16254
16511
  }
16512
+ /**
16513
+ * Apply a diff between old and new CFn-shape Tags arrays via DynamoDB's
16514
+ * `TagResource` / `UntagResource` APIs. Both take the table ARN as
16515
+ * `ResourceArn`.
16516
+ */
16517
+ async applyTagDiff(tableArn, oldTagsRaw, newTagsRaw) {
16518
+ const toMap = (tags) => {
16519
+ const m = /* @__PURE__ */ new Map();
16520
+ for (const t of tags ?? []) {
16521
+ if (t.Key !== void 0 && t.Value !== void 0)
16522
+ m.set(t.Key, t.Value);
16523
+ }
16524
+ return m;
16525
+ };
16526
+ const oldMap = toMap(oldTagsRaw);
16527
+ const newMap = toMap(newTagsRaw);
16528
+ const tagsToAdd = [];
16529
+ for (const [k, v] of newMap) {
16530
+ if (oldMap.get(k) !== v)
16531
+ tagsToAdd.push({ Key: k, Value: v });
16532
+ }
16533
+ const tagsToRemove = [];
16534
+ for (const k of oldMap.keys()) {
16535
+ if (!newMap.has(k))
16536
+ tagsToRemove.push(k);
16537
+ }
16538
+ if (tagsToRemove.length > 0) {
16539
+ await this.dynamoDBClient.send(
16540
+ new UntagResourceCommand4({ ResourceArn: tableArn, TagKeys: tagsToRemove })
16541
+ );
16542
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from DynamoDB table ${tableArn}`);
16543
+ }
16544
+ if (tagsToAdd.length > 0) {
16545
+ await this.dynamoDBClient.send(
16546
+ new TagResourceCommand4({ ResourceArn: tableArn, Tags: tagsToAdd })
16547
+ );
16548
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on DynamoDB table ${tableArn}`);
16549
+ }
16550
+ }
16255
16551
  /**
16256
16552
  * Poll DescribeTable until the table reaches ACTIVE status
16257
16553
  *
@@ -16471,8 +16767,8 @@ import {
16471
16767
  ListTagsForResourceCommand as ListTagsForResourceCommand2,
16472
16768
  PutRetentionPolicyCommand,
16473
16769
  DeleteRetentionPolicyCommand,
16474
- TagResourceCommand as TagResourceCommand2,
16475
- UntagResourceCommand as UntagResourceCommand2,
16770
+ TagResourceCommand as TagResourceCommand5,
16771
+ UntagResourceCommand as UntagResourceCommand5,
16476
16772
  PutDataProtectionPolicyCommand,
16477
16773
  DeleteDataProtectionPolicyCommand,
16478
16774
  ResourceNotFoundException as ResourceNotFoundException7,
@@ -16622,7 +16918,7 @@ var LogsLogGroupProvider = class {
16622
16918
  if (oldTags && oldTags.length > 0) {
16623
16919
  const oldTagKeys = oldTags.map((t) => t.Key);
16624
16920
  await this.logsClient.send(
16625
- new UntagResourceCommand2({
16921
+ new UntagResourceCommand5({
16626
16922
  resourceArn: arn2,
16627
16923
  tagKeys: oldTagKeys
16628
16924
  })
@@ -16631,7 +16927,7 @@ var LogsLogGroupProvider = class {
16631
16927
  if (newTags && newTags.length > 0) {
16632
16928
  const tagsMap = Object.fromEntries(newTags.map((t) => [t.Key, t.Value]));
16633
16929
  await this.logsClient.send(
16634
- new TagResourceCommand2({
16930
+ new TagResourceCommand5({
16635
16931
  resourceArn: arn2,
16636
16932
  tags: tagsMap
16637
16933
  })
@@ -16831,7 +17127,9 @@ import {
16831
17127
  PutMetricAlarmCommand,
16832
17128
  DeleteAlarmsCommand,
16833
17129
  DescribeAlarmsCommand,
16834
- ListTagsForResourceCommand as ListTagsForResourceCommand3
17130
+ ListTagsForResourceCommand as ListTagsForResourceCommand3,
17131
+ TagResourceCommand as TagResourceCommand6,
17132
+ UntagResourceCommand as UntagResourceCommand6
16835
17133
  } from "@aws-sdk/client-cloudwatch";
16836
17134
  init_aws_clients();
16837
17135
  var CloudWatchAlarmProvider = class {
@@ -16900,7 +17198,7 @@ var CloudWatchAlarmProvider = class {
16900
17198
  *
16901
17199
  * PutMetricAlarm is idempotent - calling it with the same alarm name updates the alarm.
16902
17200
  */
16903
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
17201
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
16904
17202
  this.logger.debug(`Updating CloudWatch alarm ${logicalId}: ${physicalId}`);
16905
17203
  try {
16906
17204
  await this.cloudWatchClient.send(
@@ -16908,6 +17206,11 @@ var CloudWatchAlarmProvider = class {
16908
17206
  );
16909
17207
  this.logger.debug(`Successfully updated CloudWatch alarm ${logicalId}`);
16910
17208
  const alarmArn = await this.getAlarmArn(physicalId);
17209
+ await this.applyTagDiff(
17210
+ alarmArn,
17211
+ previousProperties["Tags"],
17212
+ properties["Tags"]
17213
+ );
16911
17214
  return {
16912
17215
  physicalId,
16913
17216
  wasReplaced: false,
@@ -16992,6 +17295,44 @@ var CloudWatchAlarmProvider = class {
16992
17295
  return `arn:aws:cloudwatch:*:*:alarm:${alarmName}`;
16993
17296
  }
16994
17297
  }
17298
+ /**
17299
+ * Apply a diff between old and new CFn-shape Tags arrays via CloudWatch's
17300
+ * `TagResource` / `UntagResource` APIs (keyed by `ResourceARN`).
17301
+ */
17302
+ async applyTagDiff(resourceArn, oldTagsRaw, newTagsRaw) {
17303
+ const toMap = (tags) => {
17304
+ const m = /* @__PURE__ */ new Map();
17305
+ for (const t of tags ?? []) {
17306
+ if (t.Key !== void 0 && t.Value !== void 0)
17307
+ m.set(t.Key, t.Value);
17308
+ }
17309
+ return m;
17310
+ };
17311
+ const oldMap = toMap(oldTagsRaw);
17312
+ const newMap = toMap(newTagsRaw);
17313
+ const tagsToAdd = [];
17314
+ for (const [k, v] of newMap) {
17315
+ if (oldMap.get(k) !== v)
17316
+ tagsToAdd.push({ Key: k, Value: v });
17317
+ }
17318
+ const tagsToRemove = [];
17319
+ for (const k of oldMap.keys()) {
17320
+ if (!newMap.has(k))
17321
+ tagsToRemove.push(k);
17322
+ }
17323
+ if (tagsToRemove.length > 0) {
17324
+ await this.cloudWatchClient.send(
17325
+ new UntagResourceCommand6({ ResourceARN: resourceArn, TagKeys: tagsToRemove })
17326
+ );
17327
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from alarm ${resourceArn}`);
17328
+ }
17329
+ if (tagsToAdd.length > 0) {
17330
+ await this.cloudWatchClient.send(
17331
+ new TagResourceCommand6({ ResourceARN: resourceArn, Tags: tagsToAdd })
17332
+ );
17333
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on alarm ${resourceArn}`);
17334
+ }
17335
+ }
16995
17336
  /**
16996
17337
  * Build PutMetricAlarm parameters from CDK properties
16997
17338
  */
@@ -17184,8 +17525,8 @@ import {
17184
17525
  DescribeSecretCommand,
17185
17526
  ListSecretsCommand,
17186
17527
  UpdateSecretCommand,
17187
- TagResourceCommand as TagResourceCommand3,
17188
- UntagResourceCommand as UntagResourceCommand3,
17528
+ TagResourceCommand as TagResourceCommand7,
17529
+ UntagResourceCommand as UntagResourceCommand7,
17189
17530
  ReplicateSecretToRegionsCommand,
17190
17531
  RemoveRegionsFromReplicationCommand,
17191
17532
  ResourceNotFoundException as ResourceNotFoundException8
@@ -17298,7 +17639,7 @@ var SecretsManagerSecretProvider = class {
17298
17639
  const oldTagKeys = oldTags.map((t) => t.Key).filter((k) => !!k);
17299
17640
  if (oldTagKeys.length > 0) {
17300
17641
  await this.smClient.send(
17301
- new UntagResourceCommand3({
17642
+ new UntagResourceCommand7({
17302
17643
  SecretId: physicalId,
17303
17644
  TagKeys: oldTagKeys
17304
17645
  })
@@ -17307,7 +17648,7 @@ var SecretsManagerSecretProvider = class {
17307
17648
  }
17308
17649
  if (newTags && newTags.length > 0) {
17309
17650
  await this.smClient.send(
17310
- new TagResourceCommand3({
17651
+ new TagResourceCommand7({
17311
17652
  SecretId: physicalId,
17312
17653
  Tags: newTags
17313
17654
  })
@@ -17911,8 +18252,8 @@ import {
17911
18252
  ListRulesCommand,
17912
18253
  ListTargetsByRuleCommand,
17913
18254
  ListTagsForResourceCommand as ListTagsForResourceCommand5,
17914
- TagResourceCommand as TagResourceCommand4,
17915
- UntagResourceCommand as UntagResourceCommand4,
18255
+ TagResourceCommand as TagResourceCommand8,
18256
+ UntagResourceCommand as UntagResourceCommand8,
17916
18257
  ResourceNotFoundException as ResourceNotFoundException9
17917
18258
  } from "@aws-sdk/client-eventbridge";
17918
18259
  init_aws_clients();
@@ -18065,7 +18406,7 @@ var EventBridgeRuleProvider = class {
18065
18406
  const oldTagKeys = oldTags.map((t) => t.Key).filter((k) => !!k);
18066
18407
  if (oldTagKeys.length > 0) {
18067
18408
  await this.eventBridgeClient.send(
18068
- new UntagResourceCommand4({
18409
+ new UntagResourceCommand8({
18069
18410
  ResourceARN: ruleArn,
18070
18411
  TagKeys: oldTagKeys
18071
18412
  })
@@ -18074,7 +18415,7 @@ var EventBridgeRuleProvider = class {
18074
18415
  }
18075
18416
  if (newTags && newTags.length > 0) {
18076
18417
  await this.eventBridgeClient.send(
18077
- new TagResourceCommand4({
18418
+ new TagResourceCommand8({
18078
18419
  ResourceARN: ruleArn,
18079
18420
  Tags: newTags
18080
18421
  })
@@ -18381,8 +18722,8 @@ import {
18381
18722
  RemoveTargetsCommand as RemoveTargetsCommand2,
18382
18723
  DeleteRuleCommand as DeleteRuleCommand2,
18383
18724
  ListTargetsByRuleCommand as ListTargetsByRuleCommand2,
18384
- TagResourceCommand as TagResourceCommand5,
18385
- UntagResourceCommand as UntagResourceCommand5,
18725
+ TagResourceCommand as TagResourceCommand9,
18726
+ UntagResourceCommand as UntagResourceCommand9,
18386
18727
  ResourceNotFoundException as ResourceNotFoundException10
18387
18728
  } from "@aws-sdk/client-eventbridge";
18388
18729
  init_aws_clients();
@@ -18495,7 +18836,7 @@ var EventBridgeBusProvider = class {
18495
18836
  const oldTagKeys = oldTags.map((t) => t.Key).filter((k) => !!k);
18496
18837
  if (oldTagKeys.length > 0) {
18497
18838
  await this.eventBridgeClient.send(
18498
- new UntagResourceCommand5({
18839
+ new UntagResourceCommand9({
18499
18840
  ResourceARN: busArn,
18500
18841
  TagKeys: oldTagKeys
18501
18842
  })
@@ -18504,7 +18845,7 @@ var EventBridgeBusProvider = class {
18504
18845
  }
18505
18846
  if (newTags && newTags.length > 0) {
18506
18847
  await this.eventBridgeClient.send(
18507
- new TagResourceCommand5({
18848
+ new TagResourceCommand9({
18508
18849
  ResourceARN: busArn,
18509
18850
  Tags: newTags
18510
18851
  })
@@ -18756,6 +19097,7 @@ import {
18756
19097
  AuthorizeSecurityGroupEgressCommand,
18757
19098
  RevokeSecurityGroupEgressCommand,
18758
19099
  CreateTagsCommand,
19100
+ DeleteTagsCommand,
18759
19101
  DescribeSubnetsCommand as DescribeSubnetsCommand2,
18760
19102
  DescribeSecurityGroupsCommand as DescribeSecurityGroupsCommand2,
18761
19103
  RunInstancesCommand,
@@ -18920,7 +19262,7 @@ var EC2Provider = class {
18920
19262
  async update(logicalId, physicalId, resourceType, properties, previousProperties) {
18921
19263
  switch (resourceType) {
18922
19264
  case "AWS::EC2::VPC":
18923
- return this.updateVpc(logicalId, physicalId, resourceType, properties);
19265
+ return this.updateVpc(logicalId, physicalId, resourceType, properties, previousProperties);
18924
19266
  case "AWS::EC2::Subnet":
18925
19267
  return this.updateSubnet(logicalId, physicalId);
18926
19268
  case "AWS::EC2::InternetGateway":
@@ -18958,7 +19300,13 @@ var EC2Provider = class {
18958
19300
  previousProperties
18959
19301
  );
18960
19302
  case "AWS::EC2::Instance":
18961
- return this.updateInstance(logicalId, physicalId, resourceType, properties);
19303
+ return this.updateInstance(
19304
+ logicalId,
19305
+ physicalId,
19306
+ resourceType,
19307
+ properties,
19308
+ previousProperties
19309
+ );
18962
19310
  case "AWS::EC2::NetworkAcl":
18963
19311
  case "AWS::EC2::NetworkAclEntry":
18964
19312
  case "AWS::EC2::SubnetNetworkAclAssociation":
@@ -19104,7 +19452,7 @@ var EC2Provider = class {
19104
19452
  );
19105
19453
  }
19106
19454
  }
19107
- async updateVpc(logicalId, physicalId, resourceType, properties) {
19455
+ async updateVpc(logicalId, physicalId, resourceType, properties, previousProperties) {
19108
19456
  this.logger.debug(`Updating VPC ${logicalId}: ${physicalId}`);
19109
19457
  try {
19110
19458
  if (properties["EnableDnsHostnames"] !== void 0) {
@@ -19125,7 +19473,11 @@ var EC2Provider = class {
19125
19473
  })
19126
19474
  );
19127
19475
  }
19128
- await this.applyTags(physicalId, properties, logicalId);
19476
+ await this.applyTagDiff(
19477
+ physicalId,
19478
+ previousProperties["Tags"],
19479
+ properties["Tags"]
19480
+ );
19129
19481
  this.logger.debug(`Successfully updated VPC ${logicalId}`);
19130
19482
  return {
19131
19483
  physicalId,
@@ -19995,7 +20347,11 @@ var EC2Provider = class {
19995
20347
  async updateSecurityGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
19996
20348
  this.logger.debug(`Updating SecurityGroup ${logicalId}: ${physicalId}`);
19997
20349
  try {
19998
- await this.applyTags(physicalId, properties, logicalId);
20350
+ await this.applyTagDiff(
20351
+ physicalId,
20352
+ previousProperties["Tags"],
20353
+ properties["Tags"]
20354
+ );
19999
20355
  await this.applySecurityGroupRuleDiff(
20000
20356
  physicalId,
20001
20357
  previousProperties["SecurityGroupIngress"] ?? [],
@@ -20323,10 +20679,14 @@ var EC2Provider = class {
20323
20679
  );
20324
20680
  }
20325
20681
  }
20326
- async updateInstance(logicalId, physicalId, resourceType, _properties) {
20682
+ async updateInstance(logicalId, physicalId, resourceType, properties, previousProperties) {
20327
20683
  this.logger.debug(`Updating EC2 Instance ${logicalId}: ${physicalId}`);
20328
20684
  try {
20329
- await this.applyTags(physicalId, _properties, logicalId);
20685
+ await this.applyTagDiff(
20686
+ physicalId,
20687
+ previousProperties["Tags"],
20688
+ properties["Tags"]
20689
+ );
20330
20690
  const describeResponse = await this.ec2Client.send(
20331
20691
  new DescribeInstancesCommand({ InstanceIds: [physicalId] })
20332
20692
  );
@@ -20801,7 +21161,10 @@ var EC2Provider = class {
20801
21161
  }
20802
21162
  }
20803
21163
  /**
20804
- * Apply tags to an EC2 resource
21164
+ * Apply tags to an EC2 resource (create-time, no removal).
21165
+ *
21166
+ * Used by `create*` paths. Update paths should use `applyTagDiff` instead
21167
+ * to handle tag removal too.
20805
21168
  */
20806
21169
  async applyTags(resourceId, properties, logicalId) {
20807
21170
  const tags = properties["Tags"];
@@ -20821,6 +21184,64 @@ var EC2Provider = class {
20821
21184
  }
20822
21185
  }
20823
21186
  }
21187
+ /**
21188
+ * Apply a diff between old and new CFn-shape Tags arrays via EC2's
21189
+ * `CreateTags` / `DeleteTags` APIs. Used by `update*` paths so that
21190
+ * tag removals reach AWS too. EC2 keys both APIs by a list of resource
21191
+ * ids.
21192
+ */
21193
+ async applyTagDiff(resourceId, oldTagsRaw, newTagsRaw) {
21194
+ const toMap = (tags) => {
21195
+ const m = /* @__PURE__ */ new Map();
21196
+ for (const t of tags ?? []) {
21197
+ if (t.Key !== void 0 && t.Value !== void 0)
21198
+ m.set(t.Key, t.Value);
21199
+ }
21200
+ return m;
21201
+ };
21202
+ const oldMap = toMap(oldTagsRaw);
21203
+ const newMap = toMap(newTagsRaw);
21204
+ const tagsToAdd = [];
21205
+ for (const [k, v] of newMap) {
21206
+ if (oldMap.get(k) !== v)
21207
+ tagsToAdd.push({ Key: k, Value: v });
21208
+ }
21209
+ const tagsToRemove = [];
21210
+ for (const k of oldMap.keys()) {
21211
+ if (!newMap.has(k))
21212
+ tagsToRemove.push({ Key: k });
21213
+ }
21214
+ if (tagsToRemove.length > 0) {
21215
+ try {
21216
+ await this.ec2Client.send(
21217
+ new DeleteTagsCommand({
21218
+ Resources: [resourceId],
21219
+ Tags: tagsToRemove
21220
+ })
21221
+ );
21222
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from ${resourceId}`);
21223
+ } catch (error) {
21224
+ this.logger.warn(
21225
+ `Failed to remove tags from ${resourceId}: ${error instanceof Error ? error.message : String(error)}`
21226
+ );
21227
+ }
21228
+ }
21229
+ if (tagsToAdd.length > 0) {
21230
+ try {
21231
+ await this.ec2Client.send(
21232
+ new CreateTagsCommand({
21233
+ Resources: [resourceId],
21234
+ Tags: tagsToAdd
21235
+ })
21236
+ );
21237
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on ${resourceId}`);
21238
+ } catch (error) {
21239
+ this.logger.warn(
21240
+ `Failed to add tags on ${resourceId}: ${error instanceof Error ? error.message : String(error)}`
21241
+ );
21242
+ }
21243
+ }
21244
+ }
20824
21245
  /**
20825
21246
  * Check if an error indicates the resource was not found
20826
21247
  */
@@ -21167,6 +21588,8 @@ import {
21167
21588
  CreateAuthorizerCommand,
21168
21589
  DeleteAuthorizerCommand,
21169
21590
  GetAuthorizerCommand,
21591
+ TagResourceCommand as TagResourceCommand10,
21592
+ UntagResourceCommand as UntagResourceCommand10,
21170
21593
  NotFoundException as NotFoundException3
21171
21594
  } from "@aws-sdk/client-api-gateway";
21172
21595
  init_aws_clients();
@@ -21890,24 +22313,24 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
21890
22313
  value: description ?? ""
21891
22314
  });
21892
22315
  }
21893
- if (patchOperations.length === 0) {
21894
- this.logger.debug(`No changes detected for API Gateway Stage ${logicalId}`);
21895
- return {
21896
- physicalId,
21897
- wasReplaced: false,
21898
- attributes: {
21899
- StageName: physicalId
21900
- }
21901
- };
21902
- }
21903
22316
  try {
21904
- await this.apiGatewayClient.send(
21905
- new UpdateStageCommand({
21906
- restApiId,
21907
- stageName: physicalId,
21908
- patchOperations
21909
- })
21910
- );
22317
+ if (patchOperations.length > 0) {
22318
+ await this.apiGatewayClient.send(
22319
+ new UpdateStageCommand({
22320
+ restApiId,
22321
+ stageName: physicalId,
22322
+ patchOperations
22323
+ })
22324
+ );
22325
+ }
22326
+ const stageArn = await this.buildStageArn(restApiId, physicalId);
22327
+ if (stageArn) {
22328
+ await this.applyTagDiff(
22329
+ stageArn,
22330
+ previousProperties["Tags"],
22331
+ properties["Tags"]
22332
+ );
22333
+ }
21911
22334
  this.logger.debug(`Successfully updated API Gateway Stage ${logicalId}`);
21912
22335
  return {
21913
22336
  physicalId,
@@ -22142,6 +22565,63 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
22142
22565
  }
22143
22566
  return Promise.resolve(void 0);
22144
22567
  }
22568
+ /**
22569
+ * Build the ARN for an API Gateway Stage, used for tag mutations.
22570
+ *
22571
+ * Format: `arn:aws:apigateway:{region}::/restapis/{restApiId}/stages/{stageName}`.
22572
+ * The double colon (`::`) is intentional: API Gateway tagging uses an
22573
+ * account-id-less ARN.
22574
+ */
22575
+ async buildStageArn(restApiId, stageName) {
22576
+ try {
22577
+ const region = await this.apiGatewayClient.config.region();
22578
+ return `arn:aws:apigateway:${region}::/restapis/${restApiId}/stages/${stageName}`;
22579
+ } catch {
22580
+ return void 0;
22581
+ }
22582
+ }
22583
+ /**
22584
+ * Apply a diff between old and new CFn-shape Tags arrays via API Gateway's
22585
+ * `TagResource` / `UntagResource` APIs. API Gateway's `TagResource` takes
22586
+ * lowercase camelCase fields plus a tag-map (`{ resourceArn, tags: {key: value} }`);
22587
+ * `UntagResource` takes `{ resourceArn, tagKeys: [...] }`.
22588
+ */
22589
+ async applyTagDiff(resourceArn, oldTagsRaw, newTagsRaw) {
22590
+ const toMap = (tags) => {
22591
+ const m = /* @__PURE__ */ new Map();
22592
+ for (const t of tags ?? []) {
22593
+ if (t.Key !== void 0 && t.Value !== void 0)
22594
+ m.set(t.Key, t.Value);
22595
+ }
22596
+ return m;
22597
+ };
22598
+ const oldMap = toMap(oldTagsRaw);
22599
+ const newMap = toMap(newTagsRaw);
22600
+ const tagsToAdd = {};
22601
+ for (const [k, v] of newMap) {
22602
+ if (oldMap.get(k) !== v)
22603
+ tagsToAdd[k] = v;
22604
+ }
22605
+ const tagsToRemove = [];
22606
+ for (const k of oldMap.keys()) {
22607
+ if (!newMap.has(k))
22608
+ tagsToRemove.push(k);
22609
+ }
22610
+ if (tagsToRemove.length > 0) {
22611
+ await this.apiGatewayClient.send(
22612
+ new UntagResourceCommand10({ resourceArn, tagKeys: tagsToRemove })
22613
+ );
22614
+ this.logger.debug(
22615
+ `Removed ${tagsToRemove.length} tag(s) from API Gateway resource ${resourceArn}`
22616
+ );
22617
+ }
22618
+ if (Object.keys(tagsToAdd).length > 0) {
22619
+ await this.apiGatewayClient.send(new TagResourceCommand10({ resourceArn, tags: tagsToAdd }));
22620
+ this.logger.debug(
22621
+ `Added/updated ${Object.keys(tagsToAdd).length} tag(s) on API Gateway resource ${resourceArn}`
22622
+ );
22623
+ }
22624
+ }
22145
22625
  /**
22146
22626
  * Sleep for specified milliseconds
22147
22627
  */
@@ -24175,6 +24655,8 @@ import {
24175
24655
  DescribeStateMachineCommand,
24176
24656
  ListStateMachinesCommand,
24177
24657
  ListTagsForResourceCommand as ListTagsForResourceCommand8,
24658
+ TagResourceCommand as TagResourceCommand11,
24659
+ UntagResourceCommand as UntagResourceCommand11,
24178
24660
  StateMachineDoesNotExist
24179
24661
  } from "@aws-sdk/client-sfn";
24180
24662
  var StepFunctionsProvider = class {
@@ -24277,7 +24759,7 @@ var StepFunctionsProvider = class {
24277
24759
  /**
24278
24760
  * Update a Step Functions state machine
24279
24761
  */
24280
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
24762
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
24281
24763
  this.logger.debug(`Updating Step Functions state machine ${logicalId}: ${physicalId}`);
24282
24764
  try {
24283
24765
  const definitionString = this.buildDefinitionString(properties);
@@ -24301,6 +24783,11 @@ var StepFunctionsProvider = class {
24301
24783
  })
24302
24784
  );
24303
24785
  this.logger.debug(`Updated Step Functions state machine ${physicalId}`);
24786
+ await this.applyTagDiff(
24787
+ physicalId,
24788
+ previousProperties["Tags"],
24789
+ properties["Tags"]
24790
+ );
24304
24791
  const describeResponse = await this.getClient().send(
24305
24792
  new DescribeStateMachineCommand({ stateMachineArn: physicalId })
24306
24793
  );
@@ -24502,6 +24989,49 @@ var StepFunctionsProvider = class {
24502
24989
  } while (nextToken);
24503
24990
  return null;
24504
24991
  }
24992
+ /**
24993
+ * Apply a diff between old and new CFn-shape Tags arrays via SFN's
24994
+ * `TagResource` / `UntagResource` APIs. SFN uses lowercase camelCase
24995
+ * (`{ key, value }`) for tags.
24996
+ */
24997
+ async applyTagDiff(stateMachineArn, oldTagsRaw, newTagsRaw) {
24998
+ const toMap = (tags) => {
24999
+ const m = /* @__PURE__ */ new Map();
25000
+ for (const t of tags ?? []) {
25001
+ if (t.Key !== void 0 && t.Value !== void 0)
25002
+ m.set(t.Key, t.Value);
25003
+ }
25004
+ return m;
25005
+ };
25006
+ const oldMap = toMap(oldTagsRaw);
25007
+ const newMap = toMap(newTagsRaw);
25008
+ const tagsToAdd = [];
25009
+ for (const [k, v] of newMap) {
25010
+ if (oldMap.get(k) !== v)
25011
+ tagsToAdd.push({ key: k, value: v });
25012
+ }
25013
+ const tagsToRemove = [];
25014
+ for (const k of oldMap.keys()) {
25015
+ if (!newMap.has(k))
25016
+ tagsToRemove.push(k);
25017
+ }
25018
+ if (tagsToRemove.length > 0) {
25019
+ await this.getClient().send(
25020
+ new UntagResourceCommand11({ resourceArn: stateMachineArn, tagKeys: tagsToRemove })
25021
+ );
25022
+ this.logger.debug(
25023
+ `Removed ${tagsToRemove.length} tag(s) from SFN state machine ${stateMachineArn}`
25024
+ );
25025
+ }
25026
+ if (tagsToAdd.length > 0) {
25027
+ await this.getClient().send(
25028
+ new TagResourceCommand11({ resourceArn: stateMachineArn, tags: tagsToAdd })
25029
+ );
25030
+ this.logger.debug(
25031
+ `Added/updated ${tagsToAdd.length} tag(s) on SFN state machine ${stateMachineArn}`
25032
+ );
25033
+ }
25034
+ }
24505
25035
  /**
24506
25036
  * Match SFN's lowercase `key`/`value` tag shape against the CDK path.
24507
25037
  */
@@ -24553,7 +25083,9 @@ import {
24553
25083
  DescribeServicesCommand,
24554
25084
  ListClustersCommand,
24555
25085
  ListServicesCommand,
24556
- ListTagsForResourceCommand as ListTagsForResourceCommand9
25086
+ ListTagsForResourceCommand as ListTagsForResourceCommand9,
25087
+ TagResourceCommand as TagResourceCommand12,
25088
+ UntagResourceCommand as UntagResourceCommand12
24557
25089
  } from "@aws-sdk/client-ecs";
24558
25090
  function convertTags(tags) {
24559
25091
  if (!tags || tags.length === 0)
@@ -24648,7 +25180,13 @@ var ECSProvider = class {
24648
25180
  async update(logicalId, physicalId, resourceType, properties, previousProperties) {
24649
25181
  switch (resourceType) {
24650
25182
  case "AWS::ECS::Cluster":
24651
- return this.updateCluster(logicalId, physicalId, resourceType, properties);
25183
+ return this.updateCluster(
25184
+ logicalId,
25185
+ physicalId,
25186
+ resourceType,
25187
+ properties,
25188
+ previousProperties
25189
+ );
24652
25190
  case "AWS::ECS::TaskDefinition":
24653
25191
  return this.updateTaskDefinition(logicalId, physicalId, resourceType, properties);
24654
25192
  case "AWS::ECS::Service":
@@ -24740,7 +25278,7 @@ var ECSProvider = class {
24740
25278
  );
24741
25279
  }
24742
25280
  }
24743
- async updateCluster(logicalId, physicalId, resourceType, properties) {
25281
+ async updateCluster(logicalId, physicalId, resourceType, properties, previousProperties) {
24744
25282
  this.logger.debug(`Updating ECS cluster ${logicalId}: ${physicalId}`);
24745
25283
  const client = this.getClient();
24746
25284
  try {
@@ -24758,6 +25296,13 @@ var ECSProvider = class {
24758
25296
  new DescribeClustersCommand({ clusters: [physicalId] })
24759
25297
  );
24760
25298
  const cluster = describeResponse.clusters?.[0];
25299
+ if (cluster?.clusterArn) {
25300
+ await this.applyTagDiff(
25301
+ cluster.clusterArn,
25302
+ previousProperties["Tags"],
25303
+ properties["Tags"]
25304
+ );
25305
+ }
24761
25306
  return {
24762
25307
  physicalId,
24763
25308
  wasReplaced: false,
@@ -25026,6 +25571,13 @@ var ECSProvider = class {
25026
25571
  })
25027
25572
  );
25028
25573
  const service = response.service;
25574
+ if (service?.serviceArn) {
25575
+ await this.applyTagDiff(
25576
+ service.serviceArn,
25577
+ previousProperties["Tags"],
25578
+ properties["Tags"]
25579
+ );
25580
+ }
25029
25581
  return {
25030
25582
  physicalId,
25031
25583
  wasReplaced: false,
@@ -25127,6 +25679,42 @@ var ECSProvider = class {
25127
25679
  }
25128
25680
  }
25129
25681
  // ─── Helpers ────────────────────────────────────────────────────
25682
+ /**
25683
+ * Apply a diff between old and new CFn-shape Tags arrays via ECS's
25684
+ * `TagResource` / `UntagResource` APIs. ECS uses lowercase camelCase
25685
+ * (`{ key, value }`) for tags. Resource ARN identifies the cluster /
25686
+ * service / task definition.
25687
+ */
25688
+ async applyTagDiff(resourceArn, oldTagsRaw, newTagsRaw) {
25689
+ const toMap = (tags) => {
25690
+ const m = /* @__PURE__ */ new Map();
25691
+ for (const t of tags ?? []) {
25692
+ if (t.Key !== void 0 && t.Value !== void 0)
25693
+ m.set(t.Key, t.Value);
25694
+ }
25695
+ return m;
25696
+ };
25697
+ const oldMap = toMap(oldTagsRaw);
25698
+ const newMap = toMap(newTagsRaw);
25699
+ const tagsToAdd = [];
25700
+ for (const [k, v] of newMap) {
25701
+ if (oldMap.get(k) !== v)
25702
+ tagsToAdd.push({ key: k, value: v });
25703
+ }
25704
+ const tagsToRemove = [];
25705
+ for (const k of oldMap.keys()) {
25706
+ if (!newMap.has(k))
25707
+ tagsToRemove.push(k);
25708
+ }
25709
+ if (tagsToRemove.length > 0) {
25710
+ await this.getClient().send(new UntagResourceCommand12({ resourceArn, tagKeys: tagsToRemove }));
25711
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from ECS resource ${resourceArn}`);
25712
+ }
25713
+ if (tagsToAdd.length > 0) {
25714
+ await this.getClient().send(new TagResourceCommand12({ resourceArn, tags: tagsToAdd }));
25715
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on ECS resource ${resourceArn}`);
25716
+ }
25717
+ }
25130
25718
  /**
25131
25719
  * Convert CFn ContainerDefinitions to ECS SDK format.
25132
25720
  * CFn uses PascalCase, ECS SDK uses camelCase.
@@ -25574,6 +26162,8 @@ import {
25574
26162
  ModifyTargetGroupCommand,
25575
26163
  DescribeTargetGroupsCommand,
25576
26164
  DescribeTagsCommand,
26165
+ AddTagsCommand,
26166
+ RemoveTagsCommand,
25577
26167
  CreateListenerCommand,
25578
26168
  DeleteListenerCommand,
25579
26169
  ModifyListenerCommand,
@@ -25656,14 +26246,26 @@ var ELBv2Provider = class {
25656
26246
  );
25657
26247
  }
25658
26248
  }
25659
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
26249
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
25660
26250
  switch (resourceType) {
25661
26251
  case "AWS::ElasticLoadBalancingV2::LoadBalancer":
25662
26252
  return this.updateLoadBalancer(logicalId, physicalId, resourceType, properties);
25663
26253
  case "AWS::ElasticLoadBalancingV2::TargetGroup":
25664
- return this.updateTargetGroup(logicalId, physicalId, resourceType, properties);
26254
+ return this.updateTargetGroup(
26255
+ logicalId,
26256
+ physicalId,
26257
+ resourceType,
26258
+ properties,
26259
+ previousProperties
26260
+ );
25665
26261
  case "AWS::ElasticLoadBalancingV2::Listener":
25666
- return this.updateListener(logicalId, physicalId, resourceType, properties);
26262
+ return this.updateListener(
26263
+ logicalId,
26264
+ physicalId,
26265
+ resourceType,
26266
+ properties,
26267
+ previousProperties
26268
+ );
25667
26269
  default:
25668
26270
  throw new ProvisioningError(
25669
26271
  `Unsupported resource type: ${resourceType}`,
@@ -25842,7 +26444,7 @@ var ELBv2Provider = class {
25842
26444
  );
25843
26445
  }
25844
26446
  }
25845
- async updateTargetGroup(logicalId, physicalId, resourceType, properties) {
26447
+ async updateTargetGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
25846
26448
  this.logger.debug(`Updating TargetGroup ${logicalId}: ${physicalId}`);
25847
26449
  try {
25848
26450
  const matcher = properties["Matcher"];
@@ -25864,6 +26466,11 @@ var ELBv2Provider = class {
25864
26466
  new DescribeTargetGroupsCommand({ TargetGroupArns: [physicalId] })
25865
26467
  );
25866
26468
  const tg = describeResponse.TargetGroups?.[0];
26469
+ await this.applyTagDiff(
26470
+ physicalId,
26471
+ previousProperties["Tags"],
26472
+ properties["Tags"]
26473
+ );
25867
26474
  this.logger.debug(`Successfully updated TargetGroup ${logicalId}`);
25868
26475
  return {
25869
26476
  physicalId,
@@ -25957,7 +26564,7 @@ var ELBv2Provider = class {
25957
26564
  );
25958
26565
  }
25959
26566
  }
25960
- async updateListener(logicalId, physicalId, resourceType, properties) {
26567
+ async updateListener(logicalId, physicalId, resourceType, properties, previousProperties) {
25961
26568
  this.logger.debug(`Updating Listener ${logicalId}: ${physicalId}`);
25962
26569
  try {
25963
26570
  const defaultActions = this.convertActions(
@@ -25976,6 +26583,11 @@ var ELBv2Provider = class {
25976
26583
  ...certificates && { Certificates: certificates }
25977
26584
  })
25978
26585
  );
26586
+ await this.applyTagDiff(
26587
+ physicalId,
26588
+ previousProperties["Tags"],
26589
+ properties["Tags"]
26590
+ );
25979
26591
  this.logger.debug(`Successfully updated Listener ${logicalId}`);
25980
26592
  return {
25981
26593
  physicalId,
@@ -26033,6 +26645,44 @@ var ELBv2Provider = class {
26033
26645
  return [];
26034
26646
  return properties["Tags"];
26035
26647
  }
26648
+ /**
26649
+ * Apply a diff between old and new CFn-shape Tags arrays via ELBv2's
26650
+ * `AddTags` / `RemoveTags` APIs. Both accept `ResourceArns: [arn]`
26651
+ * (single ARN), `Tags: [{Key, Value}]` for AddTags, and
26652
+ * `TagKeys: [...]` for RemoveTags.
26653
+ */
26654
+ async applyTagDiff(arn, oldTagsRaw, newTagsRaw) {
26655
+ const toMap = (tags) => {
26656
+ const m = /* @__PURE__ */ new Map();
26657
+ for (const t of tags ?? []) {
26658
+ if (t.Key !== void 0 && t.Value !== void 0)
26659
+ m.set(t.Key, t.Value);
26660
+ }
26661
+ return m;
26662
+ };
26663
+ const oldMap = toMap(oldTagsRaw);
26664
+ const newMap = toMap(newTagsRaw);
26665
+ const tagsToAdd = [];
26666
+ for (const [k, v] of newMap) {
26667
+ if (oldMap.get(k) !== v)
26668
+ tagsToAdd.push({ Key: k, Value: v });
26669
+ }
26670
+ const tagsToRemove = [];
26671
+ for (const k of oldMap.keys()) {
26672
+ if (!newMap.has(k))
26673
+ tagsToRemove.push(k);
26674
+ }
26675
+ if (tagsToRemove.length > 0) {
26676
+ await this.getClient().send(
26677
+ new RemoveTagsCommand({ ResourceArns: [arn], TagKeys: tagsToRemove })
26678
+ );
26679
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from ELBv2 resource ${arn}`);
26680
+ }
26681
+ if (tagsToAdd.length > 0) {
26682
+ await this.getClient().send(new AddTagsCommand({ ResourceArns: [arn], Tags: tagsToAdd }));
26683
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on ELBv2 resource ${arn}`);
26684
+ }
26685
+ }
26036
26686
  /**
26037
26687
  * Convert CDK DefaultActions to ELBv2 API Action format
26038
26688
  * CDK uses PascalCase property names matching the ELBv2 API, so pass through.
@@ -26347,7 +26997,9 @@ import {
26347
26997
  DeleteDBSubnetGroupCommand,
26348
26998
  DescribeDBSubnetGroupsCommand,
26349
26999
  ModifyDBSubnetGroupCommand,
26350
- ListTagsForResourceCommand as ListTagsForResourceCommand10
27000
+ ListTagsForResourceCommand as ListTagsForResourceCommand10,
27001
+ AddTagsToResourceCommand as AddTagsToResourceCommand2,
27002
+ RemoveTagsFromResourceCommand as RemoveTagsFromResourceCommand2
26351
27003
  } from "@aws-sdk/client-rds";
26352
27004
  var RDSProvider = class {
26353
27005
  rdsClient;
@@ -26414,14 +27066,32 @@ var RDSProvider = class {
26414
27066
  );
26415
27067
  }
26416
27068
  }
26417
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
27069
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
26418
27070
  switch (resourceType) {
26419
27071
  case "AWS::RDS::DBSubnetGroup":
26420
- return this.updateDBSubnetGroup(logicalId, physicalId, resourceType, properties);
27072
+ return this.updateDBSubnetGroup(
27073
+ logicalId,
27074
+ physicalId,
27075
+ resourceType,
27076
+ properties,
27077
+ previousProperties
27078
+ );
26421
27079
  case "AWS::RDS::DBCluster":
26422
- return this.updateDBCluster(logicalId, physicalId, resourceType, properties);
27080
+ return this.updateDBCluster(
27081
+ logicalId,
27082
+ physicalId,
27083
+ resourceType,
27084
+ properties,
27085
+ previousProperties
27086
+ );
26423
27087
  case "AWS::RDS::DBInstance":
26424
- return this.updateDBInstance(logicalId, physicalId, resourceType, properties);
27088
+ return this.updateDBInstance(
27089
+ logicalId,
27090
+ physicalId,
27091
+ resourceType,
27092
+ properties,
27093
+ previousProperties
27094
+ );
26425
27095
  default:
26426
27096
  throw new ProvisioningError(
26427
27097
  `Unsupported resource type: ${resourceType}`,
@@ -26480,7 +27150,7 @@ var RDSProvider = class {
26480
27150
  );
26481
27151
  }
26482
27152
  }
26483
- async updateDBSubnetGroup(logicalId, physicalId, resourceType, properties) {
27153
+ async updateDBSubnetGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
26484
27154
  this.logger.debug(`Updating DBSubnetGroup ${logicalId}: ${physicalId}`);
26485
27155
  try {
26486
27156
  await this.getClient().send(
@@ -26490,6 +27160,17 @@ var RDSProvider = class {
26490
27160
  SubnetIds: properties["SubnetIds"]
26491
27161
  })
26492
27162
  );
27163
+ const desc = await this.getClient().send(
27164
+ new DescribeDBSubnetGroupsCommand({ DBSubnetGroupName: physicalId })
27165
+ );
27166
+ const arn = desc.DBSubnetGroups?.[0]?.DBSubnetGroupArn;
27167
+ if (arn) {
27168
+ await this.applyTagDiff(
27169
+ arn,
27170
+ previousProperties["Tags"],
27171
+ properties["Tags"]
27172
+ );
27173
+ }
26493
27174
  this.logger.debug(`Successfully updated DBSubnetGroup ${logicalId}`);
26494
27175
  return {
26495
27176
  physicalId,
@@ -26604,7 +27285,7 @@ var RDSProvider = class {
26604
27285
  );
26605
27286
  }
26606
27287
  }
26607
- async updateDBCluster(logicalId, physicalId, resourceType, properties) {
27288
+ async updateDBCluster(logicalId, physicalId, resourceType, properties, previousProperties) {
26608
27289
  this.logger.debug(`Updating DBCluster ${logicalId}: ${physicalId}`);
26609
27290
  try {
26610
27291
  const serverlessV2Config = properties["ServerlessV2ScalingConfiguration"];
@@ -26628,6 +27309,13 @@ var RDSProvider = class {
26628
27309
  );
26629
27310
  this.logger.debug(`Successfully updated DBCluster ${logicalId}`);
26630
27311
  const described = await this.describeDBCluster(physicalId);
27312
+ if (described?.DBClusterArn) {
27313
+ await this.applyTagDiff(
27314
+ described.DBClusterArn,
27315
+ previousProperties["Tags"],
27316
+ properties["Tags"]
27317
+ );
27318
+ }
26631
27319
  return {
26632
27320
  physicalId,
26633
27321
  wasReplaced: false,
@@ -26745,7 +27433,7 @@ var RDSProvider = class {
26745
27433
  );
26746
27434
  }
26747
27435
  }
26748
- async updateDBInstance(logicalId, physicalId, resourceType, properties) {
27436
+ async updateDBInstance(logicalId, physicalId, resourceType, properties, previousProperties) {
26749
27437
  this.logger.debug(`Updating DBInstance ${logicalId}: ${physicalId}`);
26750
27438
  try {
26751
27439
  await this.getClient().send(
@@ -26758,6 +27446,13 @@ var RDSProvider = class {
26758
27446
  );
26759
27447
  this.logger.debug(`Successfully updated DBInstance ${logicalId}`);
26760
27448
  const described = await this.describeDBInstance(physicalId);
27449
+ if (described?.DBInstanceArn) {
27450
+ await this.applyTagDiff(
27451
+ described.DBInstanceArn,
27452
+ previousProperties["Tags"],
27453
+ properties["Tags"]
27454
+ );
27455
+ }
26761
27456
  return {
26762
27457
  physicalId,
26763
27458
  wasReplaced: false,
@@ -26828,6 +27523,45 @@ var RDSProvider = class {
26828
27523
  }
26829
27524
  }
26830
27525
  // ─── Helpers ──────────────────────────────────────────────────────
27526
+ /**
27527
+ * Apply a diff between old and new CFn-shape Tags arrays via RDS's
27528
+ * `AddTagsToResource` / `RemoveTagsFromResource` APIs (keyed by
27529
+ * `ResourceName=arn`).
27530
+ */
27531
+ async applyTagDiff(arn, oldTagsRaw, newTagsRaw) {
27532
+ const toMap = (tags) => {
27533
+ const m = /* @__PURE__ */ new Map();
27534
+ for (const t of tags ?? []) {
27535
+ if (t.Key !== void 0 && t.Value !== void 0)
27536
+ m.set(t.Key, t.Value);
27537
+ }
27538
+ return m;
27539
+ };
27540
+ const oldMap = toMap(oldTagsRaw);
27541
+ const newMap = toMap(newTagsRaw);
27542
+ const tagsToAdd = [];
27543
+ for (const [k, v] of newMap) {
27544
+ if (oldMap.get(k) !== v)
27545
+ tagsToAdd.push({ Key: k, Value: v });
27546
+ }
27547
+ const tagsToRemove = [];
27548
+ for (const k of oldMap.keys()) {
27549
+ if (!newMap.has(k))
27550
+ tagsToRemove.push(k);
27551
+ }
27552
+ if (tagsToRemove.length > 0) {
27553
+ await this.getClient().send(
27554
+ new RemoveTagsFromResourceCommand2({ ResourceName: arn, TagKeys: tagsToRemove })
27555
+ );
27556
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from RDS resource ${arn}`);
27557
+ }
27558
+ if (tagsToAdd.length > 0) {
27559
+ await this.getClient().send(
27560
+ new AddTagsToResourceCommand2({ ResourceName: arn, Tags: tagsToAdd })
27561
+ );
27562
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on RDS resource ${arn}`);
27563
+ }
27564
+ }
26831
27565
  buildTags(properties) {
26832
27566
  if (!properties["Tags"])
26833
27567
  return [];
@@ -28115,6 +28849,8 @@ import {
28115
28849
  GetWebACLCommand,
28116
28850
  ListWebACLsCommand,
28117
28851
  ListTagsForResourceCommand as ListTagsForResourceCommand12,
28852
+ TagResourceCommand as TagResourceCommand13,
28853
+ UntagResourceCommand as UntagResourceCommand13,
28118
28854
  WAFNonexistentItemException
28119
28855
  } from "@aws-sdk/client-wafv2";
28120
28856
  function parseWebACLArn(arn) {
@@ -28221,7 +28957,7 @@ var WAFv2WebACLProvider = class {
28221
28957
  * Name and Scope are immutable - changes to those require replacement.
28222
28958
  * UpdateWebACL requires LockToken obtained from GetWebACL.
28223
28959
  */
28224
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
28960
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
28225
28961
  this.logger.debug(`Updating WAFv2 WebACL ${logicalId}: ${physicalId}`);
28226
28962
  try {
28227
28963
  const { id, name, scope } = parseWebACLArn(physicalId);
@@ -28253,6 +28989,11 @@ var WAFv2WebACLProvider = class {
28253
28989
  AssociationConfig: properties["AssociationConfig"]
28254
28990
  })
28255
28991
  );
28992
+ await this.applyTagDiff(
28993
+ physicalId,
28994
+ previousProperties["Tags"],
28995
+ properties["Tags"]
28996
+ );
28256
28997
  this.logger.debug(`Successfully updated WAFv2 WebACL ${logicalId}`);
28257
28998
  return {
28258
28999
  physicalId,
@@ -28329,6 +29070,42 @@ var WAFv2WebACLProvider = class {
28329
29070
  );
28330
29071
  }
28331
29072
  }
29073
+ /**
29074
+ * Apply a diff between old and new CFn-shape Tags arrays via WAFv2's
29075
+ * `TagResource` / `UntagResource` APIs (keyed by `ResourceARN`).
29076
+ */
29077
+ async applyTagDiff(arn, oldTagsRaw, newTagsRaw) {
29078
+ const toMap = (tags) => {
29079
+ const m = /* @__PURE__ */ new Map();
29080
+ for (const t of tags ?? []) {
29081
+ if (t.Key !== void 0 && t.Value !== void 0)
29082
+ m.set(t.Key, t.Value);
29083
+ }
29084
+ return m;
29085
+ };
29086
+ const oldMap = toMap(oldTagsRaw);
29087
+ const newMap = toMap(newTagsRaw);
29088
+ const tagsToAdd = [];
29089
+ for (const [k, v] of newMap) {
29090
+ if (oldMap.get(k) !== v)
29091
+ tagsToAdd.push({ Key: k, Value: v });
29092
+ }
29093
+ const tagsToRemove = [];
29094
+ for (const k of oldMap.keys()) {
29095
+ if (!newMap.has(k))
29096
+ tagsToRemove.push(k);
29097
+ }
29098
+ if (tagsToRemove.length > 0) {
29099
+ await this.getClient().send(
29100
+ new UntagResourceCommand13({ ResourceARN: arn, TagKeys: tagsToRemove })
29101
+ );
29102
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from WAFv2 WebACL ${arn}`);
29103
+ }
29104
+ if (tagsToAdd.length > 0) {
29105
+ await this.getClient().send(new TagResourceCommand13({ ResourceARN: arn, Tags: tagsToAdd }));
29106
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on WAFv2 WebACL ${arn}`);
29107
+ }
29108
+ }
28332
29109
  /**
28333
29110
  * Read the AWS-current WAFv2 WebACL configuration in CFn-property shape.
28334
29111
  *
@@ -28943,7 +29720,9 @@ import {
28943
29720
  DeleteCacheSubnetGroupCommand,
28944
29721
  ModifyCacheSubnetGroupCommand,
28945
29722
  ModifyCacheClusterCommand,
28946
- ListTagsForResourceCommand as ListTagsForResourceCommand14
29723
+ ListTagsForResourceCommand as ListTagsForResourceCommand14,
29724
+ AddTagsToResourceCommand as AddTagsToResourceCommand3,
29725
+ RemoveTagsFromResourceCommand as RemoveTagsFromResourceCommand3
28947
29726
  } from "@aws-sdk/client-elasticache";
28948
29727
  import { STSClient as STSClient6, GetCallerIdentityCommand as GetCallerIdentityCommand6 } from "@aws-sdk/client-sts";
28949
29728
  var ElastiCacheProvider = class {
@@ -29009,12 +29788,18 @@ var ElastiCacheProvider = class {
29009
29788
  );
29010
29789
  }
29011
29790
  }
29012
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
29791
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
29013
29792
  switch (resourceType) {
29014
29793
  case "AWS::ElastiCache::SubnetGroup":
29015
29794
  return this.updateSubnetGroup(logicalId, physicalId, resourceType, properties);
29016
29795
  case "AWS::ElastiCache::CacheCluster":
29017
- return this.updateCacheCluster(logicalId, physicalId, resourceType, properties);
29796
+ return this.updateCacheCluster(
29797
+ logicalId,
29798
+ physicalId,
29799
+ resourceType,
29800
+ properties,
29801
+ previousProperties
29802
+ );
29018
29803
  default:
29019
29804
  throw new ProvisioningError(
29020
29805
  `Unsupported resource type: ${resourceType}`,
@@ -29199,7 +29984,7 @@ var ElastiCacheProvider = class {
29199
29984
  );
29200
29985
  }
29201
29986
  }
29202
- async updateCacheCluster(logicalId, physicalId, resourceType, properties) {
29987
+ async updateCacheCluster(logicalId, physicalId, resourceType, properties, previousProperties) {
29203
29988
  this.logger.debug(`Updating CacheCluster ${logicalId}: ${physicalId}`);
29204
29989
  try {
29205
29990
  await this.getClient().send(
@@ -29222,6 +30007,13 @@ var ElastiCacheProvider = class {
29222
30007
  this.logger.debug(`Successfully updated CacheCluster ${logicalId}`);
29223
30008
  await this.waitForClusterAvailable(physicalId);
29224
30009
  const described = await this.describeCacheCluster(physicalId);
30010
+ if (described?.ARN) {
30011
+ await this.applyTagDiff(
30012
+ described.ARN,
30013
+ previousProperties["Tags"],
30014
+ properties["Tags"]
30015
+ );
30016
+ }
29225
30017
  const attributes = {};
29226
30018
  if (described?.CacheNodes?.[0]?.Endpoint) {
29227
30019
  const endpoint = described.CacheNodes[0].Endpoint;
@@ -29286,6 +30078,45 @@ var ElastiCacheProvider = class {
29286
30078
  }
29287
30079
  }
29288
30080
  // ─── Helpers ──────────────────────────────────────────────────────
30081
+ /**
30082
+ * Apply a diff between old and new CFn-shape Tags arrays via ElastiCache's
30083
+ * `AddTagsToResource` / `RemoveTagsFromResource` APIs (keyed by
30084
+ * `ResourceName=arn`).
30085
+ */
30086
+ async applyTagDiff(arn, oldTagsRaw, newTagsRaw) {
30087
+ const toMap = (tags) => {
30088
+ const m = /* @__PURE__ */ new Map();
30089
+ for (const t of tags ?? []) {
30090
+ if (t.Key !== void 0 && t.Value !== void 0)
30091
+ m.set(t.Key, t.Value);
30092
+ }
30093
+ return m;
30094
+ };
30095
+ const oldMap = toMap(oldTagsRaw);
30096
+ const newMap = toMap(newTagsRaw);
30097
+ const tagsToAdd = [];
30098
+ for (const [k, v] of newMap) {
30099
+ if (oldMap.get(k) !== v)
30100
+ tagsToAdd.push({ Key: k, Value: v });
30101
+ }
30102
+ const tagsToRemove = [];
30103
+ for (const k of oldMap.keys()) {
30104
+ if (!newMap.has(k))
30105
+ tagsToRemove.push(k);
30106
+ }
30107
+ if (tagsToRemove.length > 0) {
30108
+ await this.getClient().send(
30109
+ new RemoveTagsFromResourceCommand3({ ResourceName: arn, TagKeys: tagsToRemove })
30110
+ );
30111
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from ElastiCache resource ${arn}`);
30112
+ }
30113
+ if (tagsToAdd.length > 0) {
30114
+ await this.getClient().send(
30115
+ new AddTagsToResourceCommand3({ ResourceName: arn, Tags: tagsToAdd })
30116
+ );
30117
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on ElastiCache resource ${arn}`);
30118
+ }
30119
+ }
29289
30120
  buildTags(properties) {
29290
30121
  if (!properties["Tags"])
29291
30122
  return [];
@@ -31613,8 +32444,8 @@ import {
31613
32444
  PutKeyPolicyCommand,
31614
32445
  EnableKeyCommand,
31615
32446
  DisableKeyCommand,
31616
- TagResourceCommand as TagResourceCommand6,
31617
- UntagResourceCommand as UntagResourceCommand6,
32447
+ TagResourceCommand as TagResourceCommand14,
32448
+ UntagResourceCommand as UntagResourceCommand14,
31618
32449
  NotFoundException as NotFoundException5
31619
32450
  } from "@aws-sdk/client-kms";
31620
32451
  var KMSProvider = class {
@@ -31799,26 +32630,11 @@ var KMSProvider = class {
31799
32630
  await this.getClient().send(new EnableKeyCommand({ KeyId: physicalId }));
31800
32631
  }
31801
32632
  }
31802
- const newTags = properties["Tags"];
31803
- const oldTags = previousProperties["Tags"];
31804
- if (JSON.stringify(newTags) !== JSON.stringify(oldTags)) {
31805
- if (oldTags && oldTags.length > 0) {
31806
- await this.getClient().send(
31807
- new UntagResourceCommand6({
31808
- KeyId: physicalId,
31809
- TagKeys: oldTags.map((t) => t.Key)
31810
- })
31811
- );
31812
- }
31813
- if (newTags && newTags.length > 0) {
31814
- await this.getClient().send(
31815
- new TagResourceCommand6({
31816
- KeyId: physicalId,
31817
- Tags: newTags.map((t) => ({ TagKey: t.Key, TagValue: t.Value }))
31818
- })
31819
- );
31820
- }
31821
- }
32633
+ await this.applyTagDiff(
32634
+ physicalId,
32635
+ previousProperties["Tags"],
32636
+ properties["Tags"]
32637
+ );
31822
32638
  const newKeyPolicy = properties["KeyPolicy"];
31823
32639
  const oldKeyPolicy = previousProperties["KeyPolicy"];
31824
32640
  const newPolicyStr = newKeyPolicy ? typeof newKeyPolicy === "string" ? newKeyPolicy : JSON.stringify(newKeyPolicy) : void 0;
@@ -31880,6 +32696,43 @@ var KMSProvider = class {
31880
32696
  );
31881
32697
  }
31882
32698
  }
32699
+ /**
32700
+ * Apply a diff between old and new CFn-shape Tags arrays via KMS's
32701
+ * `TagResource` / `UntagResource` APIs. KMS uses `{TagKey, TagValue}`
32702
+ * (NOT the standard `{Key, Value}` shape) keyed by `KeyId`.
32703
+ */
32704
+ async applyTagDiff(keyId, oldTagsRaw, newTagsRaw) {
32705
+ const toMap = (tags) => {
32706
+ const m = /* @__PURE__ */ new Map();
32707
+ for (const t of tags ?? []) {
32708
+ if (t.Key !== void 0 && t.Value !== void 0)
32709
+ m.set(t.Key, t.Value);
32710
+ }
32711
+ return m;
32712
+ };
32713
+ const oldMap = toMap(oldTagsRaw);
32714
+ const newMap = toMap(newTagsRaw);
32715
+ const tagsToAdd = [];
32716
+ for (const [k, v] of newMap) {
32717
+ if (oldMap.get(k) !== v)
32718
+ tagsToAdd.push({ TagKey: k, TagValue: v });
32719
+ }
32720
+ const tagsToRemove = [];
32721
+ for (const k of oldMap.keys()) {
32722
+ if (!newMap.has(k))
32723
+ tagsToRemove.push(k);
32724
+ }
32725
+ if (tagsToRemove.length > 0) {
32726
+ await this.getClient().send(
32727
+ new UntagResourceCommand14({ KeyId: keyId, TagKeys: tagsToRemove })
32728
+ );
32729
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from KMS Key ${keyId}`);
32730
+ }
32731
+ if (tagsToAdd.length > 0) {
32732
+ await this.getClient().send(new TagResourceCommand14({ KeyId: keyId, Tags: tagsToAdd }));
32733
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on KMS Key ${keyId}`);
32734
+ }
32735
+ }
31883
32736
  // ─── AWS::KMS::Alias ───────────────────────────────────────────────
31884
32737
  async createAlias(logicalId, resourceType, properties) {
31885
32738
  this.logger.debug(`Creating KMS Alias ${logicalId}`);
@@ -32164,6 +33017,7 @@ import {
32164
33017
  DescribeStreamCommand,
32165
33018
  UpdateShardCountCommand,
32166
33019
  AddTagsToStreamCommand,
33020
+ RemoveTagsFromStreamCommand,
32167
33021
  IncreaseStreamRetentionPeriodCommand,
32168
33022
  DecreaseStreamRetentionPeriodCommand,
32169
33023
  StartStreamEncryptionCommand,
@@ -32341,6 +33195,11 @@ var KinesisStreamProvider = class {
32341
33195
  }
32342
33196
  await this.waitForStreamActive(physicalId);
32343
33197
  }
33198
+ await this.applyTagDiff(
33199
+ physicalId,
33200
+ previousProperties["Tags"],
33201
+ properties["Tags"]
33202
+ );
32344
33203
  const newEncryption = properties["StreamEncryption"];
32345
33204
  const oldEncryption = previousProperties["StreamEncryption"];
32346
33205
  if (JSON.stringify(newEncryption) !== JSON.stringify(oldEncryption)) {
@@ -32422,6 +33281,47 @@ var KinesisStreamProvider = class {
32422
33281
  );
32423
33282
  }
32424
33283
  }
33284
+ /**
33285
+ * Apply a diff between old and new CFn-shape Tags arrays via Kinesis's
33286
+ * `AddTagsToStream` (map shape) / `RemoveTagsFromStream` (TagKeys list)
33287
+ * APIs.
33288
+ */
33289
+ async applyTagDiff(streamName, oldTagsRaw, newTagsRaw) {
33290
+ const toMap = (tags) => {
33291
+ const m = /* @__PURE__ */ new Map();
33292
+ for (const t of tags ?? []) {
33293
+ if (t.Key !== void 0 && t.Value !== void 0)
33294
+ m.set(t.Key, t.Value);
33295
+ }
33296
+ return m;
33297
+ };
33298
+ const oldMap = toMap(oldTagsRaw);
33299
+ const newMap = toMap(newTagsRaw);
33300
+ const tagsToAdd = {};
33301
+ for (const [k, v] of newMap) {
33302
+ if (oldMap.get(k) !== v)
33303
+ tagsToAdd[k] = v;
33304
+ }
33305
+ const tagsToRemove = [];
33306
+ for (const k of oldMap.keys()) {
33307
+ if (!newMap.has(k))
33308
+ tagsToRemove.push(k);
33309
+ }
33310
+ if (tagsToRemove.length > 0) {
33311
+ await this.getClient().send(
33312
+ new RemoveTagsFromStreamCommand({ StreamName: streamName, TagKeys: tagsToRemove })
33313
+ );
33314
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from Kinesis stream ${streamName}`);
33315
+ }
33316
+ if (Object.keys(tagsToAdd).length > 0) {
33317
+ await this.getClient().send(
33318
+ new AddTagsToStreamCommand({ StreamName: streamName, Tags: tagsToAdd })
33319
+ );
33320
+ this.logger.debug(
33321
+ `Added/updated ${Object.keys(tagsToAdd).length} tag(s) on Kinesis stream ${streamName}`
33322
+ );
33323
+ }
33324
+ }
32425
33325
  /**
32426
33326
  * Adopt an existing Kinesis stream into cdkd state.
32427
33327
  *
@@ -33782,6 +34682,8 @@ import {
33782
34682
  GetEventSelectorsCommand,
33783
34683
  ListTrailsCommand,
33784
34684
  ListTagsCommand as ListTagsCommand3,
34685
+ AddTagsCommand as AddTagsCommand2,
34686
+ RemoveTagsCommand as RemoveTagsCommand2,
33785
34687
  TrailNotFoundException
33786
34688
  } from "@aws-sdk/client-cloudtrail";
33787
34689
  var CloudTrailProvider = class {
@@ -33965,6 +34867,11 @@ var CloudTrailProvider = class {
33965
34867
  await this.getClient().send(new StartLoggingCommand({ Name: physicalId }));
33966
34868
  }
33967
34869
  }
34870
+ await this.applyTagDiff(
34871
+ physicalId,
34872
+ previousProperties["Tags"],
34873
+ properties["Tags"]
34874
+ );
33968
34875
  this.logger.debug(`Successfully updated CloudTrail Trail ${logicalId}`);
33969
34876
  return { physicalId, wasReplaced: false };
33970
34877
  } catch (error) {
@@ -34013,6 +34920,46 @@ var CloudTrailProvider = class {
34013
34920
  getAttribute(_physicalId, _resourceType, attributeName) {
34014
34921
  return Promise.resolve(attributeName);
34015
34922
  }
34923
+ /**
34924
+ * Apply a diff between old and new CFn-shape Tags arrays via CloudTrail's
34925
+ * `AddTags` / `RemoveTags` APIs. Note: CloudTrail's `RemoveTags` takes
34926
+ * full `{Key, Value}` objects in `TagsList` (NOT just keys), unlike most
34927
+ * other AWS services.
34928
+ */
34929
+ async applyTagDiff(trailArn, oldTagsRaw, newTagsRaw) {
34930
+ const toMap = (tags) => {
34931
+ const m = /* @__PURE__ */ new Map();
34932
+ for (const t of tags ?? []) {
34933
+ if (t.Key !== void 0 && t.Value !== void 0)
34934
+ m.set(t.Key, t.Value);
34935
+ }
34936
+ return m;
34937
+ };
34938
+ const oldMap = toMap(oldTagsRaw);
34939
+ const newMap = toMap(newTagsRaw);
34940
+ const tagsToAdd = [];
34941
+ for (const [k, v] of newMap) {
34942
+ if (oldMap.get(k) !== v)
34943
+ tagsToAdd.push({ Key: k, Value: v });
34944
+ }
34945
+ const tagsToRemove = [];
34946
+ for (const [k, v] of oldMap) {
34947
+ if (!newMap.has(k))
34948
+ tagsToRemove.push({ Key: k, Value: v });
34949
+ }
34950
+ if (tagsToRemove.length > 0) {
34951
+ await this.getClient().send(
34952
+ new RemoveTagsCommand2({ ResourceId: trailArn, TagsList: tagsToRemove })
34953
+ );
34954
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from CloudTrail Trail ${trailArn}`);
34955
+ }
34956
+ if (tagsToAdd.length > 0) {
34957
+ await this.getClient().send(
34958
+ new AddTagsCommand2({ ResourceId: trailArn, TagsList: tagsToAdd })
34959
+ );
34960
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on CloudTrail Trail ${trailArn}`);
34961
+ }
34962
+ }
34016
34963
  /**
34017
34964
  * Adopt an existing CloudTrail trail into cdkd state.
34018
34965
  *
@@ -35836,7 +36783,7 @@ import {
35836
36783
  SetRepositoryPolicyCommand,
35837
36784
  PutImageScanningConfigurationCommand,
35838
36785
  PutImageTagMutabilityCommand,
35839
- TagResourceCommand as TagResourceCommand7,
36786
+ TagResourceCommand as TagResourceCommand15,
35840
36787
  ListTagsForResourceCommand as ListTagsForResourceCommand18,
35841
36788
  LifecyclePolicyNotFoundException,
35842
36789
  RepositoryNotFoundException
@@ -36002,7 +36949,7 @@ var ECRProvider = class {
36002
36949
  const repoArn = describeResponse.repositories?.[0]?.repositoryArn;
36003
36950
  if (repoArn && newTags) {
36004
36951
  await this.getClient().send(
36005
- new TagResourceCommand7({
36952
+ new TagResourceCommand15({
36006
36953
  resourceArn: repoArn,
36007
36954
  tags: newTags
36008
36955
  })
@@ -38885,6 +39832,19 @@ async function runAccept(reports, stateBackend, stateConfig, awsClients, options
38885
39832
  }
38886
39833
  }
38887
39834
  }
39835
+ function buildRevertNewProperties(drifts, desiredProperties, awsProperties) {
39836
+ const result = { ...awsProperties };
39837
+ for (const d of drifts) {
39838
+ const topLevelKey = d.path.split(".", 1)[0];
39839
+ if (!topLevelKey)
39840
+ continue;
39841
+ if (topLevelKey in desiredProperties) {
39842
+ result[topLevelKey] = desiredProperties[topLevelKey];
39843
+ } else {
39844
+ }
39845
+ }
39846
+ return result;
39847
+ }
38888
39848
  async function runRevert(reports, providerRegistry, stateConfig, awsClients, options) {
38889
39849
  const logger = getLogger();
38890
39850
  printRevertPlan(reports);
@@ -38927,13 +39887,18 @@ async function runRevert(reports, providerRegistry, stateConfig, awsClients, opt
38927
39887
  }
38928
39888
  const provider = providerRegistry.getProvider(outcome.resourceType);
38929
39889
  const desiredProperties = stateResource.observedProperties ?? stateResource.properties ?? {};
39890
+ const newProperties = buildRevertNewProperties(
39891
+ outcome.changes,
39892
+ desiredProperties,
39893
+ outcome.awsProperties
39894
+ );
38930
39895
  try {
38931
39896
  await withRetry(
38932
39897
  () => provider.update(
38933
39898
  outcome.logicalId,
38934
39899
  stateResource.physicalId,
38935
39900
  outcome.resourceType,
38936
- desiredProperties,
39901
+ newProperties,
38937
39902
  outcome.awsProperties
38938
39903
  ),
38939
39904
  outcome.logicalId,
@@ -42566,7 +43531,7 @@ function reorderArgs(argv) {
42566
43531
  }
42567
43532
  async function main() {
42568
43533
  const program = new Command14();
42569
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.50.5");
43534
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.50.7");
42570
43535
  program.addCommand(createBootstrapCommand());
42571
43536
  program.addCommand(createSynthCommand());
42572
43537
  program.addCommand(createListCommand());