serverless-tag-resources
Version:
Datamart: Tag all AWS resources with dual legacy + datamart:* tag support
91 lines (75 loc) • 2.77 kB
JavaScript
;
const { buildListTags, buildDictTags } = require("./tags");
const { classifyResource } = require("./resource-classifier");
/**
* Template Tagger — Hook: before:package:finalize
*
* Mutates the compiled CloudFormation template to inject tags
* into resource Properties before deployment.
*/
/**
* Tag all resources in a CloudFormation resources object.
*
* @param {object} resources - { LogicalId: { Type, Properties, ... } }
* @param {object} stackTags - from provider.stackTags
* @param {string} stage - deployment stage
* @param {Function} log - logging function
*/
function tagResources(resources, stackTags, stage, log) {
if (!resources) return;
for (const [logicalId, resource] of Object.entries(resources)) {
const resourceType = resource.Type;
if (!resourceType) continue;
const classification = classifyResource(resourceType);
if (classification === "skip") continue;
if (!resource.Properties) {
log(`TAGGING: skipping ${resourceType} (${logicalId}) — no Properties`);
continue;
}
switch (classification) {
case "list":
tagListBased(resource, stackTags, stage, logicalId);
break;
case "dict":
tagDictBased(resource, stackTags, stage, logicalId);
break;
case "unclassified":
log(`TAGGING: WARNING — unclassified resource type ${resourceType} (${logicalId}). Attempting list-based tagging. If deploy fails, add to skipTypes.`);
tagListBased(resource, stackTags, stage, logicalId);
break;
case "api-only":
case "related":
// These are tagged post-deploy via API, not in the template
break;
}
}
}
/**
* Apply list-based tags [{Key, Value}] to a resource.
* Merges with existing tags without overwriting.
*/
function tagListBased(resource, stackTags, stage, logicalId) {
const newTags = buildListTags(stackTags, stage, logicalId);
const existing = resource.Properties.Tags || [];
// Build set of existing keys (case-insensitive)
const existingKeys = new Set(
existing.map((t) => (t.Key || "").toLowerCase())
);
// Only add tags that don't already exist
const merged = [
...existing,
...newTags.filter((t) => !existingKeys.has(t.Key.toLowerCase())),
];
resource.Properties.Tags = merged;
}
/**
* Apply dict-based tags {key: value} to a resource.
* Merges with existing tags without overwriting.
*/
function tagDictBased(resource, stackTags, stage, logicalId) {
const newTags = buildDictTags(stackTags, stage, logicalId);
const existing = resource.Properties.Tags || {};
// Merge: existing keys take priority
resource.Properties.Tags = { ...newTags, ...existing };
}
module.exports = { tagResources, tagListBased, tagDictBased };