Instantly share code, notes, and snippets.
Created
September 13, 2024 17:50
-
Star
1
(1)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save druskus20/e3a6cfee05e4c6654b0f70b2cd6989bf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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