Proposal: user defined model version explicit converter (#1211)
* introduce user defined model version explicit convertor * bump ver
This commit is contained in:
@@ -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}
|
||||
|
||||
6
src/KubernetesClient.ModelConverter/AssemblyInfo.cs
Normal file
6
src/KubernetesClient.ModelConverter/AssemblyInfo.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("KubernetesClient")]
|
||||
[assembly: InternalsVisibleTo("KubernetesClient.Classic")]
|
||||
[assembly: InternalsVisibleTo("KubernetesClient.Basic")]
|
||||
[assembly: InternalsVisibleTo("KubernetesClient.Tests")]
|
||||
@@ -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, TTo>(TFrom from)
|
||||
{
|
||||
return VersionConverter.Mapper.Map<TTo>(from);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace k8s.ModelConverter.AutoMapper;
|
||||
|
||||
internal class KubernetesVersionComparer : IComparer<string>
|
||||
{
|
||||
public static KubernetesVersionComparer Instance { get; } = new KubernetesVersionComparer();
|
||||
private static readonly Regex KubernetesVersionRegex = new Regex(@"^v(?<major>[0-9]+)((?<stream>alpha|beta)(?<minor>[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<Stream>(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,
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Provides mappers that converts Kubernetes models between different versions
|
||||
/// </summary>
|
||||
internal static partial class VersionConverter
|
||||
{
|
||||
static VersionConverter()
|
||||
{
|
||||
UpdateMappingConfiguration(expression => { });
|
||||
}
|
||||
|
||||
public static IMapper Mapper { get; private set; }
|
||||
internal static MapperConfiguration MapperConfiguration { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Two level lookup of model types by Kind and then Version
|
||||
/// </summary>
|
||||
internal static Dictionary<string, Dictionary<string, Type>> KindVersionsMap { get; private set; }
|
||||
|
||||
public static Type GetTypeForVersion<T>(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<IMapperConfigurationExpression> 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<KubernetesEntityAttribute>() != 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<KubernetesEntityAttribute>();
|
||||
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<V1Subject, V1beta2Subject>()
|
||||
.ForMember(dest => dest.Group, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.ServiceAccount, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.User, opt => opt.Ignore())
|
||||
.ReverseMap();
|
||||
|
||||
cfg.CreateMap<V1Subject, V1beta3Subject>()
|
||||
.ForMember(dest => dest.Group, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.ServiceAccount, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.User, opt => opt.Ignore())
|
||||
.ReverseMap();
|
||||
|
||||
cfg.CreateMap<V1HorizontalPodAutoscalerSpec, V2HorizontalPodAutoscalerSpec>()
|
||||
.ForMember(dest => dest.Metrics, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Behavior, opt => opt.Ignore())
|
||||
.ReverseMap();
|
||||
|
||||
|
||||
cfg.CreateMap<V1HorizontalPodAutoscalerStatus, V2HorizontalPodAutoscalerStatus>()
|
||||
.ForMember(dest => dest.Conditions, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.CurrentMetrics, opt => opt.Ignore())
|
||||
.ReverseMap();
|
||||
|
||||
cfg.CreateMap<V1alpha1ResourceClaim, V1ResourceClaim>()
|
||||
.ForMember(dest => dest.Name, opt => opt.Ignore())
|
||||
.ReverseMap();
|
||||
|
||||
cfg.CreateMap<V1beta2LimitedPriorityLevelConfiguration, V1beta3LimitedPriorityLevelConfiguration>()
|
||||
.ForMember(dest => dest.NominalConcurrencyShares, opt => opt.Ignore())
|
||||
.ReverseMap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<RootNamespace>k8s.ModelConverter</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="Generator" />
|
||||
<AdditionalFiles Include="..\..\swagger.json" Generator="versionconverterautomap,version" />
|
||||
<ProjectReference Include="..\LibKubernetesGenerator\LibKubernetesGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\KubernetesClient.Models\KubernetesClient.Models.csproj" />
|
||||
<PackageReference Include="AutoMapper" Version="12.0.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -6,14 +6,12 @@
|
||||
|
||||
<ItemGroup>
|
||||
<CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="Generator" />
|
||||
<AdditionalFiles Include="..\..\swagger.json" Generator="model,modelext,versionconverter,version" />
|
||||
<AdditionalFiles Include="..\..\swagger.json" Generator="model,modelext,version,versionconverterstub" />
|
||||
<ProjectReference Include="..\LibKubernetesGenerator\LibKubernetesGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Text.Json" Version="7.0.2" />
|
||||
<PackageReference Include="AutoMapper" Version="12.0.0" Condition="'$(TargetFramework)' != 'netstandard2.0'" />
|
||||
<PackageReference Include="AutoMapper" Version="10.1.1" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
|
||||
<PackageReference Include="Fractions" Version="7.2.1" />
|
||||
<PackageReference Include="YamlDotNet" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
21
src/KubernetesClient.Models/ModelVersionConverter.cs
Normal file
21
src/KubernetesClient.Models/ModelVersionConverter.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace k8s.Models;
|
||||
|
||||
public static class ModelVersionConverter
|
||||
{
|
||||
public interface IModelVersionConverter
|
||||
{
|
||||
TTo Convert<TFrom, TTo>(TFrom from);
|
||||
}
|
||||
|
||||
public static IModelVersionConverter Converter { get; set; }
|
||||
|
||||
internal static TTo Convert<TFrom, TTo>(TFrom from)
|
||||
{
|
||||
if (Converter == null)
|
||||
{
|
||||
throw new InvalidOperationException("Converter is not set");
|
||||
}
|
||||
|
||||
return Converter.Convert<TFrom, TTo>(from);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace k8s.Versioning
|
||||
{
|
||||
public class KubernetesVersionComparer : IComparer<string>
|
||||
{
|
||||
public static KubernetesVersionComparer Instance { get; } = new KubernetesVersionComparer();
|
||||
private static readonly Regex KubernetesVersionRegex = new Regex(@"^v(?<major>[0-9]+)((?<stream>alpha|beta)(?<minor>[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<Stream>(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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides mappers that converts Kubernetes models between different versions
|
||||
/// </summary>
|
||||
public static partial class VersionConverter
|
||||
{
|
||||
static VersionConverter()
|
||||
{
|
||||
UpdateMappingConfiguration(expression => { });
|
||||
}
|
||||
|
||||
public static IMapper Mapper { get; private set; }
|
||||
internal static MapperConfiguration MapperConfiguration { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Two level lookup of model types by Kind and then Version
|
||||
/// </summary>
|
||||
internal static Dictionary<string, Dictionary<string, Type>> KindVersionsMap { get; private set; }
|
||||
|
||||
public static Type GetTypeForVersion<T>(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<IMapperConfigurationExpression> 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<KubernetesEntityAttribute>() != 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<KubernetesEntityAttribute>();
|
||||
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<V1Subject, V1beta2Subject>()
|
||||
.ForMember(dest => dest.Group, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.ServiceAccount, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.User, opt => opt.Ignore())
|
||||
.ReverseMap();
|
||||
|
||||
cfg.CreateMap<V1Subject, V1beta3Subject>()
|
||||
.ForMember(dest => dest.Group, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.ServiceAccount, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.User, opt => opt.Ignore())
|
||||
.ReverseMap();
|
||||
|
||||
cfg.CreateMap<V1HorizontalPodAutoscalerSpec, V2HorizontalPodAutoscalerSpec>()
|
||||
.ForMember(dest => dest.Metrics, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Behavior, opt => opt.Ignore())
|
||||
.ReverseMap();
|
||||
|
||||
|
||||
cfg.CreateMap<V1HorizontalPodAutoscalerStatus, V2HorizontalPodAutoscalerStatus>()
|
||||
.ForMember(dest => dest.Conditions, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.CurrentMetrics, opt => opt.Ignore())
|
||||
.ReverseMap();
|
||||
|
||||
cfg.CreateMap<V1alpha1ResourceClaim, V1ResourceClaim>()
|
||||
.ForMember(dest => dest.Name, opt => opt.Ignore())
|
||||
.ReverseMap();
|
||||
|
||||
cfg.CreateMap<V1beta2LimitedPriorityLevelConfiguration, V1beta3LimitedPriorityLevelConfiguration>()
|
||||
.ForMember(dest => dest.NominalConcurrencyShares, opt => opt.Ignore())
|
||||
.ReverseMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,8 @@ namespace LibKubernetesGenerator
|
||||
builder.RegisterType<ModelExtGenerator>();
|
||||
builder.RegisterType<ModelGenerator>();
|
||||
builder.RegisterType<ApiGenerator>();
|
||||
builder.RegisterType<VersionConverterGenerator>();
|
||||
builder.RegisterType<VersionConverterStubGenerator>();
|
||||
builder.RegisterType<VersionConverterAutoMapperGenerator>();
|
||||
builder.RegisterType<VersionGenerator>();
|
||||
|
||||
var container = builder.Build();
|
||||
@@ -97,9 +98,14 @@ namespace LibKubernetesGenerator
|
||||
container.Resolve<ModelExtGenerator>().Generate(swagger, context);
|
||||
}
|
||||
|
||||
if (generators.Contains("versionconverter"))
|
||||
if (generators.Contains("versionconverterstub"))
|
||||
{
|
||||
container.Resolve<VersionConverterGenerator>().Generate(swagger, context);
|
||||
container.Resolve<VersionConverterStubGenerator>().Generate(swagger, context);
|
||||
}
|
||||
|
||||
if (generators.Contains("versionconverterautomap"))
|
||||
{
|
||||
container.Resolve<VersionConverterAutoMapperGenerator>().Generate(swagger, context);
|
||||
}
|
||||
|
||||
if (generators.Contains("version"))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>CA1812</NoWarn>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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<(?<T1>.+?),\s?(?<T2>.+?)>")
|
||||
.OfType<Match>()
|
||||
.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(@"// <auto-generated />
|
||||
namespace k8s.Models;
|
||||
using k8s.Versioning;
|
||||
");
|
||||
|
||||
var sbversion = new StringBuilder(@"// <auto-generated />
|
||||
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));
|
||||
}
|
||||
|
||||
70
src/LibKubernetesGenerator/VersionConverterStubGenerator.cs
Normal file
70
src/LibKubernetesGenerator/VersionConverterStubGenerator.cs
Normal file
@@ -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<string>();
|
||||
|
||||
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(@"// <auto-generated />
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,5 +4,6 @@
|
||||
<ProjectReference Include="KubernetesClient.Basic/KubernetesClient.Basic.csproj" />
|
||||
<ProjectReference Include="KubernetesClient/KubernetesClient.csproj" />
|
||||
<ProjectReference Include="KubernetesClient.Classic/KubernetesClient.Classic.csproj" />
|
||||
<ProjectReference Include="KubernetesClient.ModelConverter/KubernetesClient.ModelConverter.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,5 +35,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\KubernetesClient\KubernetesClient.csproj" />
|
||||
<ProjectReference Include="..\..\src\KubernetesClient.ModelConverter\KubernetesClient.ModelConverter.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -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+"
|
||||
|
||||
Reference in New Issue
Block a user