using DocMigrator.Yaml; using Microsoft.Extensions.Logging; using RackPeek.Domain.Resources; using RackPeek.Domain.Resources.AccessPoints; using RackPeek.Domain.Resources.Desktops; using RackPeek.Domain.Resources.Firewalls; using RackPeek.Domain.Resources.Laptops; using RackPeek.Domain.Resources.Routers; using RackPeek.Domain.Resources.Servers; using RackPeek.Domain.Resources.Services; using RackPeek.Domain.Resources.Switches; using RackPeek.Domain.Resources.SystemResources; using RackPeek.Domain.Resources.UpsUnits; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; namespace RackPeek.Domain.Persistence.Yaml; public class RackPeekConfigMigrationDeserializer : YamlMigrationDeserializer { // List migration functions here public static readonly IReadOnlyList, ValueTask>> ListOfMigrations = new List, ValueTask>> { EnsureSchemaVersionExists, ConvertScalarRunsOnToList, ConvertNicsToPortsV3 }; public RackPeekConfigMigrationDeserializer(IServiceProvider serviceProvider, ILogger> logger) : base(serviceProvider, logger, ListOfMigrations, "version", new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance) .WithCaseInsensitivePropertyMatching() .WithTypeConverter(new StorageSizeYamlConverter()) .WithTypeConverter(new NotesStringYamlConverter()) .WithTypeDiscriminatingNodeDeserializer(options => { options.AddKeyValueTypeDiscriminator("kind", new Dictionary { { Server.KindLabel, typeof(Server) }, { Switch.KindLabel, typeof(Switch) }, { Firewall.KindLabel, typeof(Firewall) }, { Router.KindLabel, typeof(Router) }, { Desktop.KindLabel, typeof(Desktop) }, { Laptop.KindLabel, typeof(Laptop) }, { AccessPoint.KindLabel, typeof(AccessPoint) }, { Ups.KindLabel, typeof(Ups) }, { SystemResource.KindLabel, typeof(SystemResource) }, { Service.KindLabel, typeof(Service) } }); }), new SerializerBuilder() .WithNamingConvention(CamelCaseNamingConvention.Instance) .WithTypeConverter(new StorageSizeYamlConverter()) .WithTypeConverter(new NotesStringYamlConverter()) .ConfigureDefaultValuesHandling( DefaultValuesHandling.OmitNull | DefaultValuesHandling.OmitEmptyCollections )) { } #region Migrations // Define migration functions here public static ValueTask EnsureSchemaVersionExists(IServiceProvider serviceProvider, Dictionary obj) { if (!obj.ContainsKey("version")) obj["version"] = 0; return ValueTask.CompletedTask; } public static ValueTask ConvertScalarRunsOnToList( IServiceProvider serviceProvider, Dictionary obj) { const string key = "runsOn"; if (!obj.TryGetValue("resources", out var resourceListObj)) return ValueTask.CompletedTask; if (resourceListObj is not List resources) return ValueTask.CompletedTask; foreach (var resourceObj in resources) { if (resourceObj is not Dictionary resourceDict) continue; if (!resourceDict.TryGetValue(key, out var runsOn)) continue; switch (runsOn) { case string single: resourceDict[key] = new List { single }; break; case List list: resourceDict[key] = list .OfType() .ToList(); break; case List: // Already correct break; default: throw new InvalidCastException( $"Cannot convert {runsOn.GetType()} to List for resource '{resourceDict}'."); } } return ValueTask.CompletedTask; } public static ValueTask ConvertNicsToPortsV3( IServiceProvider serviceProvider, Dictionary obj) { if (!obj.TryGetValue("resources", out var resourcesObj)) return ValueTask.CompletedTask; if (resourcesObj is not List resources) return ValueTask.CompletedTask; foreach (var resourceObj in resources) { if (resourceObj is not Dictionary resourceDict) continue; if (!resourceDict.TryGetValue("nics", out var nicsObj)) continue; if (nicsObj is not List nics) continue; var ports = new List>(); foreach (var nicObj in nics) { if (nicObj is not Dictionary nicDict) continue; var port = new Dictionary(); if (nicDict.TryGetValue("type", out var type)) port["type"] = type; if (nicDict.TryGetValue("speed", out var speed)) port["speed"] = speed; if (nicDict.TryGetValue("ports", out var portCount)) port["count"] = portCount; ports.Add(port); } resourceDict.Remove("nics"); if (resourceDict.TryGetValue("ports", out var existingPortsObj) && existingPortsObj is List existingPorts) foreach (Dictionary p in ports) existingPorts.Add(p); else resourceDict["ports"] = ports.Cast().ToList(); } obj["version"] = 3; return ValueTask.CompletedTask; } #endregion }