Skip to content

Instantly share code, notes, and snippets.

@druskus20
Created September 13, 2024 17:50
Show Gist options
  • Select an option

  • Save druskus20/e3a6cfee05e4c6654b0f70b2cd6989bf to your computer and use it in GitHub Desktop.

Select an option

Save druskus20/e3a6cfee05e4c6654b0f70b2cd6989bf to your computer and use it in GitHub Desktop.
import * as pulumi from "@pulumi/pulumi";
import TAGGABLE_RESOURCE_TYPES from "./taggable_resource_types.json";
import { ResourceTransformResult } from "@pulumi/pulumi";
// Taggable resources that do not support after-tagging.
const UNSUPPORTED_RESOURCE_TYPES: string[] = [
"aws:afterscaling/group:Group",
"aws-native:servicecatalogappregistry:Application",
];
export function afterTagsTransform(
args: pulumi.ResourceTransformArgs,
afterTags: InputObjectTags | InputArrayTags,
):
| ResourceTransformResult
| undefined {
const resourceType = args.type;
// Skip resource types that do not support after-tagging
if (UNSUPPORTED_RESOURCE_TYPES.includes(resourceType)) {
pulumi.log.warn(
`Resource of type ${resourceType} does not support after-tagging.`,
);
return undefined;
}
// Apply tags only to taggable resource types
if (TAGGABLE_RESOURCE_TYPES.includes(resourceType)) {
pulumi.log.info(
`Resource of type ${resourceType} does support after-tagging.`,
);
let existingTags: OutputArrayTags;
// Determine the existing tags format (ArrayTags or ObjectTags) and convert to OutputArrayTags
if (isArrayTags(args.props.tags)) {
existingTags = pulumi.output(args.props.tags) as OutputArrayTags; // Safety: if condition right above
} else if (isObjectTags(args.props.tags)) {
existingTags = objectTagsToArrayTags(args.props.tags) as OutputArrayTags;
} else {
pulumi.log.warn(
`Resource of type ${resourceType} has invalid tags format.`,
);
return undefined;
}
// Merge existing tags with the provided after-tags
const mergedTags = existingTags.apply((tags) => {
let updatedTags: OutputArrayTags;
if (isArrayTags(afterTags)) {
afterTags = afterTags as InputArrayTags; // Safety: if condition right above
updatedTags = combineArrayTags(tags, afterTags);
} else if (isObjectTags(afterTags)) {
afterTags = afterTags as InputObjectTags; // Safety: if condition right above
updatedTags = combineArrayTags(tags, objectTagsToArrayTags(afterTags));
} else {
pulumi.log.warn(
`Resource of type ${resourceType} has invalid afterTags format.
${JSON.stringify(pulumi.output(afterTags))}
`,
);
return undefined;
}
return updatedTags;
});
args.props.tags = mergedTags;
return {
props: args.props,
opts: args.opts,
};
}
pulumi.log.warn(
`Resource of type ${resourceType} does not support after-tagging.`,
);
return undefined;
}
/**
* Registers a resource transformation that applies after-tags to supported Pulumi resources.
* After-tags are applied after the resource has been created, allowing for additional tags
* to be merged with existing resource tags.
*
* @param afterTags - The tags to apply after the resource is created. This can be in the form of either ObjectTags or ArrayTags.
*/
export function registerAfterTags(
afterTags: InputObjectTags | InputArrayTags,
): void {
pulumi.runtime.registerResourceTransform((args) => {
return afterTagsTransform(args, afterTags);
});
}
// Utils ---------------------------------------------------------------
import * as pulumi from "@pulumi/pulumi";
import { Input, Output } from "@pulumi/pulumi";
export type ObjectTags = { [key: string]: string };
export type ArrayTag = { key: string; value: string };
export type ArrayTags = ArrayTag[];
// Pulumi has two the types of tags:
//
// A: ArrayTags
//
// [{
// key: "key",
// value: "value",
// }]
//
// B: ObjectTags
//
// { key: "value" }
//
export type InputObjectTags = Input<{ [key: string]: Input<string> }>;
export type InputArrayTag = Input<{ key: Input<string>; value: Input<string> }>;
export type InputArrayTags = Input<InputArrayTag[]>;
export type OutputObjectTags = Output<{ [key: string]: string }>;
export type OutputArrayTag = Output<{ key: string; value: string }>;
export type OutputArrayTags = Output<ArrayTag[]>;
// Functions for converting between array tags and object tags
/**
* Converts an array of tags (ArrayTags) to an object of tags (ObjectTags).
*
* @param tags - An array of key-value tags to convert.
* @returns An object where each key-value pair corresponds to the tags in the array.
*/
export function arrayTagsToObjectTags(tags: InputArrayTags): OutputObjectTags {
return pulumi.output(tags).apply((tags) => {
const objectTags: ObjectTags = {};
for (const tag of tags) {
if (tag && tag.key && tag.value) {
objectTags[tag.key] = tag.value;
}
}
return objectTags;
});
}
/**
* Converts an object of tags (ObjectTags) to an array of tags (ArrayTags).
*
* @param tags - An object with key-value pairs to convert.
* @returns An array where each element is an object with `key` and `value` properties.
*/
export function objectTagsToArrayTags(tags: InputObjectTags): OutputArrayTags {
return pulumi.output(tags).apply((tags) => {
const arrayTags: ArrayTags = [];
for (const key in tags) {
arrayTags.push({ key, value: tags[key] });
}
return arrayTags;
});
}
/**
* Combines two arrays of tags (ArrayTags) into one.
*
* @param tags - The first array of key-value tags.
* @param extra_tags - The second array of key-value tags.
* @returns A new array containing all the tags from both input arrays.
*/
export function combineArrayTags(
tags: InputArrayTags,
extra_tags: InputArrayTags,
): OutputArrayTags {
return pulumi.all([tags, extra_tags]).apply(([tags, extra_tags]) => {
return tags.concat(extra_tags);
});
}
/**
* Combines two objects of tags (ObjectTags) into one.
*
* @param tags - The first object of key-value tags.
* @param extra_tags - The second object of key-value tags.
* @returns A new object containing all key-value pairs from both input objects.
*/
export function combineObjectTags(
tags: InputObjectTags,
extra_tags: InputObjectTags,
): OutputObjectTags {
return pulumi.all([tags, extra_tags]).apply(([tags, extra_tags]) => {
return { ...tags, ...extra_tags };
});
}
/**
* Checks if a given Pulumi Input value is an array of key-value tags (ArrayTags).
*
* @param tags - The Pulumi Input value to check (either InputArrayTags or InputObjectTags).
* @returns A Pulumi Output that resolves to true if the value is an array of valid key-value tags, false otherwise.
*/
export function isArrayTags(
tags: InputArrayTags | InputObjectTags,
): pulumi.Output<boolean> {
return pulumi.output(tags).apply((t) => {
// Check if the value is an array and every element contains valid 'key' and 'value' strings
return (
Array.isArray(t) &&
t.every(
(tag) =>
tag && typeof tag.key === "string" && typeof tag.value === "string",
)
);
});
}
/**
* Checks if a given Pulumi Input value is an object of key-value tags (ObjectTags).
*
* @param tags - The Pulumi Input value to check (either InputArrayTags or InputObjectTags).
* @returns A Pulumi Output that resolves to true if the value is a valid object of key-value pairs, false otherwise.
*/
export function isObjectTags(
tags: InputArrayTags | InputObjectTags,
): pulumi.Output<boolean> {
return pulumi.output(tags).apply((t) => {
// Check if the value is an object and each key is a string with corresponding values
return (
typeof t === "object" &&
t !== null &&
!Array.isArray(t) &&
Object.keys(t).every((key) => typeof key === "string")
);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment