Last active
September 12, 2022 17:46
-
-
Save dbeattie71/1f8a1a9264ceb8161ad4c49de1ee3bb3 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
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; } | |
} | |
} |
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
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