@go-to-k/cdkd 0.50.5 → 0.50.6

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
@@ -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,6 +12635,8 @@ 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";
@@ -12656,7 +12747,7 @@ var SQSQueueProvider = class {
12656
12747
  /**
12657
12748
  * Update an SQS queue
12658
12749
  */
12659
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
12750
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
12660
12751
  this.logger.debug(`Updating SQS queue ${logicalId}: ${physicalId}`);
12661
12752
  try {
12662
12753
  const attributes = {};
@@ -12681,6 +12772,11 @@ var SQSQueueProvider = class {
12681
12772
  );
12682
12773
  this.logger.debug(`Updated attributes for SQS queue ${physicalId}`);
12683
12774
  }
12775
+ await this.applyTagDiff(
12776
+ physicalId,
12777
+ previousProperties["Tags"],
12778
+ properties["Tags"]
12779
+ );
12684
12780
  const getResponse = await this.sqsClient.send(
12685
12781
  new GetQueueAttributesCommand({
12686
12782
  QueueUrl: physicalId,
@@ -12739,6 +12835,46 @@ var SQSQueueProvider = class {
12739
12835
  );
12740
12836
  }
12741
12837
  }
12838
+ /**
12839
+ * Apply a diff between old and new CFn-shape Tags arrays via SQS's
12840
+ * `TagQueue` / `UntagQueue` APIs. SQS's `TagQueue` takes a `Tags` map
12841
+ * (`{ key: value }`); `UntagQueue` takes a `TagKeys` array. cdkd state
12842
+ * holds Tags in CFn shape (`[{ Key, Value }]`).
12843
+ */
12844
+ async applyTagDiff(queueUrl, oldTagsRaw, newTagsRaw) {
12845
+ const toMap = (tags) => {
12846
+ const m = /* @__PURE__ */ new Map();
12847
+ for (const t of tags ?? []) {
12848
+ if (t.Key !== void 0 && t.Value !== void 0)
12849
+ m.set(t.Key, t.Value);
12850
+ }
12851
+ return m;
12852
+ };
12853
+ const oldMap = toMap(oldTagsRaw);
12854
+ const newMap = toMap(newTagsRaw);
12855
+ const tagsToAdd = {};
12856
+ for (const [k, v] of newMap) {
12857
+ if (oldMap.get(k) !== v)
12858
+ tagsToAdd[k] = v;
12859
+ }
12860
+ const tagsToRemove = [];
12861
+ for (const k of oldMap.keys()) {
12862
+ if (!newMap.has(k))
12863
+ tagsToRemove.push(k);
12864
+ }
12865
+ if (tagsToRemove.length > 0) {
12866
+ await this.sqsClient.send(
12867
+ new UntagQueueCommand({ QueueUrl: queueUrl, TagKeys: tagsToRemove })
12868
+ );
12869
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from SQS queue ${queueUrl}`);
12870
+ }
12871
+ if (Object.keys(tagsToAdd).length > 0) {
12872
+ await this.sqsClient.send(new TagQueueCommand({ QueueUrl: queueUrl, Tags: tagsToAdd }));
12873
+ this.logger.debug(
12874
+ `Added/updated ${Object.keys(tagsToAdd).length} tag(s) on SQS queue ${queueUrl}`
12875
+ );
12876
+ }
12877
+ }
12742
12878
  /**
12743
12879
  * Construct SQS queue ARN from account/region/queue name
12744
12880
  */
@@ -14080,6 +14216,8 @@ import {
14080
14216
  GetFunctionCommand,
14081
14217
  ListFunctionsCommand,
14082
14218
  ListTagsCommand,
14219
+ TagResourceCommand as TagResourceCommand2,
14220
+ UntagResourceCommand as UntagResourceCommand2,
14083
14221
  ResourceNotFoundException,
14084
14222
  waitUntilFunctionUpdatedV2 as waitUntilFunctionUpdatedV22
14085
14223
  } from "@aws-sdk/client-lambda";
@@ -14297,11 +14435,17 @@ var LambdaFunctionProvider = class {
14297
14435
  const getResponse = await this.lambdaClient.send(
14298
14436
  new GetFunctionCommand({ FunctionName: physicalId })
14299
14437
  );
14438
+ const functionArn = getResponse.Configuration?.FunctionArn;
14439
+ await this.applyTagDiff(
14440
+ functionArn,
14441
+ previousProperties["Tags"],
14442
+ properties["Tags"]
14443
+ );
14300
14444
  return {
14301
14445
  physicalId,
14302
14446
  wasReplaced: false,
14303
14447
  attributes: {
14304
- Arn: getResponse.Configuration?.FunctionArn,
14448
+ Arn: functionArn,
14305
14449
  FunctionName: getResponse.Configuration?.FunctionName
14306
14450
  }
14307
14451
  };
@@ -14493,6 +14637,53 @@ var LambdaFunctionProvider = class {
14493
14637
  * one resource type that actually needs it preserves the bug fix
14494
14638
  * without paying the whole-stack tax.
14495
14639
  */
14640
+ /**
14641
+ * Apply a diff between old and new CFn-shape Tags arrays via Lambda's
14642
+ * `TagResource` / `UntagResource` APIs. Without this, `cdkd deploy`
14643
+ * and `cdkd drift --revert` silently no-op tag changes — the
14644
+ * `UpdateFunctionConfiguration` command does NOT accept a Tags
14645
+ * parameter (Lambda treats tags as a separate API surface).
14646
+ */
14647
+ async applyTagDiff(functionArn, oldTagsRaw, newTagsRaw) {
14648
+ if (!functionArn)
14649
+ return;
14650
+ const toMap = (tags) => {
14651
+ const m = /* @__PURE__ */ new Map();
14652
+ for (const t of tags ?? []) {
14653
+ if (t.Key !== void 0 && t.Value !== void 0)
14654
+ m.set(t.Key, t.Value);
14655
+ }
14656
+ return m;
14657
+ };
14658
+ const oldMap = toMap(oldTagsRaw);
14659
+ const newMap = toMap(newTagsRaw);
14660
+ const tagsToAdd = {};
14661
+ for (const [k, v] of newMap) {
14662
+ if (oldMap.get(k) !== v)
14663
+ tagsToAdd[k] = v;
14664
+ }
14665
+ const tagsToRemove = [];
14666
+ for (const k of oldMap.keys()) {
14667
+ if (!newMap.has(k))
14668
+ tagsToRemove.push(k);
14669
+ }
14670
+ if (tagsToRemove.length > 0) {
14671
+ await this.lambdaClient.send(
14672
+ new UntagResourceCommand2({ Resource: functionArn, TagKeys: tagsToRemove })
14673
+ );
14674
+ this.logger.debug(
14675
+ `Removed ${tagsToRemove.length} tag(s) from Lambda function ${functionArn}`
14676
+ );
14677
+ }
14678
+ if (Object.keys(tagsToAdd).length > 0) {
14679
+ await this.lambdaClient.send(
14680
+ new TagResourceCommand2({ Resource: functionArn, Tags: tagsToAdd })
14681
+ );
14682
+ this.logger.debug(
14683
+ `Added/updated ${Object.keys(tagsToAdd).length} tag(s) on Lambda function ${functionArn}`
14684
+ );
14685
+ }
14686
+ }
14496
14687
  async waitForFunctionUpdated(logicalId, resourceType, functionName) {
14497
14688
  try {
14498
14689
  await waitUntilFunctionUpdatedV22(
@@ -14827,14 +15018,11 @@ var LambdaFunctionProvider = class {
14827
15018
  if (cfg.EphemeralStorage?.Size !== void 0) {
14828
15019
  result["EphemeralStorage"] = { Size: cfg.EphemeralStorage.Size };
14829
15020
  }
14830
- const vpc = {
15021
+ result["VpcConfig"] = {
14831
15022
  SubnetIds: cfg.VpcConfig?.SubnetIds ? [...cfg.VpcConfig.SubnetIds] : [],
14832
- SecurityGroupIds: cfg.VpcConfig?.SecurityGroupIds ? [...cfg.VpcConfig.SecurityGroupIds] : []
15023
+ SecurityGroupIds: cfg.VpcConfig?.SecurityGroupIds ? [...cfg.VpcConfig.SecurityGroupIds] : [],
15024
+ Ipv6AllowedForDualStack: cfg.VpcConfig?.Ipv6AllowedForDualStack ?? false
14833
15025
  };
14834
- if (cfg.VpcConfig?.Ipv6AllowedForDualStack !== void 0) {
14835
- vpc["Ipv6AllowedForDualStack"] = cfg.VpcConfig.Ipv6AllowedForDualStack;
14836
- }
14837
- result["VpcConfig"] = vpc;
14838
15026
  const tags = normalizeAwsTagsToCfn(resp.Tags);
14839
15027
  result["Tags"] = tags;
14840
15028
  return result;
@@ -15468,6 +15656,8 @@ import {
15468
15656
  DeleteEventSourceMappingCommand,
15469
15657
  UpdateEventSourceMappingCommand,
15470
15658
  GetEventSourceMappingCommand,
15659
+ TagResourceCommand as TagResourceCommand3,
15660
+ UntagResourceCommand as UntagResourceCommand3,
15471
15661
  ResourceNotFoundException as ResourceNotFoundException4
15472
15662
  } from "@aws-sdk/client-lambda";
15473
15663
  init_aws_clients();
@@ -15591,7 +15781,7 @@ var LambdaEventSourceMappingProvider = class {
15591
15781
  /**
15592
15782
  * Update a Lambda Event Source Mapping
15593
15783
  */
15594
- async update(logicalId, physicalId, _resourceType, properties, _previousProperties) {
15784
+ async update(logicalId, physicalId, _resourceType, properties, previousProperties) {
15595
15785
  this.logger.debug(`Updating event source mapping ${logicalId}: ${physicalId}`);
15596
15786
  const updateParams = {
15597
15787
  UUID: physicalId,
@@ -15625,7 +15815,17 @@ var LambdaEventSourceMappingProvider = class {
15625
15815
  updateParams.ScalingConfig = properties["ScalingConfig"];
15626
15816
  if (properties["DocumentDBEventSourceConfig"])
15627
15817
  updateParams.DocumentDBEventSourceConfig = properties["DocumentDBEventSourceConfig"];
15628
- await this.lambdaClient.send(new UpdateEventSourceMappingCommand(updateParams));
15818
+ const updateResp = await this.lambdaClient.send(
15819
+ new UpdateEventSourceMappingCommand(updateParams)
15820
+ );
15821
+ const eventSourceMappingArn = updateResp.EventSourceMappingArn;
15822
+ if (eventSourceMappingArn) {
15823
+ await this.applyTagDiff(
15824
+ eventSourceMappingArn,
15825
+ previousProperties["Tags"],
15826
+ properties["Tags"]
15827
+ );
15828
+ }
15629
15829
  this.logger.debug(`Successfully updated event source mapping ${logicalId}`);
15630
15830
  return {
15631
15831
  physicalId,
@@ -15635,6 +15835,46 @@ var LambdaEventSourceMappingProvider = class {
15635
15835
  }
15636
15836
  };
15637
15837
  }
15838
+ /**
15839
+ * Apply a diff between old and new CFn-shape Tags arrays via Lambda's
15840
+ * `TagResource` / `UntagResource` APIs against the EventSourceMapping
15841
+ * ARN. Lambda's `TagResource` takes `{ Resource, Tags: { key: value } }`;
15842
+ * `UntagResource` takes `{ Resource, TagKeys: [...] }`.
15843
+ */
15844
+ async applyTagDiff(arn, oldTagsRaw, newTagsRaw) {
15845
+ const toMap = (tags) => {
15846
+ const m = /* @__PURE__ */ new Map();
15847
+ for (const t of tags ?? []) {
15848
+ if (t.Key !== void 0 && t.Value !== void 0)
15849
+ m.set(t.Key, t.Value);
15850
+ }
15851
+ return m;
15852
+ };
15853
+ const oldMap = toMap(oldTagsRaw);
15854
+ const newMap = toMap(newTagsRaw);
15855
+ const tagsToAdd = {};
15856
+ for (const [k, v] of newMap) {
15857
+ if (oldMap.get(k) !== v)
15858
+ tagsToAdd[k] = v;
15859
+ }
15860
+ const tagsToRemove = [];
15861
+ for (const k of oldMap.keys()) {
15862
+ if (!newMap.has(k))
15863
+ tagsToRemove.push(k);
15864
+ }
15865
+ if (tagsToRemove.length > 0) {
15866
+ await this.lambdaClient.send(
15867
+ new UntagResourceCommand3({ Resource: arn, TagKeys: tagsToRemove })
15868
+ );
15869
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from EventSourceMapping ${arn}`);
15870
+ }
15871
+ if (Object.keys(tagsToAdd).length > 0) {
15872
+ await this.lambdaClient.send(new TagResourceCommand3({ Resource: arn, Tags: tagsToAdd }));
15873
+ this.logger.debug(
15874
+ `Added/updated ${Object.keys(tagsToAdd).length} tag(s) on EventSourceMapping ${arn}`
15875
+ );
15876
+ }
15877
+ }
15638
15878
  /**
15639
15879
  * Delete a Lambda Event Source Mapping
15640
15880
  */
@@ -16068,6 +16308,8 @@ import {
16068
16308
  DescribeTableCommand as DescribeTableCommand2,
16069
16309
  ListTablesCommand,
16070
16310
  ListTagsOfResourceCommand,
16311
+ TagResourceCommand as TagResourceCommand4,
16312
+ UntagResourceCommand as UntagResourceCommand4,
16071
16313
  ResourceNotFoundException as ResourceNotFoundException6
16072
16314
  } from "@aws-sdk/client-dynamodb";
16073
16315
  init_aws_clients();
@@ -16193,13 +16435,20 @@ var DynamoDBTableProvider = class {
16193
16435
  * For immutable property changes (KeySchema, etc.), the deployment layer
16194
16436
  * handles replacement via DELETE + CREATE.
16195
16437
  */
16196
- async update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
16438
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
16197
16439
  this.logger.debug(`Updating DynamoDB table ${logicalId}: ${physicalId}`);
16198
16440
  try {
16199
16441
  const response = await this.dynamoDBClient.send(
16200
16442
  new DescribeTableCommand2({ TableName: physicalId })
16201
16443
  );
16202
16444
  const table = response.Table;
16445
+ if (table?.TableArn) {
16446
+ await this.applyTagDiff(
16447
+ table.TableArn,
16448
+ previousProperties["Tags"],
16449
+ properties["Tags"]
16450
+ );
16451
+ }
16203
16452
  return {
16204
16453
  physicalId,
16205
16454
  wasReplaced: false,
@@ -16252,6 +16501,45 @@ var DynamoDBTableProvider = class {
16252
16501
  );
16253
16502
  }
16254
16503
  }
16504
+ /**
16505
+ * Apply a diff between old and new CFn-shape Tags arrays via DynamoDB's
16506
+ * `TagResource` / `UntagResource` APIs. Both take the table ARN as
16507
+ * `ResourceArn`.
16508
+ */
16509
+ async applyTagDiff(tableArn, oldTagsRaw, newTagsRaw) {
16510
+ const toMap = (tags) => {
16511
+ const m = /* @__PURE__ */ new Map();
16512
+ for (const t of tags ?? []) {
16513
+ if (t.Key !== void 0 && t.Value !== void 0)
16514
+ m.set(t.Key, t.Value);
16515
+ }
16516
+ return m;
16517
+ };
16518
+ const oldMap = toMap(oldTagsRaw);
16519
+ const newMap = toMap(newTagsRaw);
16520
+ const tagsToAdd = [];
16521
+ for (const [k, v] of newMap) {
16522
+ if (oldMap.get(k) !== v)
16523
+ tagsToAdd.push({ Key: k, Value: v });
16524
+ }
16525
+ const tagsToRemove = [];
16526
+ for (const k of oldMap.keys()) {
16527
+ if (!newMap.has(k))
16528
+ tagsToRemove.push(k);
16529
+ }
16530
+ if (tagsToRemove.length > 0) {
16531
+ await this.dynamoDBClient.send(
16532
+ new UntagResourceCommand4({ ResourceArn: tableArn, TagKeys: tagsToRemove })
16533
+ );
16534
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from DynamoDB table ${tableArn}`);
16535
+ }
16536
+ if (tagsToAdd.length > 0) {
16537
+ await this.dynamoDBClient.send(
16538
+ new TagResourceCommand4({ ResourceArn: tableArn, Tags: tagsToAdd })
16539
+ );
16540
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on DynamoDB table ${tableArn}`);
16541
+ }
16542
+ }
16255
16543
  /**
16256
16544
  * Poll DescribeTable until the table reaches ACTIVE status
16257
16545
  *
@@ -16471,8 +16759,8 @@ import {
16471
16759
  ListTagsForResourceCommand as ListTagsForResourceCommand2,
16472
16760
  PutRetentionPolicyCommand,
16473
16761
  DeleteRetentionPolicyCommand,
16474
- TagResourceCommand as TagResourceCommand2,
16475
- UntagResourceCommand as UntagResourceCommand2,
16762
+ TagResourceCommand as TagResourceCommand5,
16763
+ UntagResourceCommand as UntagResourceCommand5,
16476
16764
  PutDataProtectionPolicyCommand,
16477
16765
  DeleteDataProtectionPolicyCommand,
16478
16766
  ResourceNotFoundException as ResourceNotFoundException7,
@@ -16622,7 +16910,7 @@ var LogsLogGroupProvider = class {
16622
16910
  if (oldTags && oldTags.length > 0) {
16623
16911
  const oldTagKeys = oldTags.map((t) => t.Key);
16624
16912
  await this.logsClient.send(
16625
- new UntagResourceCommand2({
16913
+ new UntagResourceCommand5({
16626
16914
  resourceArn: arn2,
16627
16915
  tagKeys: oldTagKeys
16628
16916
  })
@@ -16631,7 +16919,7 @@ var LogsLogGroupProvider = class {
16631
16919
  if (newTags && newTags.length > 0) {
16632
16920
  const tagsMap = Object.fromEntries(newTags.map((t) => [t.Key, t.Value]));
16633
16921
  await this.logsClient.send(
16634
- new TagResourceCommand2({
16922
+ new TagResourceCommand5({
16635
16923
  resourceArn: arn2,
16636
16924
  tags: tagsMap
16637
16925
  })
@@ -16831,7 +17119,9 @@ import {
16831
17119
  PutMetricAlarmCommand,
16832
17120
  DeleteAlarmsCommand,
16833
17121
  DescribeAlarmsCommand,
16834
- ListTagsForResourceCommand as ListTagsForResourceCommand3
17122
+ ListTagsForResourceCommand as ListTagsForResourceCommand3,
17123
+ TagResourceCommand as TagResourceCommand6,
17124
+ UntagResourceCommand as UntagResourceCommand6
16835
17125
  } from "@aws-sdk/client-cloudwatch";
16836
17126
  init_aws_clients();
16837
17127
  var CloudWatchAlarmProvider = class {
@@ -16900,7 +17190,7 @@ var CloudWatchAlarmProvider = class {
16900
17190
  *
16901
17191
  * PutMetricAlarm is idempotent - calling it with the same alarm name updates the alarm.
16902
17192
  */
16903
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
17193
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
16904
17194
  this.logger.debug(`Updating CloudWatch alarm ${logicalId}: ${physicalId}`);
16905
17195
  try {
16906
17196
  await this.cloudWatchClient.send(
@@ -16908,6 +17198,11 @@ var CloudWatchAlarmProvider = class {
16908
17198
  );
16909
17199
  this.logger.debug(`Successfully updated CloudWatch alarm ${logicalId}`);
16910
17200
  const alarmArn = await this.getAlarmArn(physicalId);
17201
+ await this.applyTagDiff(
17202
+ alarmArn,
17203
+ previousProperties["Tags"],
17204
+ properties["Tags"]
17205
+ );
16911
17206
  return {
16912
17207
  physicalId,
16913
17208
  wasReplaced: false,
@@ -16992,6 +17287,44 @@ var CloudWatchAlarmProvider = class {
16992
17287
  return `arn:aws:cloudwatch:*:*:alarm:${alarmName}`;
16993
17288
  }
16994
17289
  }
17290
+ /**
17291
+ * Apply a diff between old and new CFn-shape Tags arrays via CloudWatch's
17292
+ * `TagResource` / `UntagResource` APIs (keyed by `ResourceARN`).
17293
+ */
17294
+ async applyTagDiff(resourceArn, oldTagsRaw, newTagsRaw) {
17295
+ const toMap = (tags) => {
17296
+ const m = /* @__PURE__ */ new Map();
17297
+ for (const t of tags ?? []) {
17298
+ if (t.Key !== void 0 && t.Value !== void 0)
17299
+ m.set(t.Key, t.Value);
17300
+ }
17301
+ return m;
17302
+ };
17303
+ const oldMap = toMap(oldTagsRaw);
17304
+ const newMap = toMap(newTagsRaw);
17305
+ const tagsToAdd = [];
17306
+ for (const [k, v] of newMap) {
17307
+ if (oldMap.get(k) !== v)
17308
+ tagsToAdd.push({ Key: k, Value: v });
17309
+ }
17310
+ const tagsToRemove = [];
17311
+ for (const k of oldMap.keys()) {
17312
+ if (!newMap.has(k))
17313
+ tagsToRemove.push(k);
17314
+ }
17315
+ if (tagsToRemove.length > 0) {
17316
+ await this.cloudWatchClient.send(
17317
+ new UntagResourceCommand6({ ResourceARN: resourceArn, TagKeys: tagsToRemove })
17318
+ );
17319
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from alarm ${resourceArn}`);
17320
+ }
17321
+ if (tagsToAdd.length > 0) {
17322
+ await this.cloudWatchClient.send(
17323
+ new TagResourceCommand6({ ResourceARN: resourceArn, Tags: tagsToAdd })
17324
+ );
17325
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on alarm ${resourceArn}`);
17326
+ }
17327
+ }
16995
17328
  /**
16996
17329
  * Build PutMetricAlarm parameters from CDK properties
16997
17330
  */
@@ -17184,8 +17517,8 @@ import {
17184
17517
  DescribeSecretCommand,
17185
17518
  ListSecretsCommand,
17186
17519
  UpdateSecretCommand,
17187
- TagResourceCommand as TagResourceCommand3,
17188
- UntagResourceCommand as UntagResourceCommand3,
17520
+ TagResourceCommand as TagResourceCommand7,
17521
+ UntagResourceCommand as UntagResourceCommand7,
17189
17522
  ReplicateSecretToRegionsCommand,
17190
17523
  RemoveRegionsFromReplicationCommand,
17191
17524
  ResourceNotFoundException as ResourceNotFoundException8
@@ -17298,7 +17631,7 @@ var SecretsManagerSecretProvider = class {
17298
17631
  const oldTagKeys = oldTags.map((t) => t.Key).filter((k) => !!k);
17299
17632
  if (oldTagKeys.length > 0) {
17300
17633
  await this.smClient.send(
17301
- new UntagResourceCommand3({
17634
+ new UntagResourceCommand7({
17302
17635
  SecretId: physicalId,
17303
17636
  TagKeys: oldTagKeys
17304
17637
  })
@@ -17307,7 +17640,7 @@ var SecretsManagerSecretProvider = class {
17307
17640
  }
17308
17641
  if (newTags && newTags.length > 0) {
17309
17642
  await this.smClient.send(
17310
- new TagResourceCommand3({
17643
+ new TagResourceCommand7({
17311
17644
  SecretId: physicalId,
17312
17645
  Tags: newTags
17313
17646
  })
@@ -17911,8 +18244,8 @@ import {
17911
18244
  ListRulesCommand,
17912
18245
  ListTargetsByRuleCommand,
17913
18246
  ListTagsForResourceCommand as ListTagsForResourceCommand5,
17914
- TagResourceCommand as TagResourceCommand4,
17915
- UntagResourceCommand as UntagResourceCommand4,
18247
+ TagResourceCommand as TagResourceCommand8,
18248
+ UntagResourceCommand as UntagResourceCommand8,
17916
18249
  ResourceNotFoundException as ResourceNotFoundException9
17917
18250
  } from "@aws-sdk/client-eventbridge";
17918
18251
  init_aws_clients();
@@ -18065,7 +18398,7 @@ var EventBridgeRuleProvider = class {
18065
18398
  const oldTagKeys = oldTags.map((t) => t.Key).filter((k) => !!k);
18066
18399
  if (oldTagKeys.length > 0) {
18067
18400
  await this.eventBridgeClient.send(
18068
- new UntagResourceCommand4({
18401
+ new UntagResourceCommand8({
18069
18402
  ResourceARN: ruleArn,
18070
18403
  TagKeys: oldTagKeys
18071
18404
  })
@@ -18074,7 +18407,7 @@ var EventBridgeRuleProvider = class {
18074
18407
  }
18075
18408
  if (newTags && newTags.length > 0) {
18076
18409
  await this.eventBridgeClient.send(
18077
- new TagResourceCommand4({
18410
+ new TagResourceCommand8({
18078
18411
  ResourceARN: ruleArn,
18079
18412
  Tags: newTags
18080
18413
  })
@@ -18381,8 +18714,8 @@ import {
18381
18714
  RemoveTargetsCommand as RemoveTargetsCommand2,
18382
18715
  DeleteRuleCommand as DeleteRuleCommand2,
18383
18716
  ListTargetsByRuleCommand as ListTargetsByRuleCommand2,
18384
- TagResourceCommand as TagResourceCommand5,
18385
- UntagResourceCommand as UntagResourceCommand5,
18717
+ TagResourceCommand as TagResourceCommand9,
18718
+ UntagResourceCommand as UntagResourceCommand9,
18386
18719
  ResourceNotFoundException as ResourceNotFoundException10
18387
18720
  } from "@aws-sdk/client-eventbridge";
18388
18721
  init_aws_clients();
@@ -18495,7 +18828,7 @@ var EventBridgeBusProvider = class {
18495
18828
  const oldTagKeys = oldTags.map((t) => t.Key).filter((k) => !!k);
18496
18829
  if (oldTagKeys.length > 0) {
18497
18830
  await this.eventBridgeClient.send(
18498
- new UntagResourceCommand5({
18831
+ new UntagResourceCommand9({
18499
18832
  ResourceARN: busArn,
18500
18833
  TagKeys: oldTagKeys
18501
18834
  })
@@ -18504,7 +18837,7 @@ var EventBridgeBusProvider = class {
18504
18837
  }
18505
18838
  if (newTags && newTags.length > 0) {
18506
18839
  await this.eventBridgeClient.send(
18507
- new TagResourceCommand5({
18840
+ new TagResourceCommand9({
18508
18841
  ResourceARN: busArn,
18509
18842
  Tags: newTags
18510
18843
  })
@@ -18756,6 +19089,7 @@ import {
18756
19089
  AuthorizeSecurityGroupEgressCommand,
18757
19090
  RevokeSecurityGroupEgressCommand,
18758
19091
  CreateTagsCommand,
19092
+ DeleteTagsCommand,
18759
19093
  DescribeSubnetsCommand as DescribeSubnetsCommand2,
18760
19094
  DescribeSecurityGroupsCommand as DescribeSecurityGroupsCommand2,
18761
19095
  RunInstancesCommand,
@@ -18920,7 +19254,7 @@ var EC2Provider = class {
18920
19254
  async update(logicalId, physicalId, resourceType, properties, previousProperties) {
18921
19255
  switch (resourceType) {
18922
19256
  case "AWS::EC2::VPC":
18923
- return this.updateVpc(logicalId, physicalId, resourceType, properties);
19257
+ return this.updateVpc(logicalId, physicalId, resourceType, properties, previousProperties);
18924
19258
  case "AWS::EC2::Subnet":
18925
19259
  return this.updateSubnet(logicalId, physicalId);
18926
19260
  case "AWS::EC2::InternetGateway":
@@ -18958,7 +19292,13 @@ var EC2Provider = class {
18958
19292
  previousProperties
18959
19293
  );
18960
19294
  case "AWS::EC2::Instance":
18961
- return this.updateInstance(logicalId, physicalId, resourceType, properties);
19295
+ return this.updateInstance(
19296
+ logicalId,
19297
+ physicalId,
19298
+ resourceType,
19299
+ properties,
19300
+ previousProperties
19301
+ );
18962
19302
  case "AWS::EC2::NetworkAcl":
18963
19303
  case "AWS::EC2::NetworkAclEntry":
18964
19304
  case "AWS::EC2::SubnetNetworkAclAssociation":
@@ -19104,7 +19444,7 @@ var EC2Provider = class {
19104
19444
  );
19105
19445
  }
19106
19446
  }
19107
- async updateVpc(logicalId, physicalId, resourceType, properties) {
19447
+ async updateVpc(logicalId, physicalId, resourceType, properties, previousProperties) {
19108
19448
  this.logger.debug(`Updating VPC ${logicalId}: ${physicalId}`);
19109
19449
  try {
19110
19450
  if (properties["EnableDnsHostnames"] !== void 0) {
@@ -19125,7 +19465,11 @@ var EC2Provider = class {
19125
19465
  })
19126
19466
  );
19127
19467
  }
19128
- await this.applyTags(physicalId, properties, logicalId);
19468
+ await this.applyTagDiff(
19469
+ physicalId,
19470
+ previousProperties["Tags"],
19471
+ properties["Tags"]
19472
+ );
19129
19473
  this.logger.debug(`Successfully updated VPC ${logicalId}`);
19130
19474
  return {
19131
19475
  physicalId,
@@ -19995,7 +20339,11 @@ var EC2Provider = class {
19995
20339
  async updateSecurityGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
19996
20340
  this.logger.debug(`Updating SecurityGroup ${logicalId}: ${physicalId}`);
19997
20341
  try {
19998
- await this.applyTags(physicalId, properties, logicalId);
20342
+ await this.applyTagDiff(
20343
+ physicalId,
20344
+ previousProperties["Tags"],
20345
+ properties["Tags"]
20346
+ );
19999
20347
  await this.applySecurityGroupRuleDiff(
20000
20348
  physicalId,
20001
20349
  previousProperties["SecurityGroupIngress"] ?? [],
@@ -20323,10 +20671,14 @@ var EC2Provider = class {
20323
20671
  );
20324
20672
  }
20325
20673
  }
20326
- async updateInstance(logicalId, physicalId, resourceType, _properties) {
20674
+ async updateInstance(logicalId, physicalId, resourceType, properties, previousProperties) {
20327
20675
  this.logger.debug(`Updating EC2 Instance ${logicalId}: ${physicalId}`);
20328
20676
  try {
20329
- await this.applyTags(physicalId, _properties, logicalId);
20677
+ await this.applyTagDiff(
20678
+ physicalId,
20679
+ previousProperties["Tags"],
20680
+ properties["Tags"]
20681
+ );
20330
20682
  const describeResponse = await this.ec2Client.send(
20331
20683
  new DescribeInstancesCommand({ InstanceIds: [physicalId] })
20332
20684
  );
@@ -20801,7 +21153,10 @@ var EC2Provider = class {
20801
21153
  }
20802
21154
  }
20803
21155
  /**
20804
- * Apply tags to an EC2 resource
21156
+ * Apply tags to an EC2 resource (create-time, no removal).
21157
+ *
21158
+ * Used by `create*` paths. Update paths should use `applyTagDiff` instead
21159
+ * to handle tag removal too.
20805
21160
  */
20806
21161
  async applyTags(resourceId, properties, logicalId) {
20807
21162
  const tags = properties["Tags"];
@@ -20821,6 +21176,64 @@ var EC2Provider = class {
20821
21176
  }
20822
21177
  }
20823
21178
  }
21179
+ /**
21180
+ * Apply a diff between old and new CFn-shape Tags arrays via EC2's
21181
+ * `CreateTags` / `DeleteTags` APIs. Used by `update*` paths so that
21182
+ * tag removals reach AWS too. EC2 keys both APIs by a list of resource
21183
+ * ids.
21184
+ */
21185
+ async applyTagDiff(resourceId, oldTagsRaw, newTagsRaw) {
21186
+ const toMap = (tags) => {
21187
+ const m = /* @__PURE__ */ new Map();
21188
+ for (const t of tags ?? []) {
21189
+ if (t.Key !== void 0 && t.Value !== void 0)
21190
+ m.set(t.Key, t.Value);
21191
+ }
21192
+ return m;
21193
+ };
21194
+ const oldMap = toMap(oldTagsRaw);
21195
+ const newMap = toMap(newTagsRaw);
21196
+ const tagsToAdd = [];
21197
+ for (const [k, v] of newMap) {
21198
+ if (oldMap.get(k) !== v)
21199
+ tagsToAdd.push({ Key: k, Value: v });
21200
+ }
21201
+ const tagsToRemove = [];
21202
+ for (const k of oldMap.keys()) {
21203
+ if (!newMap.has(k))
21204
+ tagsToRemove.push({ Key: k });
21205
+ }
21206
+ if (tagsToRemove.length > 0) {
21207
+ try {
21208
+ await this.ec2Client.send(
21209
+ new DeleteTagsCommand({
21210
+ Resources: [resourceId],
21211
+ Tags: tagsToRemove
21212
+ })
21213
+ );
21214
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from ${resourceId}`);
21215
+ } catch (error) {
21216
+ this.logger.warn(
21217
+ `Failed to remove tags from ${resourceId}: ${error instanceof Error ? error.message : String(error)}`
21218
+ );
21219
+ }
21220
+ }
21221
+ if (tagsToAdd.length > 0) {
21222
+ try {
21223
+ await this.ec2Client.send(
21224
+ new CreateTagsCommand({
21225
+ Resources: [resourceId],
21226
+ Tags: tagsToAdd
21227
+ })
21228
+ );
21229
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on ${resourceId}`);
21230
+ } catch (error) {
21231
+ this.logger.warn(
21232
+ `Failed to add tags on ${resourceId}: ${error instanceof Error ? error.message : String(error)}`
21233
+ );
21234
+ }
21235
+ }
21236
+ }
20824
21237
  /**
20825
21238
  * Check if an error indicates the resource was not found
20826
21239
  */
@@ -21167,6 +21580,8 @@ import {
21167
21580
  CreateAuthorizerCommand,
21168
21581
  DeleteAuthorizerCommand,
21169
21582
  GetAuthorizerCommand,
21583
+ TagResourceCommand as TagResourceCommand10,
21584
+ UntagResourceCommand as UntagResourceCommand10,
21170
21585
  NotFoundException as NotFoundException3
21171
21586
  } from "@aws-sdk/client-api-gateway";
21172
21587
  init_aws_clients();
@@ -21890,24 +22305,24 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
21890
22305
  value: description ?? ""
21891
22306
  });
21892
22307
  }
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
22308
  try {
21904
- await this.apiGatewayClient.send(
21905
- new UpdateStageCommand({
21906
- restApiId,
21907
- stageName: physicalId,
21908
- patchOperations
21909
- })
21910
- );
22309
+ if (patchOperations.length > 0) {
22310
+ await this.apiGatewayClient.send(
22311
+ new UpdateStageCommand({
22312
+ restApiId,
22313
+ stageName: physicalId,
22314
+ patchOperations
22315
+ })
22316
+ );
22317
+ }
22318
+ const stageArn = await this.buildStageArn(restApiId, physicalId);
22319
+ if (stageArn) {
22320
+ await this.applyTagDiff(
22321
+ stageArn,
22322
+ previousProperties["Tags"],
22323
+ properties["Tags"]
22324
+ );
22325
+ }
21911
22326
  this.logger.debug(`Successfully updated API Gateway Stage ${logicalId}`);
21912
22327
  return {
21913
22328
  physicalId,
@@ -22142,6 +22557,63 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
22142
22557
  }
22143
22558
  return Promise.resolve(void 0);
22144
22559
  }
22560
+ /**
22561
+ * Build the ARN for an API Gateway Stage, used for tag mutations.
22562
+ *
22563
+ * Format: `arn:aws:apigateway:{region}::/restapis/{restApiId}/stages/{stageName}`.
22564
+ * The double colon (`::`) is intentional: API Gateway tagging uses an
22565
+ * account-id-less ARN.
22566
+ */
22567
+ async buildStageArn(restApiId, stageName) {
22568
+ try {
22569
+ const region = await this.apiGatewayClient.config.region();
22570
+ return `arn:aws:apigateway:${region}::/restapis/${restApiId}/stages/${stageName}`;
22571
+ } catch {
22572
+ return void 0;
22573
+ }
22574
+ }
22575
+ /**
22576
+ * Apply a diff between old and new CFn-shape Tags arrays via API Gateway's
22577
+ * `TagResource` / `UntagResource` APIs. API Gateway's `TagResource` takes
22578
+ * lowercase camelCase fields plus a tag-map (`{ resourceArn, tags: {key: value} }`);
22579
+ * `UntagResource` takes `{ resourceArn, tagKeys: [...] }`.
22580
+ */
22581
+ async applyTagDiff(resourceArn, oldTagsRaw, newTagsRaw) {
22582
+ const toMap = (tags) => {
22583
+ const m = /* @__PURE__ */ new Map();
22584
+ for (const t of tags ?? []) {
22585
+ if (t.Key !== void 0 && t.Value !== void 0)
22586
+ m.set(t.Key, t.Value);
22587
+ }
22588
+ return m;
22589
+ };
22590
+ const oldMap = toMap(oldTagsRaw);
22591
+ const newMap = toMap(newTagsRaw);
22592
+ const tagsToAdd = {};
22593
+ for (const [k, v] of newMap) {
22594
+ if (oldMap.get(k) !== v)
22595
+ tagsToAdd[k] = v;
22596
+ }
22597
+ const tagsToRemove = [];
22598
+ for (const k of oldMap.keys()) {
22599
+ if (!newMap.has(k))
22600
+ tagsToRemove.push(k);
22601
+ }
22602
+ if (tagsToRemove.length > 0) {
22603
+ await this.apiGatewayClient.send(
22604
+ new UntagResourceCommand10({ resourceArn, tagKeys: tagsToRemove })
22605
+ );
22606
+ this.logger.debug(
22607
+ `Removed ${tagsToRemove.length} tag(s) from API Gateway resource ${resourceArn}`
22608
+ );
22609
+ }
22610
+ if (Object.keys(tagsToAdd).length > 0) {
22611
+ await this.apiGatewayClient.send(new TagResourceCommand10({ resourceArn, tags: tagsToAdd }));
22612
+ this.logger.debug(
22613
+ `Added/updated ${Object.keys(tagsToAdd).length} tag(s) on API Gateway resource ${resourceArn}`
22614
+ );
22615
+ }
22616
+ }
22145
22617
  /**
22146
22618
  * Sleep for specified milliseconds
22147
22619
  */
@@ -24175,6 +24647,8 @@ import {
24175
24647
  DescribeStateMachineCommand,
24176
24648
  ListStateMachinesCommand,
24177
24649
  ListTagsForResourceCommand as ListTagsForResourceCommand8,
24650
+ TagResourceCommand as TagResourceCommand11,
24651
+ UntagResourceCommand as UntagResourceCommand11,
24178
24652
  StateMachineDoesNotExist
24179
24653
  } from "@aws-sdk/client-sfn";
24180
24654
  var StepFunctionsProvider = class {
@@ -24277,7 +24751,7 @@ var StepFunctionsProvider = class {
24277
24751
  /**
24278
24752
  * Update a Step Functions state machine
24279
24753
  */
24280
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
24754
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
24281
24755
  this.logger.debug(`Updating Step Functions state machine ${logicalId}: ${physicalId}`);
24282
24756
  try {
24283
24757
  const definitionString = this.buildDefinitionString(properties);
@@ -24301,6 +24775,11 @@ var StepFunctionsProvider = class {
24301
24775
  })
24302
24776
  );
24303
24777
  this.logger.debug(`Updated Step Functions state machine ${physicalId}`);
24778
+ await this.applyTagDiff(
24779
+ physicalId,
24780
+ previousProperties["Tags"],
24781
+ properties["Tags"]
24782
+ );
24304
24783
  const describeResponse = await this.getClient().send(
24305
24784
  new DescribeStateMachineCommand({ stateMachineArn: physicalId })
24306
24785
  );
@@ -24502,6 +24981,49 @@ var StepFunctionsProvider = class {
24502
24981
  } while (nextToken);
24503
24982
  return null;
24504
24983
  }
24984
+ /**
24985
+ * Apply a diff between old and new CFn-shape Tags arrays via SFN's
24986
+ * `TagResource` / `UntagResource` APIs. SFN uses lowercase camelCase
24987
+ * (`{ key, value }`) for tags.
24988
+ */
24989
+ async applyTagDiff(stateMachineArn, oldTagsRaw, newTagsRaw) {
24990
+ const toMap = (tags) => {
24991
+ const m = /* @__PURE__ */ new Map();
24992
+ for (const t of tags ?? []) {
24993
+ if (t.Key !== void 0 && t.Value !== void 0)
24994
+ m.set(t.Key, t.Value);
24995
+ }
24996
+ return m;
24997
+ };
24998
+ const oldMap = toMap(oldTagsRaw);
24999
+ const newMap = toMap(newTagsRaw);
25000
+ const tagsToAdd = [];
25001
+ for (const [k, v] of newMap) {
25002
+ if (oldMap.get(k) !== v)
25003
+ tagsToAdd.push({ key: k, value: v });
25004
+ }
25005
+ const tagsToRemove = [];
25006
+ for (const k of oldMap.keys()) {
25007
+ if (!newMap.has(k))
25008
+ tagsToRemove.push(k);
25009
+ }
25010
+ if (tagsToRemove.length > 0) {
25011
+ await this.getClient().send(
25012
+ new UntagResourceCommand11({ resourceArn: stateMachineArn, tagKeys: tagsToRemove })
25013
+ );
25014
+ this.logger.debug(
25015
+ `Removed ${tagsToRemove.length} tag(s) from SFN state machine ${stateMachineArn}`
25016
+ );
25017
+ }
25018
+ if (tagsToAdd.length > 0) {
25019
+ await this.getClient().send(
25020
+ new TagResourceCommand11({ resourceArn: stateMachineArn, tags: tagsToAdd })
25021
+ );
25022
+ this.logger.debug(
25023
+ `Added/updated ${tagsToAdd.length} tag(s) on SFN state machine ${stateMachineArn}`
25024
+ );
25025
+ }
25026
+ }
24505
25027
  /**
24506
25028
  * Match SFN's lowercase `key`/`value` tag shape against the CDK path.
24507
25029
  */
@@ -24553,7 +25075,9 @@ import {
24553
25075
  DescribeServicesCommand,
24554
25076
  ListClustersCommand,
24555
25077
  ListServicesCommand,
24556
- ListTagsForResourceCommand as ListTagsForResourceCommand9
25078
+ ListTagsForResourceCommand as ListTagsForResourceCommand9,
25079
+ TagResourceCommand as TagResourceCommand12,
25080
+ UntagResourceCommand as UntagResourceCommand12
24557
25081
  } from "@aws-sdk/client-ecs";
24558
25082
  function convertTags(tags) {
24559
25083
  if (!tags || tags.length === 0)
@@ -24648,7 +25172,13 @@ var ECSProvider = class {
24648
25172
  async update(logicalId, physicalId, resourceType, properties, previousProperties) {
24649
25173
  switch (resourceType) {
24650
25174
  case "AWS::ECS::Cluster":
24651
- return this.updateCluster(logicalId, physicalId, resourceType, properties);
25175
+ return this.updateCluster(
25176
+ logicalId,
25177
+ physicalId,
25178
+ resourceType,
25179
+ properties,
25180
+ previousProperties
25181
+ );
24652
25182
  case "AWS::ECS::TaskDefinition":
24653
25183
  return this.updateTaskDefinition(logicalId, physicalId, resourceType, properties);
24654
25184
  case "AWS::ECS::Service":
@@ -24740,7 +25270,7 @@ var ECSProvider = class {
24740
25270
  );
24741
25271
  }
24742
25272
  }
24743
- async updateCluster(logicalId, physicalId, resourceType, properties) {
25273
+ async updateCluster(logicalId, physicalId, resourceType, properties, previousProperties) {
24744
25274
  this.logger.debug(`Updating ECS cluster ${logicalId}: ${physicalId}`);
24745
25275
  const client = this.getClient();
24746
25276
  try {
@@ -24758,6 +25288,13 @@ var ECSProvider = class {
24758
25288
  new DescribeClustersCommand({ clusters: [physicalId] })
24759
25289
  );
24760
25290
  const cluster = describeResponse.clusters?.[0];
25291
+ if (cluster?.clusterArn) {
25292
+ await this.applyTagDiff(
25293
+ cluster.clusterArn,
25294
+ previousProperties["Tags"],
25295
+ properties["Tags"]
25296
+ );
25297
+ }
24761
25298
  return {
24762
25299
  physicalId,
24763
25300
  wasReplaced: false,
@@ -25026,6 +25563,13 @@ var ECSProvider = class {
25026
25563
  })
25027
25564
  );
25028
25565
  const service = response.service;
25566
+ if (service?.serviceArn) {
25567
+ await this.applyTagDiff(
25568
+ service.serviceArn,
25569
+ previousProperties["Tags"],
25570
+ properties["Tags"]
25571
+ );
25572
+ }
25029
25573
  return {
25030
25574
  physicalId,
25031
25575
  wasReplaced: false,
@@ -25127,6 +25671,42 @@ var ECSProvider = class {
25127
25671
  }
25128
25672
  }
25129
25673
  // ─── Helpers ────────────────────────────────────────────────────
25674
+ /**
25675
+ * Apply a diff between old and new CFn-shape Tags arrays via ECS's
25676
+ * `TagResource` / `UntagResource` APIs. ECS uses lowercase camelCase
25677
+ * (`{ key, value }`) for tags. Resource ARN identifies the cluster /
25678
+ * service / task definition.
25679
+ */
25680
+ async applyTagDiff(resourceArn, oldTagsRaw, newTagsRaw) {
25681
+ const toMap = (tags) => {
25682
+ const m = /* @__PURE__ */ new Map();
25683
+ for (const t of tags ?? []) {
25684
+ if (t.Key !== void 0 && t.Value !== void 0)
25685
+ m.set(t.Key, t.Value);
25686
+ }
25687
+ return m;
25688
+ };
25689
+ const oldMap = toMap(oldTagsRaw);
25690
+ const newMap = toMap(newTagsRaw);
25691
+ const tagsToAdd = [];
25692
+ for (const [k, v] of newMap) {
25693
+ if (oldMap.get(k) !== v)
25694
+ tagsToAdd.push({ key: k, value: v });
25695
+ }
25696
+ const tagsToRemove = [];
25697
+ for (const k of oldMap.keys()) {
25698
+ if (!newMap.has(k))
25699
+ tagsToRemove.push(k);
25700
+ }
25701
+ if (tagsToRemove.length > 0) {
25702
+ await this.getClient().send(new UntagResourceCommand12({ resourceArn, tagKeys: tagsToRemove }));
25703
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from ECS resource ${resourceArn}`);
25704
+ }
25705
+ if (tagsToAdd.length > 0) {
25706
+ await this.getClient().send(new TagResourceCommand12({ resourceArn, tags: tagsToAdd }));
25707
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on ECS resource ${resourceArn}`);
25708
+ }
25709
+ }
25130
25710
  /**
25131
25711
  * Convert CFn ContainerDefinitions to ECS SDK format.
25132
25712
  * CFn uses PascalCase, ECS SDK uses camelCase.
@@ -25574,6 +26154,8 @@ import {
25574
26154
  ModifyTargetGroupCommand,
25575
26155
  DescribeTargetGroupsCommand,
25576
26156
  DescribeTagsCommand,
26157
+ AddTagsCommand,
26158
+ RemoveTagsCommand,
25577
26159
  CreateListenerCommand,
25578
26160
  DeleteListenerCommand,
25579
26161
  ModifyListenerCommand,
@@ -25656,14 +26238,26 @@ var ELBv2Provider = class {
25656
26238
  );
25657
26239
  }
25658
26240
  }
25659
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
26241
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
25660
26242
  switch (resourceType) {
25661
26243
  case "AWS::ElasticLoadBalancingV2::LoadBalancer":
25662
26244
  return this.updateLoadBalancer(logicalId, physicalId, resourceType, properties);
25663
26245
  case "AWS::ElasticLoadBalancingV2::TargetGroup":
25664
- return this.updateTargetGroup(logicalId, physicalId, resourceType, properties);
26246
+ return this.updateTargetGroup(
26247
+ logicalId,
26248
+ physicalId,
26249
+ resourceType,
26250
+ properties,
26251
+ previousProperties
26252
+ );
25665
26253
  case "AWS::ElasticLoadBalancingV2::Listener":
25666
- return this.updateListener(logicalId, physicalId, resourceType, properties);
26254
+ return this.updateListener(
26255
+ logicalId,
26256
+ physicalId,
26257
+ resourceType,
26258
+ properties,
26259
+ previousProperties
26260
+ );
25667
26261
  default:
25668
26262
  throw new ProvisioningError(
25669
26263
  `Unsupported resource type: ${resourceType}`,
@@ -25842,7 +26436,7 @@ var ELBv2Provider = class {
25842
26436
  );
25843
26437
  }
25844
26438
  }
25845
- async updateTargetGroup(logicalId, physicalId, resourceType, properties) {
26439
+ async updateTargetGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
25846
26440
  this.logger.debug(`Updating TargetGroup ${logicalId}: ${physicalId}`);
25847
26441
  try {
25848
26442
  const matcher = properties["Matcher"];
@@ -25864,6 +26458,11 @@ var ELBv2Provider = class {
25864
26458
  new DescribeTargetGroupsCommand({ TargetGroupArns: [physicalId] })
25865
26459
  );
25866
26460
  const tg = describeResponse.TargetGroups?.[0];
26461
+ await this.applyTagDiff(
26462
+ physicalId,
26463
+ previousProperties["Tags"],
26464
+ properties["Tags"]
26465
+ );
25867
26466
  this.logger.debug(`Successfully updated TargetGroup ${logicalId}`);
25868
26467
  return {
25869
26468
  physicalId,
@@ -25957,7 +26556,7 @@ var ELBv2Provider = class {
25957
26556
  );
25958
26557
  }
25959
26558
  }
25960
- async updateListener(logicalId, physicalId, resourceType, properties) {
26559
+ async updateListener(logicalId, physicalId, resourceType, properties, previousProperties) {
25961
26560
  this.logger.debug(`Updating Listener ${logicalId}: ${physicalId}`);
25962
26561
  try {
25963
26562
  const defaultActions = this.convertActions(
@@ -25976,6 +26575,11 @@ var ELBv2Provider = class {
25976
26575
  ...certificates && { Certificates: certificates }
25977
26576
  })
25978
26577
  );
26578
+ await this.applyTagDiff(
26579
+ physicalId,
26580
+ previousProperties["Tags"],
26581
+ properties["Tags"]
26582
+ );
25979
26583
  this.logger.debug(`Successfully updated Listener ${logicalId}`);
25980
26584
  return {
25981
26585
  physicalId,
@@ -26033,6 +26637,44 @@ var ELBv2Provider = class {
26033
26637
  return [];
26034
26638
  return properties["Tags"];
26035
26639
  }
26640
+ /**
26641
+ * Apply a diff between old and new CFn-shape Tags arrays via ELBv2's
26642
+ * `AddTags` / `RemoveTags` APIs. Both accept `ResourceArns: [arn]`
26643
+ * (single ARN), `Tags: [{Key, Value}]` for AddTags, and
26644
+ * `TagKeys: [...]` for RemoveTags.
26645
+ */
26646
+ async applyTagDiff(arn, oldTagsRaw, newTagsRaw) {
26647
+ const toMap = (tags) => {
26648
+ const m = /* @__PURE__ */ new Map();
26649
+ for (const t of tags ?? []) {
26650
+ if (t.Key !== void 0 && t.Value !== void 0)
26651
+ m.set(t.Key, t.Value);
26652
+ }
26653
+ return m;
26654
+ };
26655
+ const oldMap = toMap(oldTagsRaw);
26656
+ const newMap = toMap(newTagsRaw);
26657
+ const tagsToAdd = [];
26658
+ for (const [k, v] of newMap) {
26659
+ if (oldMap.get(k) !== v)
26660
+ tagsToAdd.push({ Key: k, Value: v });
26661
+ }
26662
+ const tagsToRemove = [];
26663
+ for (const k of oldMap.keys()) {
26664
+ if (!newMap.has(k))
26665
+ tagsToRemove.push(k);
26666
+ }
26667
+ if (tagsToRemove.length > 0) {
26668
+ await this.getClient().send(
26669
+ new RemoveTagsCommand({ ResourceArns: [arn], TagKeys: tagsToRemove })
26670
+ );
26671
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from ELBv2 resource ${arn}`);
26672
+ }
26673
+ if (tagsToAdd.length > 0) {
26674
+ await this.getClient().send(new AddTagsCommand({ ResourceArns: [arn], Tags: tagsToAdd }));
26675
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on ELBv2 resource ${arn}`);
26676
+ }
26677
+ }
26036
26678
  /**
26037
26679
  * Convert CDK DefaultActions to ELBv2 API Action format
26038
26680
  * CDK uses PascalCase property names matching the ELBv2 API, so pass through.
@@ -26347,7 +26989,9 @@ import {
26347
26989
  DeleteDBSubnetGroupCommand,
26348
26990
  DescribeDBSubnetGroupsCommand,
26349
26991
  ModifyDBSubnetGroupCommand,
26350
- ListTagsForResourceCommand as ListTagsForResourceCommand10
26992
+ ListTagsForResourceCommand as ListTagsForResourceCommand10,
26993
+ AddTagsToResourceCommand as AddTagsToResourceCommand2,
26994
+ RemoveTagsFromResourceCommand as RemoveTagsFromResourceCommand2
26351
26995
  } from "@aws-sdk/client-rds";
26352
26996
  var RDSProvider = class {
26353
26997
  rdsClient;
@@ -26414,14 +27058,32 @@ var RDSProvider = class {
26414
27058
  );
26415
27059
  }
26416
27060
  }
26417
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
27061
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
26418
27062
  switch (resourceType) {
26419
27063
  case "AWS::RDS::DBSubnetGroup":
26420
- return this.updateDBSubnetGroup(logicalId, physicalId, resourceType, properties);
27064
+ return this.updateDBSubnetGroup(
27065
+ logicalId,
27066
+ physicalId,
27067
+ resourceType,
27068
+ properties,
27069
+ previousProperties
27070
+ );
26421
27071
  case "AWS::RDS::DBCluster":
26422
- return this.updateDBCluster(logicalId, physicalId, resourceType, properties);
27072
+ return this.updateDBCluster(
27073
+ logicalId,
27074
+ physicalId,
27075
+ resourceType,
27076
+ properties,
27077
+ previousProperties
27078
+ );
26423
27079
  case "AWS::RDS::DBInstance":
26424
- return this.updateDBInstance(logicalId, physicalId, resourceType, properties);
27080
+ return this.updateDBInstance(
27081
+ logicalId,
27082
+ physicalId,
27083
+ resourceType,
27084
+ properties,
27085
+ previousProperties
27086
+ );
26425
27087
  default:
26426
27088
  throw new ProvisioningError(
26427
27089
  `Unsupported resource type: ${resourceType}`,
@@ -26480,7 +27142,7 @@ var RDSProvider = class {
26480
27142
  );
26481
27143
  }
26482
27144
  }
26483
- async updateDBSubnetGroup(logicalId, physicalId, resourceType, properties) {
27145
+ async updateDBSubnetGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
26484
27146
  this.logger.debug(`Updating DBSubnetGroup ${logicalId}: ${physicalId}`);
26485
27147
  try {
26486
27148
  await this.getClient().send(
@@ -26490,6 +27152,17 @@ var RDSProvider = class {
26490
27152
  SubnetIds: properties["SubnetIds"]
26491
27153
  })
26492
27154
  );
27155
+ const desc = await this.getClient().send(
27156
+ new DescribeDBSubnetGroupsCommand({ DBSubnetGroupName: physicalId })
27157
+ );
27158
+ const arn = desc.DBSubnetGroups?.[0]?.DBSubnetGroupArn;
27159
+ if (arn) {
27160
+ await this.applyTagDiff(
27161
+ arn,
27162
+ previousProperties["Tags"],
27163
+ properties["Tags"]
27164
+ );
27165
+ }
26493
27166
  this.logger.debug(`Successfully updated DBSubnetGroup ${logicalId}`);
26494
27167
  return {
26495
27168
  physicalId,
@@ -26604,7 +27277,7 @@ var RDSProvider = class {
26604
27277
  );
26605
27278
  }
26606
27279
  }
26607
- async updateDBCluster(logicalId, physicalId, resourceType, properties) {
27280
+ async updateDBCluster(logicalId, physicalId, resourceType, properties, previousProperties) {
26608
27281
  this.logger.debug(`Updating DBCluster ${logicalId}: ${physicalId}`);
26609
27282
  try {
26610
27283
  const serverlessV2Config = properties["ServerlessV2ScalingConfiguration"];
@@ -26628,6 +27301,13 @@ var RDSProvider = class {
26628
27301
  );
26629
27302
  this.logger.debug(`Successfully updated DBCluster ${logicalId}`);
26630
27303
  const described = await this.describeDBCluster(physicalId);
27304
+ if (described?.DBClusterArn) {
27305
+ await this.applyTagDiff(
27306
+ described.DBClusterArn,
27307
+ previousProperties["Tags"],
27308
+ properties["Tags"]
27309
+ );
27310
+ }
26631
27311
  return {
26632
27312
  physicalId,
26633
27313
  wasReplaced: false,
@@ -26745,7 +27425,7 @@ var RDSProvider = class {
26745
27425
  );
26746
27426
  }
26747
27427
  }
26748
- async updateDBInstance(logicalId, physicalId, resourceType, properties) {
27428
+ async updateDBInstance(logicalId, physicalId, resourceType, properties, previousProperties) {
26749
27429
  this.logger.debug(`Updating DBInstance ${logicalId}: ${physicalId}`);
26750
27430
  try {
26751
27431
  await this.getClient().send(
@@ -26758,6 +27438,13 @@ var RDSProvider = class {
26758
27438
  );
26759
27439
  this.logger.debug(`Successfully updated DBInstance ${logicalId}`);
26760
27440
  const described = await this.describeDBInstance(physicalId);
27441
+ if (described?.DBInstanceArn) {
27442
+ await this.applyTagDiff(
27443
+ described.DBInstanceArn,
27444
+ previousProperties["Tags"],
27445
+ properties["Tags"]
27446
+ );
27447
+ }
26761
27448
  return {
26762
27449
  physicalId,
26763
27450
  wasReplaced: false,
@@ -26828,6 +27515,45 @@ var RDSProvider = class {
26828
27515
  }
26829
27516
  }
26830
27517
  // ─── Helpers ──────────────────────────────────────────────────────
27518
+ /**
27519
+ * Apply a diff between old and new CFn-shape Tags arrays via RDS's
27520
+ * `AddTagsToResource` / `RemoveTagsFromResource` APIs (keyed by
27521
+ * `ResourceName=arn`).
27522
+ */
27523
+ async applyTagDiff(arn, oldTagsRaw, newTagsRaw) {
27524
+ const toMap = (tags) => {
27525
+ const m = /* @__PURE__ */ new Map();
27526
+ for (const t of tags ?? []) {
27527
+ if (t.Key !== void 0 && t.Value !== void 0)
27528
+ m.set(t.Key, t.Value);
27529
+ }
27530
+ return m;
27531
+ };
27532
+ const oldMap = toMap(oldTagsRaw);
27533
+ const newMap = toMap(newTagsRaw);
27534
+ const tagsToAdd = [];
27535
+ for (const [k, v] of newMap) {
27536
+ if (oldMap.get(k) !== v)
27537
+ tagsToAdd.push({ Key: k, Value: v });
27538
+ }
27539
+ const tagsToRemove = [];
27540
+ for (const k of oldMap.keys()) {
27541
+ if (!newMap.has(k))
27542
+ tagsToRemove.push(k);
27543
+ }
27544
+ if (tagsToRemove.length > 0) {
27545
+ await this.getClient().send(
27546
+ new RemoveTagsFromResourceCommand2({ ResourceName: arn, TagKeys: tagsToRemove })
27547
+ );
27548
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from RDS resource ${arn}`);
27549
+ }
27550
+ if (tagsToAdd.length > 0) {
27551
+ await this.getClient().send(
27552
+ new AddTagsToResourceCommand2({ ResourceName: arn, Tags: tagsToAdd })
27553
+ );
27554
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on RDS resource ${arn}`);
27555
+ }
27556
+ }
26831
27557
  buildTags(properties) {
26832
27558
  if (!properties["Tags"])
26833
27559
  return [];
@@ -28115,6 +28841,8 @@ import {
28115
28841
  GetWebACLCommand,
28116
28842
  ListWebACLsCommand,
28117
28843
  ListTagsForResourceCommand as ListTagsForResourceCommand12,
28844
+ TagResourceCommand as TagResourceCommand13,
28845
+ UntagResourceCommand as UntagResourceCommand13,
28118
28846
  WAFNonexistentItemException
28119
28847
  } from "@aws-sdk/client-wafv2";
28120
28848
  function parseWebACLArn(arn) {
@@ -28221,7 +28949,7 @@ var WAFv2WebACLProvider = class {
28221
28949
  * Name and Scope are immutable - changes to those require replacement.
28222
28950
  * UpdateWebACL requires LockToken obtained from GetWebACL.
28223
28951
  */
28224
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
28952
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
28225
28953
  this.logger.debug(`Updating WAFv2 WebACL ${logicalId}: ${physicalId}`);
28226
28954
  try {
28227
28955
  const { id, name, scope } = parseWebACLArn(physicalId);
@@ -28253,6 +28981,11 @@ var WAFv2WebACLProvider = class {
28253
28981
  AssociationConfig: properties["AssociationConfig"]
28254
28982
  })
28255
28983
  );
28984
+ await this.applyTagDiff(
28985
+ physicalId,
28986
+ previousProperties["Tags"],
28987
+ properties["Tags"]
28988
+ );
28256
28989
  this.logger.debug(`Successfully updated WAFv2 WebACL ${logicalId}`);
28257
28990
  return {
28258
28991
  physicalId,
@@ -28329,6 +29062,42 @@ var WAFv2WebACLProvider = class {
28329
29062
  );
28330
29063
  }
28331
29064
  }
29065
+ /**
29066
+ * Apply a diff between old and new CFn-shape Tags arrays via WAFv2's
29067
+ * `TagResource` / `UntagResource` APIs (keyed by `ResourceARN`).
29068
+ */
29069
+ async applyTagDiff(arn, oldTagsRaw, newTagsRaw) {
29070
+ const toMap = (tags) => {
29071
+ const m = /* @__PURE__ */ new Map();
29072
+ for (const t of tags ?? []) {
29073
+ if (t.Key !== void 0 && t.Value !== void 0)
29074
+ m.set(t.Key, t.Value);
29075
+ }
29076
+ return m;
29077
+ };
29078
+ const oldMap = toMap(oldTagsRaw);
29079
+ const newMap = toMap(newTagsRaw);
29080
+ const tagsToAdd = [];
29081
+ for (const [k, v] of newMap) {
29082
+ if (oldMap.get(k) !== v)
29083
+ tagsToAdd.push({ Key: k, Value: v });
29084
+ }
29085
+ const tagsToRemove = [];
29086
+ for (const k of oldMap.keys()) {
29087
+ if (!newMap.has(k))
29088
+ tagsToRemove.push(k);
29089
+ }
29090
+ if (tagsToRemove.length > 0) {
29091
+ await this.getClient().send(
29092
+ new UntagResourceCommand13({ ResourceARN: arn, TagKeys: tagsToRemove })
29093
+ );
29094
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from WAFv2 WebACL ${arn}`);
29095
+ }
29096
+ if (tagsToAdd.length > 0) {
29097
+ await this.getClient().send(new TagResourceCommand13({ ResourceARN: arn, Tags: tagsToAdd }));
29098
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on WAFv2 WebACL ${arn}`);
29099
+ }
29100
+ }
28332
29101
  /**
28333
29102
  * Read the AWS-current WAFv2 WebACL configuration in CFn-property shape.
28334
29103
  *
@@ -28943,7 +29712,9 @@ import {
28943
29712
  DeleteCacheSubnetGroupCommand,
28944
29713
  ModifyCacheSubnetGroupCommand,
28945
29714
  ModifyCacheClusterCommand,
28946
- ListTagsForResourceCommand as ListTagsForResourceCommand14
29715
+ ListTagsForResourceCommand as ListTagsForResourceCommand14,
29716
+ AddTagsToResourceCommand as AddTagsToResourceCommand3,
29717
+ RemoveTagsFromResourceCommand as RemoveTagsFromResourceCommand3
28947
29718
  } from "@aws-sdk/client-elasticache";
28948
29719
  import { STSClient as STSClient6, GetCallerIdentityCommand as GetCallerIdentityCommand6 } from "@aws-sdk/client-sts";
28949
29720
  var ElastiCacheProvider = class {
@@ -29009,12 +29780,18 @@ var ElastiCacheProvider = class {
29009
29780
  );
29010
29781
  }
29011
29782
  }
29012
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
29783
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
29013
29784
  switch (resourceType) {
29014
29785
  case "AWS::ElastiCache::SubnetGroup":
29015
29786
  return this.updateSubnetGroup(logicalId, physicalId, resourceType, properties);
29016
29787
  case "AWS::ElastiCache::CacheCluster":
29017
- return this.updateCacheCluster(logicalId, physicalId, resourceType, properties);
29788
+ return this.updateCacheCluster(
29789
+ logicalId,
29790
+ physicalId,
29791
+ resourceType,
29792
+ properties,
29793
+ previousProperties
29794
+ );
29018
29795
  default:
29019
29796
  throw new ProvisioningError(
29020
29797
  `Unsupported resource type: ${resourceType}`,
@@ -29199,7 +29976,7 @@ var ElastiCacheProvider = class {
29199
29976
  );
29200
29977
  }
29201
29978
  }
29202
- async updateCacheCluster(logicalId, physicalId, resourceType, properties) {
29979
+ async updateCacheCluster(logicalId, physicalId, resourceType, properties, previousProperties) {
29203
29980
  this.logger.debug(`Updating CacheCluster ${logicalId}: ${physicalId}`);
29204
29981
  try {
29205
29982
  await this.getClient().send(
@@ -29222,6 +29999,13 @@ var ElastiCacheProvider = class {
29222
29999
  this.logger.debug(`Successfully updated CacheCluster ${logicalId}`);
29223
30000
  await this.waitForClusterAvailable(physicalId);
29224
30001
  const described = await this.describeCacheCluster(physicalId);
30002
+ if (described?.ARN) {
30003
+ await this.applyTagDiff(
30004
+ described.ARN,
30005
+ previousProperties["Tags"],
30006
+ properties["Tags"]
30007
+ );
30008
+ }
29225
30009
  const attributes = {};
29226
30010
  if (described?.CacheNodes?.[0]?.Endpoint) {
29227
30011
  const endpoint = described.CacheNodes[0].Endpoint;
@@ -29286,6 +30070,45 @@ var ElastiCacheProvider = class {
29286
30070
  }
29287
30071
  }
29288
30072
  // ─── Helpers ──────────────────────────────────────────────────────
30073
+ /**
30074
+ * Apply a diff between old and new CFn-shape Tags arrays via ElastiCache's
30075
+ * `AddTagsToResource` / `RemoveTagsFromResource` APIs (keyed by
30076
+ * `ResourceName=arn`).
30077
+ */
30078
+ async applyTagDiff(arn, oldTagsRaw, newTagsRaw) {
30079
+ const toMap = (tags) => {
30080
+ const m = /* @__PURE__ */ new Map();
30081
+ for (const t of tags ?? []) {
30082
+ if (t.Key !== void 0 && t.Value !== void 0)
30083
+ m.set(t.Key, t.Value);
30084
+ }
30085
+ return m;
30086
+ };
30087
+ const oldMap = toMap(oldTagsRaw);
30088
+ const newMap = toMap(newTagsRaw);
30089
+ const tagsToAdd = [];
30090
+ for (const [k, v] of newMap) {
30091
+ if (oldMap.get(k) !== v)
30092
+ tagsToAdd.push({ Key: k, Value: v });
30093
+ }
30094
+ const tagsToRemove = [];
30095
+ for (const k of oldMap.keys()) {
30096
+ if (!newMap.has(k))
30097
+ tagsToRemove.push(k);
30098
+ }
30099
+ if (tagsToRemove.length > 0) {
30100
+ await this.getClient().send(
30101
+ new RemoveTagsFromResourceCommand3({ ResourceName: arn, TagKeys: tagsToRemove })
30102
+ );
30103
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from ElastiCache resource ${arn}`);
30104
+ }
30105
+ if (tagsToAdd.length > 0) {
30106
+ await this.getClient().send(
30107
+ new AddTagsToResourceCommand3({ ResourceName: arn, Tags: tagsToAdd })
30108
+ );
30109
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on ElastiCache resource ${arn}`);
30110
+ }
30111
+ }
29289
30112
  buildTags(properties) {
29290
30113
  if (!properties["Tags"])
29291
30114
  return [];
@@ -31613,8 +32436,8 @@ import {
31613
32436
  PutKeyPolicyCommand,
31614
32437
  EnableKeyCommand,
31615
32438
  DisableKeyCommand,
31616
- TagResourceCommand as TagResourceCommand6,
31617
- UntagResourceCommand as UntagResourceCommand6,
32439
+ TagResourceCommand as TagResourceCommand14,
32440
+ UntagResourceCommand as UntagResourceCommand14,
31618
32441
  NotFoundException as NotFoundException5
31619
32442
  } from "@aws-sdk/client-kms";
31620
32443
  var KMSProvider = class {
@@ -31799,26 +32622,11 @@ var KMSProvider = class {
31799
32622
  await this.getClient().send(new EnableKeyCommand({ KeyId: physicalId }));
31800
32623
  }
31801
32624
  }
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
- }
32625
+ await this.applyTagDiff(
32626
+ physicalId,
32627
+ previousProperties["Tags"],
32628
+ properties["Tags"]
32629
+ );
31822
32630
  const newKeyPolicy = properties["KeyPolicy"];
31823
32631
  const oldKeyPolicy = previousProperties["KeyPolicy"];
31824
32632
  const newPolicyStr = newKeyPolicy ? typeof newKeyPolicy === "string" ? newKeyPolicy : JSON.stringify(newKeyPolicy) : void 0;
@@ -31880,6 +32688,43 @@ var KMSProvider = class {
31880
32688
  );
31881
32689
  }
31882
32690
  }
32691
+ /**
32692
+ * Apply a diff between old and new CFn-shape Tags arrays via KMS's
32693
+ * `TagResource` / `UntagResource` APIs. KMS uses `{TagKey, TagValue}`
32694
+ * (NOT the standard `{Key, Value}` shape) keyed by `KeyId`.
32695
+ */
32696
+ async applyTagDiff(keyId, oldTagsRaw, newTagsRaw) {
32697
+ const toMap = (tags) => {
32698
+ const m = /* @__PURE__ */ new Map();
32699
+ for (const t of tags ?? []) {
32700
+ if (t.Key !== void 0 && t.Value !== void 0)
32701
+ m.set(t.Key, t.Value);
32702
+ }
32703
+ return m;
32704
+ };
32705
+ const oldMap = toMap(oldTagsRaw);
32706
+ const newMap = toMap(newTagsRaw);
32707
+ const tagsToAdd = [];
32708
+ for (const [k, v] of newMap) {
32709
+ if (oldMap.get(k) !== v)
32710
+ tagsToAdd.push({ TagKey: k, TagValue: v });
32711
+ }
32712
+ const tagsToRemove = [];
32713
+ for (const k of oldMap.keys()) {
32714
+ if (!newMap.has(k))
32715
+ tagsToRemove.push(k);
32716
+ }
32717
+ if (tagsToRemove.length > 0) {
32718
+ await this.getClient().send(
32719
+ new UntagResourceCommand14({ KeyId: keyId, TagKeys: tagsToRemove })
32720
+ );
32721
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from KMS Key ${keyId}`);
32722
+ }
32723
+ if (tagsToAdd.length > 0) {
32724
+ await this.getClient().send(new TagResourceCommand14({ KeyId: keyId, Tags: tagsToAdd }));
32725
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on KMS Key ${keyId}`);
32726
+ }
32727
+ }
31883
32728
  // ─── AWS::KMS::Alias ───────────────────────────────────────────────
31884
32729
  async createAlias(logicalId, resourceType, properties) {
31885
32730
  this.logger.debug(`Creating KMS Alias ${logicalId}`);
@@ -32164,6 +33009,7 @@ import {
32164
33009
  DescribeStreamCommand,
32165
33010
  UpdateShardCountCommand,
32166
33011
  AddTagsToStreamCommand,
33012
+ RemoveTagsFromStreamCommand,
32167
33013
  IncreaseStreamRetentionPeriodCommand,
32168
33014
  DecreaseStreamRetentionPeriodCommand,
32169
33015
  StartStreamEncryptionCommand,
@@ -32341,6 +33187,11 @@ var KinesisStreamProvider = class {
32341
33187
  }
32342
33188
  await this.waitForStreamActive(physicalId);
32343
33189
  }
33190
+ await this.applyTagDiff(
33191
+ physicalId,
33192
+ previousProperties["Tags"],
33193
+ properties["Tags"]
33194
+ );
32344
33195
  const newEncryption = properties["StreamEncryption"];
32345
33196
  const oldEncryption = previousProperties["StreamEncryption"];
32346
33197
  if (JSON.stringify(newEncryption) !== JSON.stringify(oldEncryption)) {
@@ -32422,6 +33273,47 @@ var KinesisStreamProvider = class {
32422
33273
  );
32423
33274
  }
32424
33275
  }
33276
+ /**
33277
+ * Apply a diff between old and new CFn-shape Tags arrays via Kinesis's
33278
+ * `AddTagsToStream` (map shape) / `RemoveTagsFromStream` (TagKeys list)
33279
+ * APIs.
33280
+ */
33281
+ async applyTagDiff(streamName, oldTagsRaw, newTagsRaw) {
33282
+ const toMap = (tags) => {
33283
+ const m = /* @__PURE__ */ new Map();
33284
+ for (const t of tags ?? []) {
33285
+ if (t.Key !== void 0 && t.Value !== void 0)
33286
+ m.set(t.Key, t.Value);
33287
+ }
33288
+ return m;
33289
+ };
33290
+ const oldMap = toMap(oldTagsRaw);
33291
+ const newMap = toMap(newTagsRaw);
33292
+ const tagsToAdd = {};
33293
+ for (const [k, v] of newMap) {
33294
+ if (oldMap.get(k) !== v)
33295
+ tagsToAdd[k] = v;
33296
+ }
33297
+ const tagsToRemove = [];
33298
+ for (const k of oldMap.keys()) {
33299
+ if (!newMap.has(k))
33300
+ tagsToRemove.push(k);
33301
+ }
33302
+ if (tagsToRemove.length > 0) {
33303
+ await this.getClient().send(
33304
+ new RemoveTagsFromStreamCommand({ StreamName: streamName, TagKeys: tagsToRemove })
33305
+ );
33306
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from Kinesis stream ${streamName}`);
33307
+ }
33308
+ if (Object.keys(tagsToAdd).length > 0) {
33309
+ await this.getClient().send(
33310
+ new AddTagsToStreamCommand({ StreamName: streamName, Tags: tagsToAdd })
33311
+ );
33312
+ this.logger.debug(
33313
+ `Added/updated ${Object.keys(tagsToAdd).length} tag(s) on Kinesis stream ${streamName}`
33314
+ );
33315
+ }
33316
+ }
32425
33317
  /**
32426
33318
  * Adopt an existing Kinesis stream into cdkd state.
32427
33319
  *
@@ -33782,6 +34674,8 @@ import {
33782
34674
  GetEventSelectorsCommand,
33783
34675
  ListTrailsCommand,
33784
34676
  ListTagsCommand as ListTagsCommand3,
34677
+ AddTagsCommand as AddTagsCommand2,
34678
+ RemoveTagsCommand as RemoveTagsCommand2,
33785
34679
  TrailNotFoundException
33786
34680
  } from "@aws-sdk/client-cloudtrail";
33787
34681
  var CloudTrailProvider = class {
@@ -33965,6 +34859,11 @@ var CloudTrailProvider = class {
33965
34859
  await this.getClient().send(new StartLoggingCommand({ Name: physicalId }));
33966
34860
  }
33967
34861
  }
34862
+ await this.applyTagDiff(
34863
+ physicalId,
34864
+ previousProperties["Tags"],
34865
+ properties["Tags"]
34866
+ );
33968
34867
  this.logger.debug(`Successfully updated CloudTrail Trail ${logicalId}`);
33969
34868
  return { physicalId, wasReplaced: false };
33970
34869
  } catch (error) {
@@ -34013,6 +34912,46 @@ var CloudTrailProvider = class {
34013
34912
  getAttribute(_physicalId, _resourceType, attributeName) {
34014
34913
  return Promise.resolve(attributeName);
34015
34914
  }
34915
+ /**
34916
+ * Apply a diff between old and new CFn-shape Tags arrays via CloudTrail's
34917
+ * `AddTags` / `RemoveTags` APIs. Note: CloudTrail's `RemoveTags` takes
34918
+ * full `{Key, Value}` objects in `TagsList` (NOT just keys), unlike most
34919
+ * other AWS services.
34920
+ */
34921
+ async applyTagDiff(trailArn, oldTagsRaw, newTagsRaw) {
34922
+ const toMap = (tags) => {
34923
+ const m = /* @__PURE__ */ new Map();
34924
+ for (const t of tags ?? []) {
34925
+ if (t.Key !== void 0 && t.Value !== void 0)
34926
+ m.set(t.Key, t.Value);
34927
+ }
34928
+ return m;
34929
+ };
34930
+ const oldMap = toMap(oldTagsRaw);
34931
+ const newMap = toMap(newTagsRaw);
34932
+ const tagsToAdd = [];
34933
+ for (const [k, v] of newMap) {
34934
+ if (oldMap.get(k) !== v)
34935
+ tagsToAdd.push({ Key: k, Value: v });
34936
+ }
34937
+ const tagsToRemove = [];
34938
+ for (const [k, v] of oldMap) {
34939
+ if (!newMap.has(k))
34940
+ tagsToRemove.push({ Key: k, Value: v });
34941
+ }
34942
+ if (tagsToRemove.length > 0) {
34943
+ await this.getClient().send(
34944
+ new RemoveTagsCommand2({ ResourceId: trailArn, TagsList: tagsToRemove })
34945
+ );
34946
+ this.logger.debug(`Removed ${tagsToRemove.length} tag(s) from CloudTrail Trail ${trailArn}`);
34947
+ }
34948
+ if (tagsToAdd.length > 0) {
34949
+ await this.getClient().send(
34950
+ new AddTagsCommand2({ ResourceId: trailArn, TagsList: tagsToAdd })
34951
+ );
34952
+ this.logger.debug(`Added/updated ${tagsToAdd.length} tag(s) on CloudTrail Trail ${trailArn}`);
34953
+ }
34954
+ }
34016
34955
  /**
34017
34956
  * Adopt an existing CloudTrail trail into cdkd state.
34018
34957
  *
@@ -35836,7 +36775,7 @@ import {
35836
36775
  SetRepositoryPolicyCommand,
35837
36776
  PutImageScanningConfigurationCommand,
35838
36777
  PutImageTagMutabilityCommand,
35839
- TagResourceCommand as TagResourceCommand7,
36778
+ TagResourceCommand as TagResourceCommand15,
35840
36779
  ListTagsForResourceCommand as ListTagsForResourceCommand18,
35841
36780
  LifecyclePolicyNotFoundException,
35842
36781
  RepositoryNotFoundException
@@ -36002,7 +36941,7 @@ var ECRProvider = class {
36002
36941
  const repoArn = describeResponse.repositories?.[0]?.repositoryArn;
36003
36942
  if (repoArn && newTags) {
36004
36943
  await this.getClient().send(
36005
- new TagResourceCommand7({
36944
+ new TagResourceCommand15({
36006
36945
  resourceArn: repoArn,
36007
36946
  tags: newTags
36008
36947
  })
@@ -42566,7 +43505,7 @@ function reorderArgs(argv) {
42566
43505
  }
42567
43506
  async function main() {
42568
43507
  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");
43508
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.50.6");
42570
43509
  program.addCommand(createBootstrapCommand());
42571
43510
  program.addCommand(createSynthCommand());
42572
43511
  program.addCommand(createListCommand());