Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions kubernetes-client.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
6 changes: 6 additions & 0 deletions src/KubernetesClient.ModelConverter/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("KubernetesClient")]
[assembly: InternalsVisibleTo("KubernetesClient.Classic")]
[assembly: InternalsVisibleTo("KubernetesClient.Basic")]
[assembly: InternalsVisibleTo("KubernetesClient.Tests")]
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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,
}
}
186 changes: 186 additions & 0 deletions src/KubernetesClient.ModelConverter/AutoMapper/VersionConverter.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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>
4 changes: 1 addition & 3 deletions src/KubernetesClient.Models/KubernetesClient.Models.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
21 changes: 21 additions & 0 deletions src/KubernetesClient.Models/ModelVersionConverter.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading