serverless-tag-resources 3.1.1 → 3.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverless-tag-resources",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "description": "Datamart: Tag all AWS resources with dual legacy + datamart:* tag support",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -10,20 +10,19 @@ const {
10
10
  DescribeStackResourcesCommand,
11
11
  } = require("@aws-sdk/client-cloudformation");
12
12
  const { getClient } = require("../aws-clients");
13
+ const { SKIP_TYPES } = require("../resource-classifier");
13
14
 
14
15
  /**
15
16
  * Remove unwanted tags from the stack itself and all its resources.
16
17
  * Uses Resource Groups Tagging API (UntagResources) which works on
17
18
  * both CF stacks and individual resources.
18
19
  *
19
- * This runs post-deploy to clean up tags injected by SFW (e.g., STAGE)
20
- * that we cannot prevent at template/deploy time.
20
+ * No UpdateStack only tag removal, zero infrastructure changes.
21
21
  */
22
22
  async function removeUnwantedTags(config, stackName, tagKeysToRemove, log) {
23
23
  const cfnClient = getClient(CloudFormationClient, config);
24
24
  const taggingClient = getClient(ResourceGroupsTaggingAPIClient, config);
25
25
 
26
- // Collect ARNs: stack + all resources
27
26
  const arns = [];
28
27
 
29
28
  // 1. Get stack ARN
@@ -33,13 +32,18 @@ async function removeUnwantedTags(config, stackName, tagKeysToRemove, log) {
33
32
  const stackArn = stackResult.Stacks?.[0]?.StackId;
34
33
  if (stackArn) arns.push(stackArn);
35
34
 
36
- // 2. Get all resource ARNs
35
+ // 2. Get resource ARNs (deduplicated, excluding types that don't support tags)
36
+ const seen = new Set();
37
37
  const resourceResult = await cfnClient.send(
38
38
  new DescribeStackResourcesCommand({ StackName: stackName })
39
39
  );
40
40
  for (const resource of resourceResult.StackResources || []) {
41
+ if (SKIP_TYPES.has(resource.ResourceType)) continue;
41
42
  const arn = resolveArn(resource);
42
- if (arn) arns.push(arn);
43
+ if (arn && !seen.has(arn)) {
44
+ seen.add(arn);
45
+ arns.push(arn);
46
+ }
43
47
  }
44
48
 
45
49
  if (arns.length === 0) return;
@@ -47,7 +51,7 @@ async function removeUnwantedTags(config, stackName, tagKeysToRemove, log) {
47
51
  // UntagResources accepts max 20 ARNs per call
48
52
  const batchSize = 20;
49
53
  let untagged = 0;
50
- let failed = 0;
54
+ let skipped = 0;
51
55
 
52
56
  for (let i = 0; i < arns.length; i += batchSize) {
53
57
  const batch = arns.slice(i, i + batchSize);
@@ -58,16 +62,16 @@ async function removeUnwantedTags(config, stackName, tagKeysToRemove, log) {
58
62
  TagKeys: tagKeysToRemove,
59
63
  })
60
64
  );
61
- const failures = Object.keys(result.FailedResourcesMap || {}).length;
65
+ const failedMap = result.FailedResourcesMap || {};
66
+ const failures = Object.keys(failedMap).length;
62
67
  untagged += batch.length - failures;
63
- failed += failures;
68
+ skipped += failures;
64
69
  } catch (err) {
65
- // Some resource types don't support tagging API — skip
66
- failed += batch.length;
70
+ skipped += batch.length;
67
71
  }
68
72
  }
69
73
 
70
- log(`TAGGING: Removed [${tagKeysToRemove.join(", ")}] from ${untagged} resources (${failed} skipped)`);
74
+ log(`TAGGING: Removed [${tagKeysToRemove.join(", ")}] from ${untagged} resources (${skipped} skipped)`);
71
75
  }
72
76
 
73
77
  /**
@@ -98,21 +102,24 @@ function resolveArn(resource) {
98
102
  `arn:${partition}:sns:${region}:${account}:${physicalId}`,
99
103
  "AWS::Events::EventBus": () =>
100
104
  `arn:${partition}:events:${region}:${account}:event-bus/${physicalId}`,
105
+ "AWS::Events::Rule": () =>
106
+ `arn:${partition}:events:${region}:${account}:rule/${physicalId}`,
101
107
  "AWS::Logs::LogGroup": () =>
102
108
  `arn:${partition}:logs:${region}:${account}:log-group:${physicalId}`,
103
109
  "AWS::IAM::Role": () =>
104
110
  `arn:${partition}:iam::${account}:role/${physicalId}`,
105
- "AWS::IAM::ManagedPolicy": () =>
106
- `arn:${partition}:iam::${account}:policy/${physicalId}`,
107
111
  "AWS::S3::Bucket": () =>
108
112
  `arn:${partition}:s3:::${physicalId}`,
109
113
  "AWS::SSM::Parameter": () =>
110
114
  `arn:${partition}:ssm:${region}:${account}:parameter${physicalId.startsWith("/") ? "" : "/"}${physicalId}`,
111
115
  "AWS::KMS::Key": () =>
112
116
  `arn:${partition}:kms:${region}:${account}:key/${physicalId}`,
113
- "AWS::KMS::Alias": () => null, // aliases don't support tagging
114
117
  "AWS::CodeBuild::Project": () =>
115
118
  `arn:${partition}:codebuild:${region}:${account}:project/${physicalId}`,
119
+ "AWS::ApiGateway::RestApi": () =>
120
+ `arn:${partition}:apigateway:${region}::/restapis/${physicalId}`,
121
+ "AWS::WAFv2::WebACL": () =>
122
+ `arn:${partition}:wafv2:${region}:${account}:regional/webacl/${physicalId}`,
116
123
  };
117
124
 
118
125
  const builder = builders[type];
@@ -264,6 +264,7 @@ const SKIP_TYPES = new Set([
264
264
  // Glue
265
265
  "AWS::Glue::Database",
266
266
  "AWS::Glue::Classifier",
267
+ "AWS::Glue::Crawler",
267
268
  "AWS::Glue::Connection",
268
269
  "AWS::Glue::DataCatalogEncryptionSettings",
269
270
  "AWS::Glue::Partition",