Skip to content

Instantly share code, notes, and snippets.

@dbeattie71
Last active September 12, 2022 17:46
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dbeattie71/1f8a1a9264ceb8161ad4c49de1ee3bb3 to your computer and use it in GitHub Desktop.
Save dbeattie71/1f8a1a9264ceb8161ad4c49de1ee3bb3 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
namespace Pulumi.Helpers
{
public interface ITaggingStrategy
{
bool IsTaggable(string typeName);
Dictionary<string, string> GetTags();
}
public interface INamingStrategy
{
string GetName(ResourceInfo resourceInfo);
}
public class MyNamingStrategy : INamingStrategy
{
private readonly string _code;
private readonly string _environment;
private readonly HashSet<string> _names = new();
private readonly Dictionary<string, string> _typeToTypeMap = new()
{
{"Bucket", "bckt"}
};
public MyNamingStrategy(string code, string environment)
{
_code = code;
_environment = environment;
}
public string GetName(ResourceInfo resourceInfo)
{
if (!_typeToTypeMap.TryGetValue(resourceInfo.Type, out var resourceType))
{
resourceType = resourceInfo.TypeShort;
}
return GetLogicalName($"{_code}-{_environment[0]}-{resourceType}");
}
public string GetLogicalName(string logicalName)
{
var logicalNameCount = GetLogicalNameCount(logicalName);
logicalName = $"{logicalName}-{logicalNameCount:D3}";
_names.Add(logicalName);
return logicalName;
}
public int GetLogicalNameCount(string logicalName)
{
return _names.Count(s => s.StartsWith(logicalName)) + 1;
}
}
public class Auto
{
private readonly ITaggingStrategy _taggingStrategy;
public Auto() : this(null, null)
{
}
public Auto(ITaggingStrategy taggingStrategy) : this(null, taggingStrategy)
{
}
public Auto(INamingStrategy namingStrategy) : this(namingStrategy, null)
{
}
public Auto(INamingStrategy namingStrategy, ITaggingStrategy taggingStrategy)
{
NamingStrategy = namingStrategy;
_taggingStrategy = taggingStrategy;
var harmony = new Harmony("PulumiCtorPatch");
harmony.PatchAll();
if (_taggingStrategy != null)
{
StackPatch.PrefixCallback = StackPrefixCallback;
}
ResourcePatch.PrefixCallback = ResourcePrefixCallback;
}
internal INamingStrategy NamingStrategy { get; set; }
private ResourceTransformationResult StackPrefixCallback(ResourceTransformationArgs args)
{
if (args.Resource.GetType() == typeof(Stack))
{
return new ResourceTransformationResult(args.Args,
args.Options);
}
if (!_taggingStrategy.IsTaggable(args.Resource.GetResourceType()))
{
return new ResourceTransformationResult(args.Args, args.Options);
}
var propertyInfo = args.Args.GetType().GetProperty("Tags");
if (propertyInfo == null)
{
return new ResourceTransformationResult(args.Args, args.Options);
}
var tags = (InputMap<string>) propertyInfo.GetValue(args.Args, null) ?? new InputMap<string>();
var newTags = _taggingStrategy.GetTags();
foreach (var (key, value) in newTags)
{
tags[key] = value;
}
propertyInfo.SetValue(args.Args, tags);
return new ResourceTransformationResult(args.Args, args.Options);
}
private string ResourcePrefixCallback(string typeName, string name)
{
return NamingStrategy == null ? name : NamingStrategy.GetName(new ResourceInfo(typeName));
}
public ScopedNamingStrategy ScopedNamingStrategy(INamingStrategy namingStrategy)
{
return new ScopedNamingStrategy(this, namingStrategy);
}
}
public class ScopedNamingStrategy : IDisposable
{
private readonly Auto _auto;
private readonly INamingStrategy _previousNamingStrategy;
public ScopedNamingStrategy(Auto auto, INamingStrategy namingStrategy)
{
_auto = auto;
_previousNamingStrategy = auto.NamingStrategy;
SetNamingStrategy(namingStrategy);
}
public void Dispose()
{
SetNamingStrategy(_previousNamingStrategy);
}
~ScopedNamingStrategy()
{
Dispose();
}
private void SetNamingStrategy(INamingStrategy namingStrategy)
{
_auto.NamingStrategy = namingStrategy;
}
}
//https://docs.aws.amazon.com/ARG/latest/userguide/supported-resources.html
//https://github.com/axuno/SmartFormat/wiki
[HarmonyPatch(typeof(Stack), MethodType.Constructor, typeof(StackOptions))]
public class StackPatch
{
/*
https://github.com/pulumi/pulumi/blob/414ef7e9d0738a76ce6fbcf39092f57659a4e371/sdk/dotnet/Pulumi/Stack.cs#L52
public Stack(StackOptions? options = null)
: base(_rootPulumiStackTypeName,
$"{Deployment.Instance.ProjectName}-{Deployment.Instance.StackName}",
ConvertOptions(options))
*/
public static Func<ResourceTransformationArgs, ResourceTransformationResult> PrefixCallback;
private static void Prefix(Stack __instance, ref StackOptions options)
{
if (PrefixCallback != null)
{
options = new StackOptions
{
ResourceTransformations =
{
new ResourceTransformation(resourceTransformationArgs =>
PrefixCallback.Invoke(resourceTransformationArgs))
}
};
}
}
}
[HarmonyPatch(typeof(Resource), MethodType.Constructor, typeof(string), typeof(string), typeof(bool),
typeof(ResourceArgs), typeof(ResourceOptions), typeof(bool), typeof(bool))]
public class ResourcePatch
{
/*
https://github.com/pulumi/pulumi/blob/b58c39476f822a8f0e2966d737d642c255d92505/sdk/dotnet/Pulumi/Resources/Resource.cs#L120
private protected Resource(
string type, string name, bool custom,
ResourceArgs args, ResourceOptions options,
bool remote = false, bool dependency = false)
*/
public static Func<string, string, string> PrefixCallback;
private static void Prefix(Resource __instance,
string type, ref string name, bool custom,
ResourceArgs args, ResourceOptions options,
bool remote, bool dependency, ref string ____name)
{
if (PrefixCallback == null || !__instance.IsResourceTypeAttribute())
{
return;
}
if (!string.IsNullOrEmpty(name))
{
return;
}
var newName =
PrefixCallback.Invoke(__instance.GetResourceTypeAttribute()?.Type, name);
name = newName;
____name = newName;
}
}
public class ResourceInfo
{
public ResourceInfo(string typeName)
{
//<package>:<module>:<type>
//aws:s3/bucket:Bucket
TypeName = typeName;
var split = TypeName.Split(":");
Package = split[0];
Module = split[1];
Type = split[2];
TypeShort = string.Concat(Type.Where(char.IsUpper)).ToLower();
}
public string TypeName { get; }
public string Package { get; }
public string Module { get; }
public string Type { get; }
public string TypeShort { get; }
}
}
public static class Extensions
{
public static bool IsResourceTypeAttribute(this Resource resource)
{
return Attribute.GetCustomAttributes(resource.GetType()).Any(attribute =>
attribute.GetType().IsSubclassOf(typeof(ResourceTypeAttribute)));
}
public static ResourceTypeAttribute GetResourceTypeAttribute(this Resource resource)
{
return Attribute.GetCustomAttributes(resource.GetType()).FirstOrDefault(attribute =>
attribute.GetType().IsSubclassOf(typeof(ResourceTypeAttribute))) as ResourceTypeAttribute;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment