From abf6950caec465137fa950ce3bb745d4617e04d4 Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Fri, 3 Mar 2023 09:14:58 -0800 Subject: [PATCH] Proposal: user defined model version explicit converter (#1211) * introduce user defined model version explicit convertor * bump ver --- kubernetes-client.sln | 15 ++ .../AssemblyInfo.cs | 6 + .../AutoMapperModelVersionConverter.cs | 17 ++ .../AutoMapper/KubernetesVersionComparer.cs | 58 ++++++ .../AutoMapper/VersionConverter.cs | 186 ++++++++++++++++++ .../KubernetesClient.ModelConverter.csproj | 17 ++ .../KubernetesClient.Models.csproj | 4 +- .../ModelVersionConverter.cs | 21 ++ .../Versioning/KubernetesVersionComparer.cs | 57 ------ .../Versioning/VersionConverter.cs | 184 ----------------- .../KubernetesClientSourceGenerator.cs | 12 +- .../LibKubernetesGenerator.csproj | 1 + ...=> VersionConverterAutoMapperGenerator.cs} | 27 +-- .../VersionConverterStubGenerator.cs | 70 +++++++ src/nuget.proj | 1 + ....cs => AutoMapperVersionConverterTests.cs} | 16 +- .../KubernetesClient.Tests.csproj | 1 + version.json | 2 +- 18 files changed, 424 insertions(+), 271 deletions(-) create mode 100644 src/KubernetesClient.ModelConverter/AssemblyInfo.cs create mode 100644 src/KubernetesClient.ModelConverter/AutoMapper/AutoMapperModelVersionConverter.cs create mode 100644 src/KubernetesClient.ModelConverter/AutoMapper/KubernetesVersionComparer.cs create mode 100644 src/KubernetesClient.ModelConverter/AutoMapper/VersionConverter.cs create mode 100644 src/KubernetesClient.ModelConverter/KubernetesClient.ModelConverter.csproj create mode 100644 src/KubernetesClient.Models/ModelVersionConverter.cs delete mode 100644 src/KubernetesClient.Models/Versioning/KubernetesVersionComparer.cs delete mode 100644 src/KubernetesClient.Models/Versioning/VersionConverter.cs rename src/LibKubernetesGenerator/{VersionConverterGenerator.cs => VersionConverterAutoMapperGenerator.cs} (80%) create mode 100644 src/LibKubernetesGenerator/VersionConverterStubGenerator.cs rename tests/KubernetesClient.Tests/{VersionConverterTests.cs => AutoMapperVersionConverterTests.cs} (66%) diff --git a/kubernetes-client.sln b/kubernetes-client.sln index 698be91..c11aeeb 100644 --- a/kubernetes-client.sln +++ b/kubernetes-client.sln @@ -71,6 +71,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "workerServiceDependencyInje EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "cp", "examples\cp\cp.csproj", "{CC41E248-2139-427E-8DD4-B047A8924FD2}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KubernetesClient.ModelConverter", "src\KubernetesClient.ModelConverter\KubernetesClient.ModelConverter.csproj", "{F1C8276A-8A60-4362-96B8-A006314446AE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -453,6 +455,18 @@ Global {CC41E248-2139-427E-8DD4-B047A8924FD2}.Release|x64.Build.0 = Release|Any CPU {CC41E248-2139-427E-8DD4-B047A8924FD2}.Release|x86.ActiveCfg = Release|Any CPU {CC41E248-2139-427E-8DD4-B047A8924FD2}.Release|x86.Build.0 = Release|Any CPU + {F1C8276A-8A60-4362-96B8-A006314446AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1C8276A-8A60-4362-96B8-A006314446AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1C8276A-8A60-4362-96B8-A006314446AE}.Debug|x64.ActiveCfg = Debug|Any CPU + {F1C8276A-8A60-4362-96B8-A006314446AE}.Debug|x64.Build.0 = Debug|Any CPU + {F1C8276A-8A60-4362-96B8-A006314446AE}.Debug|x86.ActiveCfg = Debug|Any CPU + {F1C8276A-8A60-4362-96B8-A006314446AE}.Debug|x86.Build.0 = Debug|Any CPU + {F1C8276A-8A60-4362-96B8-A006314446AE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1C8276A-8A60-4362-96B8-A006314446AE}.Release|Any CPU.Build.0 = Release|Any CPU + {F1C8276A-8A60-4362-96B8-A006314446AE}.Release|x64.ActiveCfg = Release|Any CPU + {F1C8276A-8A60-4362-96B8-A006314446AE}.Release|x64.Build.0 = Release|Any CPU + {F1C8276A-8A60-4362-96B8-A006314446AE}.Release|x86.ActiveCfg = Release|Any CPU + {F1C8276A-8A60-4362-96B8-A006314446AE}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -489,6 +503,7 @@ Global {C0759F88-A010-4DEF-BD3B-E183D3328FFC} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40} {05DC8884-AC54-4603-AC25-AE9D9F24E7AE} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40} {CC41E248-2139-427E-8DD4-B047A8924FD2} = {B70AFB57-57C9-46DC-84BE-11B7DDD34B40} + {F1C8276A-8A60-4362-96B8-A006314446AE} = {3D1864AA-1FFC-4512-BB13-46055E410F73} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {049A763A-C891-4E8D-80CF-89DD3E22ADC7} diff --git a/src/KubernetesClient.ModelConverter/AssemblyInfo.cs b/src/KubernetesClient.ModelConverter/AssemblyInfo.cs new file mode 100644 index 0000000..e3f8177 --- /dev/null +++ b/src/KubernetesClient.ModelConverter/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("KubernetesClient")] +[assembly: InternalsVisibleTo("KubernetesClient.Classic")] +[assembly: InternalsVisibleTo("KubernetesClient.Basic")] +[assembly: InternalsVisibleTo("KubernetesClient.Tests")] diff --git a/src/KubernetesClient.ModelConverter/AutoMapper/AutoMapperModelVersionConverter.cs b/src/KubernetesClient.ModelConverter/AutoMapper/AutoMapperModelVersionConverter.cs new file mode 100644 index 0000000..c44190a --- /dev/null +++ b/src/KubernetesClient.ModelConverter/AutoMapper/AutoMapperModelVersionConverter.cs @@ -0,0 +1,17 @@ +using static k8s.Models.ModelVersionConverter; + +namespace k8s.ModelConverter.AutoMapper; + +public class AutoMapperModelVersionConverter : IModelVersionConverter +{ + public static IModelVersionConverter Instance { get; } = new AutoMapperModelVersionConverter(); + + private AutoMapperModelVersionConverter() + { + } + + public TTo Convert(TFrom from) + { + return VersionConverter.Mapper.Map(from); + } +} diff --git a/src/KubernetesClient.ModelConverter/AutoMapper/KubernetesVersionComparer.cs b/src/KubernetesClient.ModelConverter/AutoMapper/KubernetesVersionComparer.cs new file mode 100644 index 0000000..de22d25 --- /dev/null +++ b/src/KubernetesClient.ModelConverter/AutoMapper/KubernetesVersionComparer.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace k8s.ModelConverter.AutoMapper; + +internal class KubernetesVersionComparer : IComparer +{ + public static KubernetesVersionComparer Instance { get; } = new KubernetesVersionComparer(); + private static readonly Regex KubernetesVersionRegex = new Regex(@"^v(?[0-9]+)((?alpha|beta)(?[0-9]+))?$", RegexOptions.Compiled); + + internal KubernetesVersionComparer() + { + } + + public int Compare(string x, string y) + { + if (x == null || y == null) + { + return StringComparer.CurrentCulture.Compare(x, y); + } + + var matchX = KubernetesVersionRegex.Match(x); + if (!matchX.Success) + { + return StringComparer.CurrentCulture.Compare(x, y); + } + + var matchY = KubernetesVersionRegex.Match(y); + if (!matchY.Success) + { + return StringComparer.CurrentCulture.Compare(x, y); + } + + var versionX = ExtractVersion(matchX); + var versionY = ExtractVersion(matchY); + return versionX.CompareTo(versionY); + } + + private Version ExtractVersion(Match match) + { + var major = int.Parse(match.Groups["major"].Value); + if (!Enum.TryParse(match.Groups["stream"].Value, true, out var stream)) + { + stream = Stream.Final; + } + + _ = int.TryParse(match.Groups["minor"].Value, out var minor); + return new Version(major, (int)stream, minor); + } + + private enum Stream + { + Alpha = 1, + Beta = 2, + Final = 3, + } +} diff --git a/src/KubernetesClient.ModelConverter/AutoMapper/VersionConverter.cs b/src/KubernetesClient.ModelConverter/AutoMapper/VersionConverter.cs new file mode 100644 index 0000000..dcd9ef4 --- /dev/null +++ b/src/KubernetesClient.ModelConverter/AutoMapper/VersionConverter.cs @@ -0,0 +1,186 @@ +// WARNING: DO NOT LEAVE COMMENTED CODE IN THIS FILE. IT GETS SCANNED BY GEN PROJECT SO IT CAN EXCLUDE ANY MANUALLY DEFINED MAPS + +using AutoMapper; +#if NET6_0_OR_GREATER +using AutoMapper.Internal; +#endif +using k8s.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace k8s.ModelConverter.AutoMapper; + +/// +/// Provides mappers that converts Kubernetes models between different versions +/// +internal static partial class VersionConverter +{ + static VersionConverter() + { + UpdateMappingConfiguration(expression => { }); + } + + public static IMapper Mapper { get; private set; } + internal static MapperConfiguration MapperConfiguration { get; private set; } + + /// + /// Two level lookup of model types by Kind and then Version + /// + internal static Dictionary> KindVersionsMap { get; private set; } + + public static Type GetTypeForVersion(string version) + { + return GetTypeForVersion(typeof(T), version); + } + + public static Type GetTypeForVersion(Type type, string version) + { + return KindVersionsMap[type.GetKubernetesTypeMetadata().Kind][version]; + } + + public static void UpdateMappingConfiguration(Action configuration) + { + MapperConfiguration = new MapperConfiguration(cfg => + { + GetConfigurations(cfg); + configuration(cfg); + }); + Mapper = MapperConfiguration +#if NET6_0_OR_GREATER + .Internal() +#endif + .CreateMapper(); + KindVersionsMap = MapperConfiguration +#if NET6_0_OR_GREATER + .Internal() +#endif + .GetAllTypeMaps() + .SelectMany(x => new[] { x.Types.SourceType, x.Types.DestinationType }) + .Where(x => x.GetCustomAttribute() != null) + .Select(x => + { + var attr = GetKubernetesEntityAttribute(x); + return new { attr.Kind, attr.ApiVersion, Type = x }; + }) + .GroupBy(x => x.Kind) + .ToDictionary(x => x.Key, kindGroup => kindGroup + .GroupBy(x => x.ApiVersion) + .ToDictionary( + x => x.Key, + versionGroup => versionGroup.Select(x => x.Type).Distinct().Single())); // should only be one type for each Kind/Version combination + } + + public static object ConvertToVersion(object source, string apiVersion) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + var type = source.GetType(); + var attr = GetKubernetesEntityAttribute(type); + if (attr.ApiVersion == apiVersion) + { + return source; + } + + if (!KindVersionsMap.TryGetValue(attr.Kind, out var kindVersions)) + { + throw new InvalidOperationException($"Version converter does not have any registered types for Kind `{attr.Kind}`"); + } + + if (!kindVersions.TryGetValue(apiVersion, out var targetType) || !kindVersions.TryGetValue(attr.ApiVersion, out var sourceType) || + MapperConfiguration +#if NET6_0_OR_GREATER + .Internal() +#endif + .FindTypeMapFor(sourceType, targetType) == null) + { + throw new InvalidOperationException($"There is no conversion mapping registered for Kind `{attr.Kind}` from ApiVersion {attr.ApiVersion} to {apiVersion}"); + } + + return Mapper.Map(source, sourceType, targetType); + } + + private static KubernetesEntityAttribute GetKubernetesEntityAttribute(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var attr = type.GetCustomAttribute(); + if (attr == null) + { + throw new InvalidOperationException($"Type {type} does not have {nameof(KubernetesEntityAttribute)}"); + } + + return attr; + } + + internal static void GetConfigurations(IMapperConfigurationExpression cfg) + { + AutoConfigurations(cfg); + ManualConfigurations(cfg); + } + + private static void ManualConfigurations(IMapperConfigurationExpression cfg) + { + cfg.AllowNullCollections = true; + cfg.DisableConstructorMapping(); + cfg +#if NET6_0_OR_GREATER + .Internal() +#endif + .ForAllMaps((typeMap, opt) => + { + if (!typeof(IKubernetesObject).IsAssignableFrom(typeMap.Types.DestinationType)) + { + return; + } + + var metadata = typeMap.Types.DestinationType.GetKubernetesTypeMetadata(); + opt.ForMember(nameof(IKubernetesObject.ApiVersion), x => x.Ignore()); + opt.ForMember(nameof(IKubernetesObject.Kind), x => x.Ignore()); + opt.AfterMap((from, to) => + { + var obj = (IKubernetesObject)to; + obj.ApiVersion = !string.IsNullOrEmpty(metadata.Group) ? $"{metadata.Group}/{metadata.ApiVersion}" : metadata.ApiVersion; + obj.Kind = metadata.Kind; + }); + }); + + cfg.CreateMap() + .ForMember(dest => dest.Group, opt => opt.Ignore()) + .ForMember(dest => dest.ServiceAccount, opt => opt.Ignore()) + .ForMember(dest => dest.User, opt => opt.Ignore()) + .ReverseMap(); + + cfg.CreateMap() + .ForMember(dest => dest.Group, opt => opt.Ignore()) + .ForMember(dest => dest.ServiceAccount, opt => opt.Ignore()) + .ForMember(dest => dest.User, opt => opt.Ignore()) + .ReverseMap(); + + cfg.CreateMap() + .ForMember(dest => dest.Metrics, opt => opt.Ignore()) + .ForMember(dest => dest.Behavior, opt => opt.Ignore()) + .ReverseMap(); + + + cfg.CreateMap() + .ForMember(dest => dest.Conditions, opt => opt.Ignore()) + .ForMember(dest => dest.CurrentMetrics, opt => opt.Ignore()) + .ReverseMap(); + + cfg.CreateMap() + .ForMember(dest => dest.Name, opt => opt.Ignore()) + .ReverseMap(); + + cfg.CreateMap() + .ForMember(dest => dest.NominalConcurrencyShares, opt => opt.Ignore()) + .ReverseMap(); + } +} diff --git a/src/KubernetesClient.ModelConverter/KubernetesClient.ModelConverter.csproj b/src/KubernetesClient.ModelConverter/KubernetesClient.ModelConverter.csproj new file mode 100644 index 0000000..8a46aa2 --- /dev/null +++ b/src/KubernetesClient.ModelConverter/KubernetesClient.ModelConverter.csproj @@ -0,0 +1,17 @@ + + + net6.0;net7.0 + k8s.ModelConverter + + + + + + + + + + + + + diff --git a/src/KubernetesClient.Models/KubernetesClient.Models.csproj b/src/KubernetesClient.Models/KubernetesClient.Models.csproj index 0c1aeb4..e35a30f 100644 --- a/src/KubernetesClient.Models/KubernetesClient.Models.csproj +++ b/src/KubernetesClient.Models/KubernetesClient.Models.csproj @@ -6,14 +6,12 @@ - + - - diff --git a/src/KubernetesClient.Models/ModelVersionConverter.cs b/src/KubernetesClient.Models/ModelVersionConverter.cs new file mode 100644 index 0000000..e48f397 --- /dev/null +++ b/src/KubernetesClient.Models/ModelVersionConverter.cs @@ -0,0 +1,21 @@ +namespace k8s.Models; + +public static class ModelVersionConverter +{ + public interface IModelVersionConverter + { + TTo Convert(TFrom from); + } + + public static IModelVersionConverter Converter { get; set; } + + internal static TTo Convert(TFrom from) + { + if (Converter == null) + { + throw new InvalidOperationException("Converter is not set"); + } + + return Converter.Convert(from); + } +} diff --git a/src/KubernetesClient.Models/Versioning/KubernetesVersionComparer.cs b/src/KubernetesClient.Models/Versioning/KubernetesVersionComparer.cs deleted file mode 100644 index bd24359..0000000 --- a/src/KubernetesClient.Models/Versioning/KubernetesVersionComparer.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Text.RegularExpressions; - -namespace k8s.Versioning -{ - public class KubernetesVersionComparer : IComparer - { - public static KubernetesVersionComparer Instance { get; } = new KubernetesVersionComparer(); - private static readonly Regex KubernetesVersionRegex = new Regex(@"^v(?[0-9]+)((?alpha|beta)(?[0-9]+))?$", RegexOptions.Compiled); - - internal KubernetesVersionComparer() - { - } - - public int Compare(string x, string y) - { - if (x == null || y == null) - { - return StringComparer.CurrentCulture.Compare(x, y); - } - - var matchX = KubernetesVersionRegex.Match(x); - if (!matchX.Success) - { - return StringComparer.CurrentCulture.Compare(x, y); - } - - var matchY = KubernetesVersionRegex.Match(y); - if (!matchY.Success) - { - return StringComparer.CurrentCulture.Compare(x, y); - } - - var versionX = ExtractVersion(matchX); - var versionY = ExtractVersion(matchY); - return versionX.CompareTo(versionY); - } - - private Version ExtractVersion(Match match) - { - var major = int.Parse(match.Groups["major"].Value); - if (!Enum.TryParse(match.Groups["stream"].Value, true, out var stream)) - { - stream = Stream.Final; - } - - _ = int.TryParse(match.Groups["minor"].Value, out var minor); - return new Version(major, (int)stream, minor); - } - - private enum Stream - { - Alpha = 1, - Beta = 2, - Final = 3, - } - } -} diff --git a/src/KubernetesClient.Models/Versioning/VersionConverter.cs b/src/KubernetesClient.Models/Versioning/VersionConverter.cs deleted file mode 100644 index 4c05934..0000000 --- a/src/KubernetesClient.Models/Versioning/VersionConverter.cs +++ /dev/null @@ -1,184 +0,0 @@ -// WARNING: DO NOT LEAVE COMMENTED CODE IN THIS FILE. IT GETS SCANNED BY GEN PROJECT SO IT CAN EXCLUDE ANY MANUALLY DEFINED MAPS - -using AutoMapper; -#if NET6_0_OR_GREATER -using AutoMapper.Internal; -#endif -using k8s.Models; -using System.Reflection; - -namespace k8s.Versioning -{ - /// - /// Provides mappers that converts Kubernetes models between different versions - /// - public static partial class VersionConverter - { - static VersionConverter() - { - UpdateMappingConfiguration(expression => { }); - } - - public static IMapper Mapper { get; private set; } - internal static MapperConfiguration MapperConfiguration { get; private set; } - - /// - /// Two level lookup of model types by Kind and then Version - /// - internal static Dictionary> KindVersionsMap { get; private set; } - - public static Type GetTypeForVersion(string version) - { - return GetTypeForVersion(typeof(T), version); - } - - public static Type GetTypeForVersion(Type type, string version) - { - return KindVersionsMap[type.GetKubernetesTypeMetadata().Kind][version]; - } - - public static void UpdateMappingConfiguration(Action configuration) - { - MapperConfiguration = new MapperConfiguration(cfg => - { - GetConfigurations(cfg); - configuration(cfg); - }); - Mapper = MapperConfiguration -#if NET6_0_OR_GREATER - .Internal() -#endif - .CreateMapper(); - KindVersionsMap = MapperConfiguration -#if NET6_0_OR_GREATER - .Internal() -#endif - .GetAllTypeMaps() - .SelectMany(x => new[] { x.Types.SourceType, x.Types.DestinationType }) - .Where(x => x.GetCustomAttribute() != null) - .Select(x => - { - var attr = GetKubernetesEntityAttribute(x); - return new { attr.Kind, attr.ApiVersion, Type = x }; - }) - .GroupBy(x => x.Kind) - .ToDictionary(x => x.Key, kindGroup => kindGroup - .GroupBy(x => x.ApiVersion) - .ToDictionary( - x => x.Key, - versionGroup => versionGroup.Select(x => x.Type).Distinct().Single())); // should only be one type for each Kind/Version combination - } - - public static object ConvertToVersion(object source, string apiVersion) - { - if (source == null) - { - throw new ArgumentNullException(nameof(source)); - } - - var type = source.GetType(); - var attr = GetKubernetesEntityAttribute(type); - if (attr.ApiVersion == apiVersion) - { - return source; - } - - if (!KindVersionsMap.TryGetValue(attr.Kind, out var kindVersions)) - { - throw new InvalidOperationException($"Version converter does not have any registered types for Kind `{attr.Kind}`"); - } - - if (!kindVersions.TryGetValue(apiVersion, out var targetType) || !kindVersions.TryGetValue(attr.ApiVersion, out var sourceType) || - MapperConfiguration -#if NET6_0_OR_GREATER - .Internal() -#endif - .FindTypeMapFor(sourceType, targetType) == null) - { - throw new InvalidOperationException($"There is no conversion mapping registered for Kind `{attr.Kind}` from ApiVersion {attr.ApiVersion} to {apiVersion}"); - } - - return Mapper.Map(source, sourceType, targetType); - } - - private static KubernetesEntityAttribute GetKubernetesEntityAttribute(Type type) - { - if (type == null) - { - throw new ArgumentNullException(nameof(type)); - } - - var attr = type.GetCustomAttribute(); - if (attr == null) - { - throw new InvalidOperationException($"Type {type} does not have {nameof(KubernetesEntityAttribute)}"); - } - - return attr; - } - - internal static void GetConfigurations(IMapperConfigurationExpression cfg) - { - AutoConfigurations(cfg); - ManualConfigurations(cfg); - } - - private static void ManualConfigurations(IMapperConfigurationExpression cfg) - { - cfg.AllowNullCollections = true; - cfg.DisableConstructorMapping(); - cfg -#if NET6_0_OR_GREATER - .Internal() -#endif - .ForAllMaps((typeMap, opt) => - { - if (!typeof(IKubernetesObject).IsAssignableFrom(typeMap.Types.DestinationType)) - { - return; - } - - var metadata = typeMap.Types.DestinationType.GetKubernetesTypeMetadata(); - opt.ForMember(nameof(IKubernetesObject.ApiVersion), x => x.Ignore()); - opt.ForMember(nameof(IKubernetesObject.Kind), x => x.Ignore()); - opt.AfterMap((from, to) => - { - var obj = (IKubernetesObject)to; - obj.ApiVersion = !string.IsNullOrEmpty(metadata.Group) ? $"{metadata.Group}/{metadata.ApiVersion}" : metadata.ApiVersion; - obj.Kind = metadata.Kind; - }); - }); - - cfg.CreateMap() - .ForMember(dest => dest.Group, opt => opt.Ignore()) - .ForMember(dest => dest.ServiceAccount, opt => opt.Ignore()) - .ForMember(dest => dest.User, opt => opt.Ignore()) - .ReverseMap(); - - cfg.CreateMap() - .ForMember(dest => dest.Group, opt => opt.Ignore()) - .ForMember(dest => dest.ServiceAccount, opt => opt.Ignore()) - .ForMember(dest => dest.User, opt => opt.Ignore()) - .ReverseMap(); - - cfg.CreateMap() - .ForMember(dest => dest.Metrics, opt => opt.Ignore()) - .ForMember(dest => dest.Behavior, opt => opt.Ignore()) - .ReverseMap(); - - - cfg.CreateMap() - .ForMember(dest => dest.Conditions, opt => opt.Ignore()) - .ForMember(dest => dest.CurrentMetrics, opt => opt.Ignore()) - .ReverseMap(); - - cfg.CreateMap() - .ForMember(dest => dest.Name, opt => opt.Ignore()) - .ReverseMap(); - - cfg.CreateMap() - .ForMember(dest => dest.NominalConcurrencyShares, opt => opt.Ignore()) - .ReverseMap(); - } - } -} diff --git a/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs b/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs index 241b1bb..020c772 100644 --- a/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs +++ b/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs @@ -66,7 +66,8 @@ namespace LibKubernetesGenerator builder.RegisterType(); builder.RegisterType(); builder.RegisterType(); - builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); builder.RegisterType(); var container = builder.Build(); @@ -97,9 +98,14 @@ namespace LibKubernetesGenerator container.Resolve().Generate(swagger, context); } - if (generators.Contains("versionconverter")) + if (generators.Contains("versionconverterstub")) { - container.Resolve().Generate(swagger, context); + container.Resolve().Generate(swagger, context); + } + + if (generators.Contains("versionconverterautomap")) + { + container.Resolve().Generate(swagger, context); } if (generators.Contains("version")) diff --git a/src/LibKubernetesGenerator/LibKubernetesGenerator.csproj b/src/LibKubernetesGenerator/LibKubernetesGenerator.csproj index 83e5a7d..fec0da3 100644 --- a/src/LibKubernetesGenerator/LibKubernetesGenerator.csproj +++ b/src/LibKubernetesGenerator/LibKubernetesGenerator.csproj @@ -2,6 +2,7 @@ netstandard2.0 CA1812 + true diff --git a/src/LibKubernetesGenerator/VersionConverterGenerator.cs b/src/LibKubernetesGenerator/VersionConverterAutoMapperGenerator.cs similarity index 80% rename from src/LibKubernetesGenerator/VersionConverterGenerator.cs rename to src/LibKubernetesGenerator/VersionConverterAutoMapperGenerator.cs index d049e7b..cd7a48e 100644 --- a/src/LibKubernetesGenerator/VersionConverterGenerator.cs +++ b/src/LibKubernetesGenerator/VersionConverterAutoMapperGenerator.cs @@ -10,11 +10,11 @@ using System.Text.RegularExpressions; namespace LibKubernetesGenerator { - internal class VersionConverterGenerator + internal class VersionConverterAutoMapperGenerator { private readonly ClassNameHelper classNameHelper; - public VersionConverterGenerator(ClassNameHelper classNameHelper) + public VersionConverterAutoMapperGenerator(ClassNameHelper classNameHelper) { this.classNameHelper = classNameHelper; } @@ -32,7 +32,7 @@ namespace LibKubernetesGenerator var manualMaps = new List<(string, string)>(); - var manualconverter = context.Compilation.SyntaxTrees.First(s => PathSuffixMath(s.FilePath, "Versioning/VersionConverter.cs")); + var manualconverter = context.Compilation.SyntaxTrees.First(s => PathSuffixMath(s.FilePath, "AutoMapper/VersionConverter.cs")); manualMaps = Regex.Matches(manualconverter.GetText().ToString(), @"\.CreateMap<(?.+?),\s?(?.+?)>") .OfType() .Select(x => (x.Groups["T1"].Value, x.Groups["T2"].Value)) @@ -59,17 +59,13 @@ namespace LibKubernetesGenerator var versionConverterPairs = typePairs.Except(manualMaps).ToList(); - var sbmodel = new StringBuilder(@"// -namespace k8s.Models; -using k8s.Versioning; -"); - var sbversion = new StringBuilder(@"// -namespace k8s.Versioning; using AutoMapper; using k8s.Models; - public static partial class VersionConverter +namespace k8s.ModelConverter.AutoMapper; + +internal static partial class VersionConverter { private static void AutoConfigurations(IMapperConfigurationExpression cfg) { @@ -78,22 +74,11 @@ using k8s.Models; foreach (var (t0, t1) in versionConverterPairs) { - sbmodel.AppendLine($@" - public partial class {t0} - {{ - public static explicit operator {t0}({t1} s) => VersionConverter.Mapper.Map<{t0}>(s); - }} - public partial class {t1} - {{ - public static explicit operator {t1}({t0} s) => VersionConverter.Mapper.Map<{t1}>(s); - }}"); - sbversion.AppendLine($@"cfg.CreateMap<{t0}, {t1}>().ReverseMap();"); } sbversion.AppendLine("}}"); - context.AddSource($"ModelOperators.g.cs", SourceText.From(sbmodel.ToString(), Encoding.UTF8)); context.AddSource($"VersionConverter.g.cs", SourceText.From(sbversion.ToString(), Encoding.UTF8)); } diff --git a/src/LibKubernetesGenerator/VersionConverterStubGenerator.cs b/src/LibKubernetesGenerator/VersionConverterStubGenerator.cs new file mode 100644 index 0000000..19c5d3f --- /dev/null +++ b/src/LibKubernetesGenerator/VersionConverterStubGenerator.cs @@ -0,0 +1,70 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using NSwag; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace LibKubernetesGenerator +{ + internal class VersionConverterStubGenerator + { + private readonly ClassNameHelper classNameHelper; + + public VersionConverterStubGenerator(ClassNameHelper classNameHelper) + { + this.classNameHelper = classNameHelper; + } + + public void Generate(OpenApiDocument swagger, GeneratorExecutionContext context) + { + var allGeneratedModelClassNames = new List(); + + foreach (var kv in swagger.Definitions) + { + var def = kv.Value; + var clz = classNameHelper.GetClassNameForSchemaDefinition(def); + allGeneratedModelClassNames.Add(clz); + } + + var versionRegex = @"(^V|v)[0-9]+((alpha|beta)[0-9]+)?"; + var typePairs = allGeneratedModelClassNames + .OrderBy(x => x) + .Select(x => new + { + Version = Regex.Match(x, versionRegex).Value?.ToLower(), + Kinda = Regex.Replace(x, versionRegex, string.Empty), + Type = x, + }) + .Where(x => !string.IsNullOrEmpty(x.Version)) + .GroupBy(x => x.Kinda) + .Where(x => x.Count() > 1) + .SelectMany(x => + x.SelectMany((value, index) => x.Skip(index + 1), (first, second) => new { first, second })) + .OrderBy(x => x.first.Kinda) + .ThenBy(x => x.first.Version) + .Select(x => (x.first.Type, x.second.Type)) + .ToList(); + + var sbmodel = new StringBuilder(@"// +namespace k8s.Models; +"); + + foreach (var (t0, t1) in typePairs) + { + sbmodel.AppendLine($@" + public partial class {t0} + {{ + public static explicit operator {t0}({t1} s) => ModelVersionConverter.Convert<{t1}, {t0}>(s); + }} + public partial class {t1} + {{ + public static explicit operator {t1}({t0} s) => ModelVersionConverter.Convert<{t0}, {t1}>(s); + }}"); + } + + context.AddSource($"ModelOperators.g.cs", SourceText.From(sbmodel.ToString(), Encoding.UTF8)); + } + } +} diff --git a/src/nuget.proj b/src/nuget.proj index fa0a58d..93ed6ba 100644 --- a/src/nuget.proj +++ b/src/nuget.proj @@ -4,5 +4,6 @@ + diff --git a/tests/KubernetesClient.Tests/VersionConverterTests.cs b/tests/KubernetesClient.Tests/AutoMapperVersionConverterTests.cs similarity index 66% rename from tests/KubernetesClient.Tests/VersionConverterTests.cs rename to tests/KubernetesClient.Tests/AutoMapperVersionConverterTests.cs index bb1b495..e76a49f 100644 --- a/tests/KubernetesClient.Tests/VersionConverterTests.cs +++ b/tests/KubernetesClient.Tests/AutoMapperVersionConverterTests.cs @@ -1,11 +1,12 @@ using AutoMapper; using FluentAssertions; -using k8s.Versioning; +using k8s.ModelConverter.AutoMapper; +using k8s.Models; using Xunit; namespace k8s.Tests { - public class VersionConverterTests + public class AutoMapperVersionConverterTests { [Fact] public void ConfigurationsAreValid() @@ -31,5 +32,16 @@ namespace k8s.Tests { KubernetesVersionComparer.Instance.Compare(x, y).Should().Be(expected); } + + [Fact] + public void ObjectMapAreValid() + { + ModelVersionConverter.Converter = AutoMapperModelVersionConverter.Instance; + var from = new V2HorizontalPodAutoscalerSpec(); // TODO shuold auto load all objects + from.MaxReplicas = 234; + var to = (V1HorizontalPodAutoscalerSpec)from; + + Assert.Equal(from.MaxReplicas, to.MaxReplicas); + } } } diff --git a/tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj b/tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj index 2f220b5..06d045e 100644 --- a/tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj +++ b/tests/KubernetesClient.Tests/KubernetesClient.Tests.csproj @@ -35,5 +35,6 @@ + diff --git a/version.json b/version.json index 0075c0d..90cebca 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.0", + "version": "10.1", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/tags/v\\d+\\.\\d+\\.\\d+"