@go-to-k/cdkd 0.50.4 → 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
  */
@@ -12857,13 +12993,11 @@ var SQSQueueProvider = class {
12857
12993
  if (v !== void 0)
12858
12994
  result[key] = v === "true";
12859
12995
  }
12860
- const str = [
12861
- "KmsMasterKeyId",
12862
- "DeduplicationScope",
12863
- "FifoThroughputLimit"
12864
- ];
12865
- for (const key of str) {
12866
- result[key] = attributes[key] ?? "";
12996
+ result["KmsMasterKeyId"] = attributes["KmsMasterKeyId"] ?? "";
12997
+ const isFifo = attributes["FifoQueue"] === "true";
12998
+ if (isFifo) {
12999
+ result["DeduplicationScope"] = attributes["DeduplicationScope"] ?? "";
13000
+ result["FifoThroughputLimit"] = attributes["FifoThroughputLimit"] ?? "";
12867
13001
  }
12868
13002
  if (attributes["RedrivePolicy"]) {
12869
13003
  try {
@@ -13544,16 +13678,14 @@ var SNSTopicProvider = class {
13544
13678
  if (v !== void 0)
13545
13679
  result[key] = v === "true";
13546
13680
  }
13547
- const str = [
13548
- "DisplayName",
13549
- "KmsMasterKeyId",
13550
- "TracingConfig",
13551
- "SignatureVersion",
13552
- "FifoThroughputScope"
13553
- ];
13681
+ const str = ["DisplayName", "KmsMasterKeyId", "TracingConfig", "SignatureVersion"];
13554
13682
  for (const key of str) {
13555
13683
  result[key] = attrs[key] ?? "";
13556
13684
  }
13685
+ const isFifo = attrs["FifoTopic"] === "true";
13686
+ if (isFifo) {
13687
+ result["FifoThroughputScope"] = attrs["FifoThroughputScope"] ?? "";
13688
+ }
13557
13689
  for (const key of ["ArchivePolicy", "DataProtectionPolicy"]) {
13558
13690
  const v = attrs[key];
13559
13691
  if (v) {
@@ -14084,6 +14216,8 @@ import {
14084
14216
  GetFunctionCommand,
14085
14217
  ListFunctionsCommand,
14086
14218
  ListTagsCommand,
14219
+ TagResourceCommand as TagResourceCommand2,
14220
+ UntagResourceCommand as UntagResourceCommand2,
14087
14221
  ResourceNotFoundException,
14088
14222
  waitUntilFunctionUpdatedV2 as waitUntilFunctionUpdatedV22
14089
14223
  } from "@aws-sdk/client-lambda";
@@ -14301,11 +14435,17 @@ var LambdaFunctionProvider = class {
14301
14435
  const getResponse = await this.lambdaClient.send(
14302
14436
  new GetFunctionCommand({ FunctionName: physicalId })
14303
14437
  );
14438
+ const functionArn = getResponse.Configuration?.FunctionArn;
14439
+ await this.applyTagDiff(
14440
+ functionArn,
14441
+ previousProperties["Tags"],
14442
+ properties["Tags"]
14443
+ );
14304
14444
  return {
14305
14445
  physicalId,
14306
14446
  wasReplaced: false,
14307
14447
  attributes: {
14308
- Arn: getResponse.Configuration?.FunctionArn,
14448
+ Arn: functionArn,
14309
14449
  FunctionName: getResponse.Configuration?.FunctionName
14310
14450
  }
14311
14451
  };
@@ -14497,6 +14637,53 @@ var LambdaFunctionProvider = class {
14497
14637
  * one resource type that actually needs it preserves the bug fix
14498
14638
  * without paying the whole-stack tax.
14499
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
+ }
14500
14687
  async waitForFunctionUpdated(logicalId, resourceType, functionName) {
14501
14688
  try {
14502
14689
  await waitUntilFunctionUpdatedV22(
@@ -14831,14 +15018,11 @@ var LambdaFunctionProvider = class {
14831
15018
  if (cfg.EphemeralStorage?.Size !== void 0) {
14832
15019
  result["EphemeralStorage"] = { Size: cfg.EphemeralStorage.Size };
14833
15020
  }
14834
- const vpc = {
15021
+ result["VpcConfig"] = {
14835
15022
  SubnetIds: cfg.VpcConfig?.SubnetIds ? [...cfg.VpcConfig.SubnetIds] : [],
14836
- SecurityGroupIds: cfg.VpcConfig?.SecurityGroupIds ? [...cfg.VpcConfig.SecurityGroupIds] : []
15023
+ SecurityGroupIds: cfg.VpcConfig?.SecurityGroupIds ? [...cfg.VpcConfig.SecurityGroupIds] : [],
15024
+ Ipv6AllowedForDualStack: cfg.VpcConfig?.Ipv6AllowedForDualStack ?? false
14837
15025
  };
14838
- if (cfg.VpcConfig?.Ipv6AllowedForDualStack !== void 0) {
14839
- vpc["Ipv6AllowedForDualStack"] = cfg.VpcConfig.Ipv6AllowedForDualStack;
14840
- }
14841
- result["VpcConfig"] = vpc;
14842
15026
  const tags = normalizeAwsTagsToCfn(resp.Tags);
14843
15027
  result["Tags"] = tags;
14844
15028
  return result;
@@ -15472,6 +15656,8 @@ import {
15472
15656
  DeleteEventSourceMappingCommand,
15473
15657
  UpdateEventSourceMappingCommand,
15474
15658
  GetEventSourceMappingCommand,
15659
+ TagResourceCommand as TagResourceCommand3,
15660
+ UntagResourceCommand as UntagResourceCommand3,
15475
15661
  ResourceNotFoundException as ResourceNotFoundException4
15476
15662
  } from "@aws-sdk/client-lambda";
15477
15663
  init_aws_clients();
@@ -15595,7 +15781,7 @@ var LambdaEventSourceMappingProvider = class {
15595
15781
  /**
15596
15782
  * Update a Lambda Event Source Mapping
15597
15783
  */
15598
- async update(logicalId, physicalId, _resourceType, properties, _previousProperties) {
15784
+ async update(logicalId, physicalId, _resourceType, properties, previousProperties) {
15599
15785
  this.logger.debug(`Updating event source mapping ${logicalId}: ${physicalId}`);
15600
15786
  const updateParams = {
15601
15787
  UUID: physicalId,
@@ -15629,7 +15815,17 @@ var LambdaEventSourceMappingProvider = class {
15629
15815
  updateParams.ScalingConfig = properties["ScalingConfig"];
15630
15816
  if (properties["DocumentDBEventSourceConfig"])
15631
15817
  updateParams.DocumentDBEventSourceConfig = properties["DocumentDBEventSourceConfig"];
15632
- 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
+ }
15633
15829
  this.logger.debug(`Successfully updated event source mapping ${logicalId}`);
15634
15830
  return {
15635
15831
  physicalId,
@@ -15639,6 +15835,46 @@ var LambdaEventSourceMappingProvider = class {
15639
15835
  }
15640
15836
  };
15641
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
+ }
15642
15878
  /**
15643
15879
  * Delete a Lambda Event Source Mapping
15644
15880
  */
@@ -16072,6 +16308,8 @@ import {
16072
16308
  DescribeTableCommand as DescribeTableCommand2,
16073
16309
  ListTablesCommand,
16074
16310
  ListTagsOfResourceCommand,
16311
+ TagResourceCommand as TagResourceCommand4,
16312
+ UntagResourceCommand as UntagResourceCommand4,
16075
16313
  ResourceNotFoundException as ResourceNotFoundException6
16076
16314
  } from "@aws-sdk/client-dynamodb";
16077
16315
  init_aws_clients();
@@ -16197,13 +16435,20 @@ var DynamoDBTableProvider = class {
16197
16435
  * For immutable property changes (KeySchema, etc.), the deployment layer
16198
16436
  * handles replacement via DELETE + CREATE.
16199
16437
  */
16200
- async update(logicalId, physicalId, resourceType, _properties, _previousProperties) {
16438
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
16201
16439
  this.logger.debug(`Updating DynamoDB table ${logicalId}: ${physicalId}`);
16202
16440
  try {
16203
16441
  const response = await this.dynamoDBClient.send(
16204
16442
  new DescribeTableCommand2({ TableName: physicalId })
16205
16443
  );
16206
16444
  const table = response.Table;
16445
+ if (table?.TableArn) {
16446
+ await this.applyTagDiff(
16447
+ table.TableArn,
16448
+ previousProperties["Tags"],
16449
+ properties["Tags"]
16450
+ );
16451
+ }
16207
16452
  return {
16208
16453
  physicalId,
16209
16454
  wasReplaced: false,
@@ -16256,6 +16501,45 @@ var DynamoDBTableProvider = class {
16256
16501
  );
16257
16502
  }
16258
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
+ }
16259
16543
  /**
16260
16544
  * Poll DescribeTable until the table reaches ACTIVE status
16261
16545
  *
@@ -16475,8 +16759,8 @@ import {
16475
16759
  ListTagsForResourceCommand as ListTagsForResourceCommand2,
16476
16760
  PutRetentionPolicyCommand,
16477
16761
  DeleteRetentionPolicyCommand,
16478
- TagResourceCommand as TagResourceCommand2,
16479
- UntagResourceCommand as UntagResourceCommand2,
16762
+ TagResourceCommand as TagResourceCommand5,
16763
+ UntagResourceCommand as UntagResourceCommand5,
16480
16764
  PutDataProtectionPolicyCommand,
16481
16765
  DeleteDataProtectionPolicyCommand,
16482
16766
  ResourceNotFoundException as ResourceNotFoundException7,
@@ -16626,7 +16910,7 @@ var LogsLogGroupProvider = class {
16626
16910
  if (oldTags && oldTags.length > 0) {
16627
16911
  const oldTagKeys = oldTags.map((t) => t.Key);
16628
16912
  await this.logsClient.send(
16629
- new UntagResourceCommand2({
16913
+ new UntagResourceCommand5({
16630
16914
  resourceArn: arn2,
16631
16915
  tagKeys: oldTagKeys
16632
16916
  })
@@ -16635,7 +16919,7 @@ var LogsLogGroupProvider = class {
16635
16919
  if (newTags && newTags.length > 0) {
16636
16920
  const tagsMap = Object.fromEntries(newTags.map((t) => [t.Key, t.Value]));
16637
16921
  await this.logsClient.send(
16638
- new TagResourceCommand2({
16922
+ new TagResourceCommand5({
16639
16923
  resourceArn: arn2,
16640
16924
  tags: tagsMap
16641
16925
  })
@@ -16835,7 +17119,9 @@ import {
16835
17119
  PutMetricAlarmCommand,
16836
17120
  DeleteAlarmsCommand,
16837
17121
  DescribeAlarmsCommand,
16838
- ListTagsForResourceCommand as ListTagsForResourceCommand3
17122
+ ListTagsForResourceCommand as ListTagsForResourceCommand3,
17123
+ TagResourceCommand as TagResourceCommand6,
17124
+ UntagResourceCommand as UntagResourceCommand6
16839
17125
  } from "@aws-sdk/client-cloudwatch";
16840
17126
  init_aws_clients();
16841
17127
  var CloudWatchAlarmProvider = class {
@@ -16904,7 +17190,7 @@ var CloudWatchAlarmProvider = class {
16904
17190
  *
16905
17191
  * PutMetricAlarm is idempotent - calling it with the same alarm name updates the alarm.
16906
17192
  */
16907
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
17193
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
16908
17194
  this.logger.debug(`Updating CloudWatch alarm ${logicalId}: ${physicalId}`);
16909
17195
  try {
16910
17196
  await this.cloudWatchClient.send(
@@ -16912,6 +17198,11 @@ var CloudWatchAlarmProvider = class {
16912
17198
  );
16913
17199
  this.logger.debug(`Successfully updated CloudWatch alarm ${logicalId}`);
16914
17200
  const alarmArn = await this.getAlarmArn(physicalId);
17201
+ await this.applyTagDiff(
17202
+ alarmArn,
17203
+ previousProperties["Tags"],
17204
+ properties["Tags"]
17205
+ );
16915
17206
  return {
16916
17207
  physicalId,
16917
17208
  wasReplaced: false,
@@ -16996,6 +17287,44 @@ var CloudWatchAlarmProvider = class {
16996
17287
  return `arn:aws:cloudwatch:*:*:alarm:${alarmName}`;
16997
17288
  }
16998
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
+ }
16999
17328
  /**
17000
17329
  * Build PutMetricAlarm parameters from CDK properties
17001
17330
  */
@@ -17188,8 +17517,8 @@ import {
17188
17517
  DescribeSecretCommand,
17189
17518
  ListSecretsCommand,
17190
17519
  UpdateSecretCommand,
17191
- TagResourceCommand as TagResourceCommand3,
17192
- UntagResourceCommand as UntagResourceCommand3,
17520
+ TagResourceCommand as TagResourceCommand7,
17521
+ UntagResourceCommand as UntagResourceCommand7,
17193
17522
  ReplicateSecretToRegionsCommand,
17194
17523
  RemoveRegionsFromReplicationCommand,
17195
17524
  ResourceNotFoundException as ResourceNotFoundException8
@@ -17302,7 +17631,7 @@ var SecretsManagerSecretProvider = class {
17302
17631
  const oldTagKeys = oldTags.map((t) => t.Key).filter((k) => !!k);
17303
17632
  if (oldTagKeys.length > 0) {
17304
17633
  await this.smClient.send(
17305
- new UntagResourceCommand3({
17634
+ new UntagResourceCommand7({
17306
17635
  SecretId: physicalId,
17307
17636
  TagKeys: oldTagKeys
17308
17637
  })
@@ -17311,7 +17640,7 @@ var SecretsManagerSecretProvider = class {
17311
17640
  }
17312
17641
  if (newTags && newTags.length > 0) {
17313
17642
  await this.smClient.send(
17314
- new TagResourceCommand3({
17643
+ new TagResourceCommand7({
17315
17644
  SecretId: physicalId,
17316
17645
  Tags: newTags
17317
17646
  })
@@ -17915,8 +18244,8 @@ import {
17915
18244
  ListRulesCommand,
17916
18245
  ListTargetsByRuleCommand,
17917
18246
  ListTagsForResourceCommand as ListTagsForResourceCommand5,
17918
- TagResourceCommand as TagResourceCommand4,
17919
- UntagResourceCommand as UntagResourceCommand4,
18247
+ TagResourceCommand as TagResourceCommand8,
18248
+ UntagResourceCommand as UntagResourceCommand8,
17920
18249
  ResourceNotFoundException as ResourceNotFoundException9
17921
18250
  } from "@aws-sdk/client-eventbridge";
17922
18251
  init_aws_clients();
@@ -18069,7 +18398,7 @@ var EventBridgeRuleProvider = class {
18069
18398
  const oldTagKeys = oldTags.map((t) => t.Key).filter((k) => !!k);
18070
18399
  if (oldTagKeys.length > 0) {
18071
18400
  await this.eventBridgeClient.send(
18072
- new UntagResourceCommand4({
18401
+ new UntagResourceCommand8({
18073
18402
  ResourceARN: ruleArn,
18074
18403
  TagKeys: oldTagKeys
18075
18404
  })
@@ -18078,7 +18407,7 @@ var EventBridgeRuleProvider = class {
18078
18407
  }
18079
18408
  if (newTags && newTags.length > 0) {
18080
18409
  await this.eventBridgeClient.send(
18081
- new TagResourceCommand4({
18410
+ new TagResourceCommand8({
18082
18411
  ResourceARN: ruleArn,
18083
18412
  Tags: newTags
18084
18413
  })
@@ -18385,8 +18714,8 @@ import {
18385
18714
  RemoveTargetsCommand as RemoveTargetsCommand2,
18386
18715
  DeleteRuleCommand as DeleteRuleCommand2,
18387
18716
  ListTargetsByRuleCommand as ListTargetsByRuleCommand2,
18388
- TagResourceCommand as TagResourceCommand5,
18389
- UntagResourceCommand as UntagResourceCommand5,
18717
+ TagResourceCommand as TagResourceCommand9,
18718
+ UntagResourceCommand as UntagResourceCommand9,
18390
18719
  ResourceNotFoundException as ResourceNotFoundException10
18391
18720
  } from "@aws-sdk/client-eventbridge";
18392
18721
  init_aws_clients();
@@ -18499,7 +18828,7 @@ var EventBridgeBusProvider = class {
18499
18828
  const oldTagKeys = oldTags.map((t) => t.Key).filter((k) => !!k);
18500
18829
  if (oldTagKeys.length > 0) {
18501
18830
  await this.eventBridgeClient.send(
18502
- new UntagResourceCommand5({
18831
+ new UntagResourceCommand9({
18503
18832
  ResourceARN: busArn,
18504
18833
  TagKeys: oldTagKeys
18505
18834
  })
@@ -18508,7 +18837,7 @@ var EventBridgeBusProvider = class {
18508
18837
  }
18509
18838
  if (newTags && newTags.length > 0) {
18510
18839
  await this.eventBridgeClient.send(
18511
- new TagResourceCommand5({
18840
+ new TagResourceCommand9({
18512
18841
  ResourceARN: busArn,
18513
18842
  Tags: newTags
18514
18843
  })
@@ -18760,6 +19089,7 @@ import {
18760
19089
  AuthorizeSecurityGroupEgressCommand,
18761
19090
  RevokeSecurityGroupEgressCommand,
18762
19091
  CreateTagsCommand,
19092
+ DeleteTagsCommand,
18763
19093
  DescribeSubnetsCommand as DescribeSubnetsCommand2,
18764
19094
  DescribeSecurityGroupsCommand as DescribeSecurityGroupsCommand2,
18765
19095
  RunInstancesCommand,
@@ -18924,7 +19254,7 @@ var EC2Provider = class {
18924
19254
  async update(logicalId, physicalId, resourceType, properties, previousProperties) {
18925
19255
  switch (resourceType) {
18926
19256
  case "AWS::EC2::VPC":
18927
- return this.updateVpc(logicalId, physicalId, resourceType, properties);
19257
+ return this.updateVpc(logicalId, physicalId, resourceType, properties, previousProperties);
18928
19258
  case "AWS::EC2::Subnet":
18929
19259
  return this.updateSubnet(logicalId, physicalId);
18930
19260
  case "AWS::EC2::InternetGateway":
@@ -18962,7 +19292,13 @@ var EC2Provider = class {
18962
19292
  previousProperties
18963
19293
  );
18964
19294
  case "AWS::EC2::Instance":
18965
- return this.updateInstance(logicalId, physicalId, resourceType, properties);
19295
+ return this.updateInstance(
19296
+ logicalId,
19297
+ physicalId,
19298
+ resourceType,
19299
+ properties,
19300
+ previousProperties
19301
+ );
18966
19302
  case "AWS::EC2::NetworkAcl":
18967
19303
  case "AWS::EC2::NetworkAclEntry":
18968
19304
  case "AWS::EC2::SubnetNetworkAclAssociation":
@@ -19108,7 +19444,7 @@ var EC2Provider = class {
19108
19444
  );
19109
19445
  }
19110
19446
  }
19111
- async updateVpc(logicalId, physicalId, resourceType, properties) {
19447
+ async updateVpc(logicalId, physicalId, resourceType, properties, previousProperties) {
19112
19448
  this.logger.debug(`Updating VPC ${logicalId}: ${physicalId}`);
19113
19449
  try {
19114
19450
  if (properties["EnableDnsHostnames"] !== void 0) {
@@ -19129,7 +19465,11 @@ var EC2Provider = class {
19129
19465
  })
19130
19466
  );
19131
19467
  }
19132
- await this.applyTags(physicalId, properties, logicalId);
19468
+ await this.applyTagDiff(
19469
+ physicalId,
19470
+ previousProperties["Tags"],
19471
+ properties["Tags"]
19472
+ );
19133
19473
  this.logger.debug(`Successfully updated VPC ${logicalId}`);
19134
19474
  return {
19135
19475
  physicalId,
@@ -19999,7 +20339,11 @@ var EC2Provider = class {
19999
20339
  async updateSecurityGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
20000
20340
  this.logger.debug(`Updating SecurityGroup ${logicalId}: ${physicalId}`);
20001
20341
  try {
20002
- await this.applyTags(physicalId, properties, logicalId);
20342
+ await this.applyTagDiff(
20343
+ physicalId,
20344
+ previousProperties["Tags"],
20345
+ properties["Tags"]
20346
+ );
20003
20347
  await this.applySecurityGroupRuleDiff(
20004
20348
  physicalId,
20005
20349
  previousProperties["SecurityGroupIngress"] ?? [],
@@ -20327,10 +20671,14 @@ var EC2Provider = class {
20327
20671
  );
20328
20672
  }
20329
20673
  }
20330
- async updateInstance(logicalId, physicalId, resourceType, _properties) {
20674
+ async updateInstance(logicalId, physicalId, resourceType, properties, previousProperties) {
20331
20675
  this.logger.debug(`Updating EC2 Instance ${logicalId}: ${physicalId}`);
20332
20676
  try {
20333
- await this.applyTags(physicalId, _properties, logicalId);
20677
+ await this.applyTagDiff(
20678
+ physicalId,
20679
+ previousProperties["Tags"],
20680
+ properties["Tags"]
20681
+ );
20334
20682
  const describeResponse = await this.ec2Client.send(
20335
20683
  new DescribeInstancesCommand({ InstanceIds: [physicalId] })
20336
20684
  );
@@ -20805,7 +21153,10 @@ var EC2Provider = class {
20805
21153
  }
20806
21154
  }
20807
21155
  /**
20808
- * 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.
20809
21160
  */
20810
21161
  async applyTags(resourceId, properties, logicalId) {
20811
21162
  const tags = properties["Tags"];
@@ -20825,6 +21176,64 @@ var EC2Provider = class {
20825
21176
  }
20826
21177
  }
20827
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
+ }
20828
21237
  /**
20829
21238
  * Check if an error indicates the resource was not found
20830
21239
  */
@@ -21171,6 +21580,8 @@ import {
21171
21580
  CreateAuthorizerCommand,
21172
21581
  DeleteAuthorizerCommand,
21173
21582
  GetAuthorizerCommand,
21583
+ TagResourceCommand as TagResourceCommand10,
21584
+ UntagResourceCommand as UntagResourceCommand10,
21174
21585
  NotFoundException as NotFoundException3
21175
21586
  } from "@aws-sdk/client-api-gateway";
21176
21587
  init_aws_clients();
@@ -21894,24 +22305,24 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
21894
22305
  value: description ?? ""
21895
22306
  });
21896
22307
  }
21897
- if (patchOperations.length === 0) {
21898
- this.logger.debug(`No changes detected for API Gateway Stage ${logicalId}`);
21899
- return {
21900
- physicalId,
21901
- wasReplaced: false,
21902
- attributes: {
21903
- StageName: physicalId
21904
- }
21905
- };
21906
- }
21907
22308
  try {
21908
- await this.apiGatewayClient.send(
21909
- new UpdateStageCommand({
21910
- restApiId,
21911
- stageName: physicalId,
21912
- patchOperations
21913
- })
21914
- );
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
+ }
21915
22326
  this.logger.debug(`Successfully updated API Gateway Stage ${logicalId}`);
21916
22327
  return {
21917
22328
  physicalId,
@@ -22144,7 +22555,64 @@ var ApiGatewayProvider = class _ApiGatewayProvider {
22144
22555
  if (attributeName === "HttpMethod")
22145
22556
  return Promise.resolve(parts[2]);
22146
22557
  }
22147
- return Promise.resolve(void 0);
22558
+ return Promise.resolve(void 0);
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
+ }
22148
22616
  }
22149
22617
  /**
22150
22618
  * Sleep for specified milliseconds
@@ -24179,6 +24647,8 @@ import {
24179
24647
  DescribeStateMachineCommand,
24180
24648
  ListStateMachinesCommand,
24181
24649
  ListTagsForResourceCommand as ListTagsForResourceCommand8,
24650
+ TagResourceCommand as TagResourceCommand11,
24651
+ UntagResourceCommand as UntagResourceCommand11,
24182
24652
  StateMachineDoesNotExist
24183
24653
  } from "@aws-sdk/client-sfn";
24184
24654
  var StepFunctionsProvider = class {
@@ -24281,7 +24751,7 @@ var StepFunctionsProvider = class {
24281
24751
  /**
24282
24752
  * Update a Step Functions state machine
24283
24753
  */
24284
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
24754
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
24285
24755
  this.logger.debug(`Updating Step Functions state machine ${logicalId}: ${physicalId}`);
24286
24756
  try {
24287
24757
  const definitionString = this.buildDefinitionString(properties);
@@ -24305,6 +24775,11 @@ var StepFunctionsProvider = class {
24305
24775
  })
24306
24776
  );
24307
24777
  this.logger.debug(`Updated Step Functions state machine ${physicalId}`);
24778
+ await this.applyTagDiff(
24779
+ physicalId,
24780
+ previousProperties["Tags"],
24781
+ properties["Tags"]
24782
+ );
24308
24783
  const describeResponse = await this.getClient().send(
24309
24784
  new DescribeStateMachineCommand({ stateMachineArn: physicalId })
24310
24785
  );
@@ -24506,6 +24981,49 @@ var StepFunctionsProvider = class {
24506
24981
  } while (nextToken);
24507
24982
  return null;
24508
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
+ }
24509
25027
  /**
24510
25028
  * Match SFN's lowercase `key`/`value` tag shape against the CDK path.
24511
25029
  */
@@ -24557,7 +25075,9 @@ import {
24557
25075
  DescribeServicesCommand,
24558
25076
  ListClustersCommand,
24559
25077
  ListServicesCommand,
24560
- ListTagsForResourceCommand as ListTagsForResourceCommand9
25078
+ ListTagsForResourceCommand as ListTagsForResourceCommand9,
25079
+ TagResourceCommand as TagResourceCommand12,
25080
+ UntagResourceCommand as UntagResourceCommand12
24561
25081
  } from "@aws-sdk/client-ecs";
24562
25082
  function convertTags(tags) {
24563
25083
  if (!tags || tags.length === 0)
@@ -24652,7 +25172,13 @@ var ECSProvider = class {
24652
25172
  async update(logicalId, physicalId, resourceType, properties, previousProperties) {
24653
25173
  switch (resourceType) {
24654
25174
  case "AWS::ECS::Cluster":
24655
- return this.updateCluster(logicalId, physicalId, resourceType, properties);
25175
+ return this.updateCluster(
25176
+ logicalId,
25177
+ physicalId,
25178
+ resourceType,
25179
+ properties,
25180
+ previousProperties
25181
+ );
24656
25182
  case "AWS::ECS::TaskDefinition":
24657
25183
  return this.updateTaskDefinition(logicalId, physicalId, resourceType, properties);
24658
25184
  case "AWS::ECS::Service":
@@ -24744,7 +25270,7 @@ var ECSProvider = class {
24744
25270
  );
24745
25271
  }
24746
25272
  }
24747
- async updateCluster(logicalId, physicalId, resourceType, properties) {
25273
+ async updateCluster(logicalId, physicalId, resourceType, properties, previousProperties) {
24748
25274
  this.logger.debug(`Updating ECS cluster ${logicalId}: ${physicalId}`);
24749
25275
  const client = this.getClient();
24750
25276
  try {
@@ -24762,6 +25288,13 @@ var ECSProvider = class {
24762
25288
  new DescribeClustersCommand({ clusters: [physicalId] })
24763
25289
  );
24764
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
+ }
24765
25298
  return {
24766
25299
  physicalId,
24767
25300
  wasReplaced: false,
@@ -25030,6 +25563,13 @@ var ECSProvider = class {
25030
25563
  })
25031
25564
  );
25032
25565
  const service = response.service;
25566
+ if (service?.serviceArn) {
25567
+ await this.applyTagDiff(
25568
+ service.serviceArn,
25569
+ previousProperties["Tags"],
25570
+ properties["Tags"]
25571
+ );
25572
+ }
25033
25573
  return {
25034
25574
  physicalId,
25035
25575
  wasReplaced: false,
@@ -25131,6 +25671,42 @@ var ECSProvider = class {
25131
25671
  }
25132
25672
  }
25133
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
+ }
25134
25710
  /**
25135
25711
  * Convert CFn ContainerDefinitions to ECS SDK format.
25136
25712
  * CFn uses PascalCase, ECS SDK uses camelCase.
@@ -25578,6 +26154,8 @@ import {
25578
26154
  ModifyTargetGroupCommand,
25579
26155
  DescribeTargetGroupsCommand,
25580
26156
  DescribeTagsCommand,
26157
+ AddTagsCommand,
26158
+ RemoveTagsCommand,
25581
26159
  CreateListenerCommand,
25582
26160
  DeleteListenerCommand,
25583
26161
  ModifyListenerCommand,
@@ -25660,14 +26238,26 @@ var ELBv2Provider = class {
25660
26238
  );
25661
26239
  }
25662
26240
  }
25663
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
26241
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
25664
26242
  switch (resourceType) {
25665
26243
  case "AWS::ElasticLoadBalancingV2::LoadBalancer":
25666
26244
  return this.updateLoadBalancer(logicalId, physicalId, resourceType, properties);
25667
26245
  case "AWS::ElasticLoadBalancingV2::TargetGroup":
25668
- return this.updateTargetGroup(logicalId, physicalId, resourceType, properties);
26246
+ return this.updateTargetGroup(
26247
+ logicalId,
26248
+ physicalId,
26249
+ resourceType,
26250
+ properties,
26251
+ previousProperties
26252
+ );
25669
26253
  case "AWS::ElasticLoadBalancingV2::Listener":
25670
- return this.updateListener(logicalId, physicalId, resourceType, properties);
26254
+ return this.updateListener(
26255
+ logicalId,
26256
+ physicalId,
26257
+ resourceType,
26258
+ properties,
26259
+ previousProperties
26260
+ );
25671
26261
  default:
25672
26262
  throw new ProvisioningError(
25673
26263
  `Unsupported resource type: ${resourceType}`,
@@ -25846,7 +26436,7 @@ var ELBv2Provider = class {
25846
26436
  );
25847
26437
  }
25848
26438
  }
25849
- async updateTargetGroup(logicalId, physicalId, resourceType, properties) {
26439
+ async updateTargetGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
25850
26440
  this.logger.debug(`Updating TargetGroup ${logicalId}: ${physicalId}`);
25851
26441
  try {
25852
26442
  const matcher = properties["Matcher"];
@@ -25868,6 +26458,11 @@ var ELBv2Provider = class {
25868
26458
  new DescribeTargetGroupsCommand({ TargetGroupArns: [physicalId] })
25869
26459
  );
25870
26460
  const tg = describeResponse.TargetGroups?.[0];
26461
+ await this.applyTagDiff(
26462
+ physicalId,
26463
+ previousProperties["Tags"],
26464
+ properties["Tags"]
26465
+ );
25871
26466
  this.logger.debug(`Successfully updated TargetGroup ${logicalId}`);
25872
26467
  return {
25873
26468
  physicalId,
@@ -25961,7 +26556,7 @@ var ELBv2Provider = class {
25961
26556
  );
25962
26557
  }
25963
26558
  }
25964
- async updateListener(logicalId, physicalId, resourceType, properties) {
26559
+ async updateListener(logicalId, physicalId, resourceType, properties, previousProperties) {
25965
26560
  this.logger.debug(`Updating Listener ${logicalId}: ${physicalId}`);
25966
26561
  try {
25967
26562
  const defaultActions = this.convertActions(
@@ -25980,6 +26575,11 @@ var ELBv2Provider = class {
25980
26575
  ...certificates && { Certificates: certificates }
25981
26576
  })
25982
26577
  );
26578
+ await this.applyTagDiff(
26579
+ physicalId,
26580
+ previousProperties["Tags"],
26581
+ properties["Tags"]
26582
+ );
25983
26583
  this.logger.debug(`Successfully updated Listener ${logicalId}`);
25984
26584
  return {
25985
26585
  physicalId,
@@ -26037,6 +26637,44 @@ var ELBv2Provider = class {
26037
26637
  return [];
26038
26638
  return properties["Tags"];
26039
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
+ }
26040
26678
  /**
26041
26679
  * Convert CDK DefaultActions to ELBv2 API Action format
26042
26680
  * CDK uses PascalCase property names matching the ELBv2 API, so pass through.
@@ -26351,7 +26989,9 @@ import {
26351
26989
  DeleteDBSubnetGroupCommand,
26352
26990
  DescribeDBSubnetGroupsCommand,
26353
26991
  ModifyDBSubnetGroupCommand,
26354
- ListTagsForResourceCommand as ListTagsForResourceCommand10
26992
+ ListTagsForResourceCommand as ListTagsForResourceCommand10,
26993
+ AddTagsToResourceCommand as AddTagsToResourceCommand2,
26994
+ RemoveTagsFromResourceCommand as RemoveTagsFromResourceCommand2
26355
26995
  } from "@aws-sdk/client-rds";
26356
26996
  var RDSProvider = class {
26357
26997
  rdsClient;
@@ -26418,14 +27058,32 @@ var RDSProvider = class {
26418
27058
  );
26419
27059
  }
26420
27060
  }
26421
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
27061
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
26422
27062
  switch (resourceType) {
26423
27063
  case "AWS::RDS::DBSubnetGroup":
26424
- return this.updateDBSubnetGroup(logicalId, physicalId, resourceType, properties);
27064
+ return this.updateDBSubnetGroup(
27065
+ logicalId,
27066
+ physicalId,
27067
+ resourceType,
27068
+ properties,
27069
+ previousProperties
27070
+ );
26425
27071
  case "AWS::RDS::DBCluster":
26426
- return this.updateDBCluster(logicalId, physicalId, resourceType, properties);
27072
+ return this.updateDBCluster(
27073
+ logicalId,
27074
+ physicalId,
27075
+ resourceType,
27076
+ properties,
27077
+ previousProperties
27078
+ );
26427
27079
  case "AWS::RDS::DBInstance":
26428
- return this.updateDBInstance(logicalId, physicalId, resourceType, properties);
27080
+ return this.updateDBInstance(
27081
+ logicalId,
27082
+ physicalId,
27083
+ resourceType,
27084
+ properties,
27085
+ previousProperties
27086
+ );
26429
27087
  default:
26430
27088
  throw new ProvisioningError(
26431
27089
  `Unsupported resource type: ${resourceType}`,
@@ -26484,7 +27142,7 @@ var RDSProvider = class {
26484
27142
  );
26485
27143
  }
26486
27144
  }
26487
- async updateDBSubnetGroup(logicalId, physicalId, resourceType, properties) {
27145
+ async updateDBSubnetGroup(logicalId, physicalId, resourceType, properties, previousProperties) {
26488
27146
  this.logger.debug(`Updating DBSubnetGroup ${logicalId}: ${physicalId}`);
26489
27147
  try {
26490
27148
  await this.getClient().send(
@@ -26494,6 +27152,17 @@ var RDSProvider = class {
26494
27152
  SubnetIds: properties["SubnetIds"]
26495
27153
  })
26496
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
+ }
26497
27166
  this.logger.debug(`Successfully updated DBSubnetGroup ${logicalId}`);
26498
27167
  return {
26499
27168
  physicalId,
@@ -26608,7 +27277,7 @@ var RDSProvider = class {
26608
27277
  );
26609
27278
  }
26610
27279
  }
26611
- async updateDBCluster(logicalId, physicalId, resourceType, properties) {
27280
+ async updateDBCluster(logicalId, physicalId, resourceType, properties, previousProperties) {
26612
27281
  this.logger.debug(`Updating DBCluster ${logicalId}: ${physicalId}`);
26613
27282
  try {
26614
27283
  const serverlessV2Config = properties["ServerlessV2ScalingConfiguration"];
@@ -26632,6 +27301,13 @@ var RDSProvider = class {
26632
27301
  );
26633
27302
  this.logger.debug(`Successfully updated DBCluster ${logicalId}`);
26634
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
+ }
26635
27311
  return {
26636
27312
  physicalId,
26637
27313
  wasReplaced: false,
@@ -26749,7 +27425,7 @@ var RDSProvider = class {
26749
27425
  );
26750
27426
  }
26751
27427
  }
26752
- async updateDBInstance(logicalId, physicalId, resourceType, properties) {
27428
+ async updateDBInstance(logicalId, physicalId, resourceType, properties, previousProperties) {
26753
27429
  this.logger.debug(`Updating DBInstance ${logicalId}: ${physicalId}`);
26754
27430
  try {
26755
27431
  await this.getClient().send(
@@ -26762,6 +27438,13 @@ var RDSProvider = class {
26762
27438
  );
26763
27439
  this.logger.debug(`Successfully updated DBInstance ${logicalId}`);
26764
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
+ }
26765
27448
  return {
26766
27449
  physicalId,
26767
27450
  wasReplaced: false,
@@ -26832,6 +27515,45 @@ var RDSProvider = class {
26832
27515
  }
26833
27516
  }
26834
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
+ }
26835
27557
  buildTags(properties) {
26836
27558
  if (!properties["Tags"])
26837
27559
  return [];
@@ -28119,6 +28841,8 @@ import {
28119
28841
  GetWebACLCommand,
28120
28842
  ListWebACLsCommand,
28121
28843
  ListTagsForResourceCommand as ListTagsForResourceCommand12,
28844
+ TagResourceCommand as TagResourceCommand13,
28845
+ UntagResourceCommand as UntagResourceCommand13,
28122
28846
  WAFNonexistentItemException
28123
28847
  } from "@aws-sdk/client-wafv2";
28124
28848
  function parseWebACLArn(arn) {
@@ -28225,7 +28949,7 @@ var WAFv2WebACLProvider = class {
28225
28949
  * Name and Scope are immutable - changes to those require replacement.
28226
28950
  * UpdateWebACL requires LockToken obtained from GetWebACL.
28227
28951
  */
28228
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
28952
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
28229
28953
  this.logger.debug(`Updating WAFv2 WebACL ${logicalId}: ${physicalId}`);
28230
28954
  try {
28231
28955
  const { id, name, scope } = parseWebACLArn(physicalId);
@@ -28257,6 +28981,11 @@ var WAFv2WebACLProvider = class {
28257
28981
  AssociationConfig: properties["AssociationConfig"]
28258
28982
  })
28259
28983
  );
28984
+ await this.applyTagDiff(
28985
+ physicalId,
28986
+ previousProperties["Tags"],
28987
+ properties["Tags"]
28988
+ );
28260
28989
  this.logger.debug(`Successfully updated WAFv2 WebACL ${logicalId}`);
28261
28990
  return {
28262
28991
  physicalId,
@@ -28333,6 +29062,42 @@ var WAFv2WebACLProvider = class {
28333
29062
  );
28334
29063
  }
28335
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
+ }
28336
29101
  /**
28337
29102
  * Read the AWS-current WAFv2 WebACL configuration in CFn-property shape.
28338
29103
  *
@@ -28947,7 +29712,9 @@ import {
28947
29712
  DeleteCacheSubnetGroupCommand,
28948
29713
  ModifyCacheSubnetGroupCommand,
28949
29714
  ModifyCacheClusterCommand,
28950
- ListTagsForResourceCommand as ListTagsForResourceCommand14
29715
+ ListTagsForResourceCommand as ListTagsForResourceCommand14,
29716
+ AddTagsToResourceCommand as AddTagsToResourceCommand3,
29717
+ RemoveTagsFromResourceCommand as RemoveTagsFromResourceCommand3
28951
29718
  } from "@aws-sdk/client-elasticache";
28952
29719
  import { STSClient as STSClient6, GetCallerIdentityCommand as GetCallerIdentityCommand6 } from "@aws-sdk/client-sts";
28953
29720
  var ElastiCacheProvider = class {
@@ -29013,12 +29780,18 @@ var ElastiCacheProvider = class {
29013
29780
  );
29014
29781
  }
29015
29782
  }
29016
- async update(logicalId, physicalId, resourceType, properties, _previousProperties) {
29783
+ async update(logicalId, physicalId, resourceType, properties, previousProperties) {
29017
29784
  switch (resourceType) {
29018
29785
  case "AWS::ElastiCache::SubnetGroup":
29019
29786
  return this.updateSubnetGroup(logicalId, physicalId, resourceType, properties);
29020
29787
  case "AWS::ElastiCache::CacheCluster":
29021
- return this.updateCacheCluster(logicalId, physicalId, resourceType, properties);
29788
+ return this.updateCacheCluster(
29789
+ logicalId,
29790
+ physicalId,
29791
+ resourceType,
29792
+ properties,
29793
+ previousProperties
29794
+ );
29022
29795
  default:
29023
29796
  throw new ProvisioningError(
29024
29797
  `Unsupported resource type: ${resourceType}`,
@@ -29203,7 +29976,7 @@ var ElastiCacheProvider = class {
29203
29976
  );
29204
29977
  }
29205
29978
  }
29206
- async updateCacheCluster(logicalId, physicalId, resourceType, properties) {
29979
+ async updateCacheCluster(logicalId, physicalId, resourceType, properties, previousProperties) {
29207
29980
  this.logger.debug(`Updating CacheCluster ${logicalId}: ${physicalId}`);
29208
29981
  try {
29209
29982
  await this.getClient().send(
@@ -29226,6 +29999,13 @@ var ElastiCacheProvider = class {
29226
29999
  this.logger.debug(`Successfully updated CacheCluster ${logicalId}`);
29227
30000
  await this.waitForClusterAvailable(physicalId);
29228
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
+ }
29229
30009
  const attributes = {};
29230
30010
  if (described?.CacheNodes?.[0]?.Endpoint) {
29231
30011
  const endpoint = described.CacheNodes[0].Endpoint;
@@ -29290,6 +30070,45 @@ var ElastiCacheProvider = class {
29290
30070
  }
29291
30071
  }
29292
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
+ }
29293
30112
  buildTags(properties) {
29294
30113
  if (!properties["Tags"])
29295
30114
  return [];
@@ -31617,8 +32436,8 @@ import {
31617
32436
  PutKeyPolicyCommand,
31618
32437
  EnableKeyCommand,
31619
32438
  DisableKeyCommand,
31620
- TagResourceCommand as TagResourceCommand6,
31621
- UntagResourceCommand as UntagResourceCommand6,
32439
+ TagResourceCommand as TagResourceCommand14,
32440
+ UntagResourceCommand as UntagResourceCommand14,
31622
32441
  NotFoundException as NotFoundException5
31623
32442
  } from "@aws-sdk/client-kms";
31624
32443
  var KMSProvider = class {
@@ -31803,26 +32622,11 @@ var KMSProvider = class {
31803
32622
  await this.getClient().send(new EnableKeyCommand({ KeyId: physicalId }));
31804
32623
  }
31805
32624
  }
31806
- const newTags = properties["Tags"];
31807
- const oldTags = previousProperties["Tags"];
31808
- if (JSON.stringify(newTags) !== JSON.stringify(oldTags)) {
31809
- if (oldTags && oldTags.length > 0) {
31810
- await this.getClient().send(
31811
- new UntagResourceCommand6({
31812
- KeyId: physicalId,
31813
- TagKeys: oldTags.map((t) => t.Key)
31814
- })
31815
- );
31816
- }
31817
- if (newTags && newTags.length > 0) {
31818
- await this.getClient().send(
31819
- new TagResourceCommand6({
31820
- KeyId: physicalId,
31821
- Tags: newTags.map((t) => ({ TagKey: t.Key, TagValue: t.Value }))
31822
- })
31823
- );
31824
- }
31825
- }
32625
+ await this.applyTagDiff(
32626
+ physicalId,
32627
+ previousProperties["Tags"],
32628
+ properties["Tags"]
32629
+ );
31826
32630
  const newKeyPolicy = properties["KeyPolicy"];
31827
32631
  const oldKeyPolicy = previousProperties["KeyPolicy"];
31828
32632
  const newPolicyStr = newKeyPolicy ? typeof newKeyPolicy === "string" ? newKeyPolicy : JSON.stringify(newKeyPolicy) : void 0;
@@ -31884,6 +32688,43 @@ var KMSProvider = class {
31884
32688
  );
31885
32689
  }
31886
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
+ }
31887
32728
  // ─── AWS::KMS::Alias ───────────────────────────────────────────────
31888
32729
  async createAlias(logicalId, resourceType, properties) {
31889
32730
  this.logger.debug(`Creating KMS Alias ${logicalId}`);
@@ -32168,6 +33009,7 @@ import {
32168
33009
  DescribeStreamCommand,
32169
33010
  UpdateShardCountCommand,
32170
33011
  AddTagsToStreamCommand,
33012
+ RemoveTagsFromStreamCommand,
32171
33013
  IncreaseStreamRetentionPeriodCommand,
32172
33014
  DecreaseStreamRetentionPeriodCommand,
32173
33015
  StartStreamEncryptionCommand,
@@ -32345,6 +33187,11 @@ var KinesisStreamProvider = class {
32345
33187
  }
32346
33188
  await this.waitForStreamActive(physicalId);
32347
33189
  }
33190
+ await this.applyTagDiff(
33191
+ physicalId,
33192
+ previousProperties["Tags"],
33193
+ properties["Tags"]
33194
+ );
32348
33195
  const newEncryption = properties["StreamEncryption"];
32349
33196
  const oldEncryption = previousProperties["StreamEncryption"];
32350
33197
  if (JSON.stringify(newEncryption) !== JSON.stringify(oldEncryption)) {
@@ -32426,6 +33273,47 @@ var KinesisStreamProvider = class {
32426
33273
  );
32427
33274
  }
32428
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
+ }
32429
33317
  /**
32430
33318
  * Adopt an existing Kinesis stream into cdkd state.
32431
33319
  *
@@ -33786,6 +34674,8 @@ import {
33786
34674
  GetEventSelectorsCommand,
33787
34675
  ListTrailsCommand,
33788
34676
  ListTagsCommand as ListTagsCommand3,
34677
+ AddTagsCommand as AddTagsCommand2,
34678
+ RemoveTagsCommand as RemoveTagsCommand2,
33789
34679
  TrailNotFoundException
33790
34680
  } from "@aws-sdk/client-cloudtrail";
33791
34681
  var CloudTrailProvider = class {
@@ -33969,6 +34859,11 @@ var CloudTrailProvider = class {
33969
34859
  await this.getClient().send(new StartLoggingCommand({ Name: physicalId }));
33970
34860
  }
33971
34861
  }
34862
+ await this.applyTagDiff(
34863
+ physicalId,
34864
+ previousProperties["Tags"],
34865
+ properties["Tags"]
34866
+ );
33972
34867
  this.logger.debug(`Successfully updated CloudTrail Trail ${logicalId}`);
33973
34868
  return { physicalId, wasReplaced: false };
33974
34869
  } catch (error) {
@@ -34017,6 +34912,46 @@ var CloudTrailProvider = class {
34017
34912
  getAttribute(_physicalId, _resourceType, attributeName) {
34018
34913
  return Promise.resolve(attributeName);
34019
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
+ }
34020
34955
  /**
34021
34956
  * Adopt an existing CloudTrail trail into cdkd state.
34022
34957
  *
@@ -35840,7 +36775,7 @@ import {
35840
36775
  SetRepositoryPolicyCommand,
35841
36776
  PutImageScanningConfigurationCommand,
35842
36777
  PutImageTagMutabilityCommand,
35843
- TagResourceCommand as TagResourceCommand7,
36778
+ TagResourceCommand as TagResourceCommand15,
35844
36779
  ListTagsForResourceCommand as ListTagsForResourceCommand18,
35845
36780
  LifecyclePolicyNotFoundException,
35846
36781
  RepositoryNotFoundException
@@ -36006,7 +36941,7 @@ var ECRProvider = class {
36006
36941
  const repoArn = describeResponse.repositories?.[0]?.repositoryArn;
36007
36942
  if (repoArn && newTags) {
36008
36943
  await this.getClient().send(
36009
- new TagResourceCommand7({
36944
+ new TagResourceCommand15({
36010
36945
  resourceArn: repoArn,
36011
36946
  tags: newTags
36012
36947
  })
@@ -42570,7 +43505,7 @@ function reorderArgs(argv) {
42570
43505
  }
42571
43506
  async function main() {
42572
43507
  const program = new Command14();
42573
- program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.50.4");
43508
+ program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.50.6");
42574
43509
  program.addCommand(createBootstrapCommand());
42575
43510
  program.addCommand(createSynthCommand());
42576
43511
  program.addCommand(createListCommand());