Skip to content

Support for conversions between models of different versions #420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
6 changes: 6 additions & 0 deletions gen/KubernetesWatchGenerator/KubernetesWatchGenerator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
<None Update="IKubernetes.Watch.cs.template">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="ModelOperators.cs.template">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="VersionConverter.cs.template">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
15 changes: 15 additions & 0 deletions gen/KubernetesWatchGenerator/ModelOperators.cs.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using k8s.Versioning;

namespace k8s.Models
{
{{#.}}
public partial class {{GetTuple . index="0"}}
{
public static explicit operator {{GetTuple . index="0"}}({{GetTuple . index="1"}} s) => VersionConverter.Mapper.Map<{{GetTuple . index="0"}}>(s);
}
public partial class {{GetTuple . index="1"}}
{
public static explicit operator {{GetTuple . index="1"}}({{GetTuple . index="0"}} s) => VersionConverter.Mapper.Map<{{GetTuple . index="1"}}>(s);
}
{{/.}}
}
50 changes: 48 additions & 2 deletions gen/KubernetesWatchGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace KubernetesWatchGenerator
Expand Down Expand Up @@ -101,6 +103,7 @@ static async Task Main(string[] args)
Helpers.Register(nameof(GetApiVersion), GetApiVersion);
Helpers.Register(nameof(GetKind), GetKind);
Helpers.Register(nameof(GetPlural), GetPlural);
Helpers.Register(nameof(GetTuple), GetTuple);

// Generate the Watcher operations
// We skip operations where the name of the class in the C# client could not be determined correctly.
Expand Down Expand Up @@ -134,10 +137,40 @@ static async Task Main(string[] args)
.Select(x => x.Class)
.ToHashSet();

Render.FileToFile("ModelExtensions.cs.template", definitions,
Path.Combine(outputDirectory, "ModelExtensions.cs"));
Render.FileToFile("ModelExtensions.cs.template", definitions, Path.Combine(outputDirectory, "ModelExtensions.cs"));

// generate version converter maps
var allGeneratedModelClassNames = Directory
.EnumerateFiles(Path.Combine(outputDirectory, "Models"))
.Select(Path.GetFileNameWithoutExtension)
.ToList();

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 => (ITuple)Tuple.Create(x.first.Type, x.second.Type))
.ToList();

var versionFile = File.ReadAllText(Path.Combine(outputDirectory, "..", "Versioning", "VersionConverter.cs"));
var manualMaps = Regex.Matches(versionFile, @"\.CreateMap<(?<T1>.+?),\s?(?<T2>.+?)>")
.Select(x => Tuple.Create(x.Groups["T1"].Value, x.Groups["T2"].Value))
.ToList();
var versionConverterPairs = typePairs.Except(manualMaps).ToList();

Render.FileToFile("VersionConverter.cs.template", versionConverterPairs, Path.Combine(outputDirectory, "VersionConverter.cs"));
Render.FileToFile("ModelOperators.cs.template", typePairs, Path.Combine(outputDirectory, "ModelOperators.cs"));

}



static void ToXmlDoc(RenderContext context, IList<object> arguments, IDictionary<string, object> options,
RenderBlock fn, RenderBlock inverse)
{
Expand Down Expand Up @@ -166,6 +199,18 @@ static void ToXmlDoc(RenderContext context, IList<object> arguments, IDictionary
}
}

static void GetTuple(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
{
if (arguments != null && arguments.Count > 0 && arguments[0] is ITuple && options.TryGetValue("index", out var indexObj) && int.TryParse(indexObj?.ToString(), out var index))
{
var pair = (ITuple)arguments[0];
var value = pair[index];
context.Write(value.ToString());
}
}



static void GetClassName(RenderContext context, IList<object> arguments, IDictionary<string, object> options,
RenderBlock fn, RenderBlock inverse)
{
Expand Down Expand Up @@ -343,6 +388,7 @@ static void GetMethodName(RenderContext context, IList<object> arguments, IDicti
}
}


static string GetMethodName(SwaggerOperation watchOperation)
{
var tag = watchOperation.Tags[0];
Expand Down
19 changes: 19 additions & 0 deletions gen/KubernetesWatchGenerator/VersionConverter.cs.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using AutoMapper;
using k8s.Models;

namespace k8s.Versioning
{


public static partial class VersionConverter
{
private static void AutoConfigurations(IMapperConfigurationExpression cfg)
{
{{#.}}
cfg.CreateMap<{{GetTuple . index="0"}}, {{GetTuple . index="1"}}>().ReverseMap();
{{/.}}
}
}


}
49 changes: 49 additions & 0 deletions src/KubernetesClient/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using k8s.Models;
using Microsoft.Rest;
using Microsoft.Rest.TransientFaultHandling;
using Newtonsoft.Json.Converters;
using VersionConverter = k8s.Versioning.VersionConverter;

namespace k8s
{
public static class Extensions
{

public static KubernetesEntityAttribute GetKubernetesTypeMetadata<T>(this T obj) where T : IKubernetesObject =>
obj.GetType().GetKubernetesTypeMetadata();
public static KubernetesEntityAttribute GetKubernetesTypeMetadata(this Type currentType)
{
var attr = currentType.GetCustomAttribute<KubernetesEntityAttribute>();
if (attr == null)
{
throw new InvalidOperationException($"Custom resource must have {nameof(KubernetesEntityAttribute)} applied to it");
}
return attr;
}

public static T Initialize<T>(this T obj) where T : IKubernetesObject
{
var metadata = obj.GetKubernetesTypeMetadata();
obj.ApiVersion = !string.IsNullOrEmpty(metadata.Group) ? $"{metadata.Group}/{metadata.ApiVersion}" : metadata.ApiVersion;
obj.Kind = metadata.Kind ?? obj.GetType().Name;
if (obj is IMetadata<V1ObjectMeta> withMetadata && withMetadata.Metadata == null)
{
withMetadata.Metadata = new V1ObjectMeta();
}

return obj;
}

internal static bool IsValidKubernetesName(this string value) => !Regex.IsMatch(value, "^[a-z0-9-]+$");


}
}
5 changes: 4 additions & 1 deletion src/KubernetesClient/KubernetesClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" Version="7.0.1" Condition="'$(TargetFramework)' == 'net452'" />
<PackageReference Include="AutoMapper" Version="9.0.0" Condition="'$(TargetFramework)' != 'net452'" />
<PackageReference Include="Fractions" Version="4.0.1" />
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="1.1.2" Condition="'$(TargetFramework)' != 'netstandard2.0' and '$(TargetFramework)' != 'netcoreapp2.1'" />
<PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="3.0.0" Condition="'$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'netcoreapp2.1'" />
Expand All @@ -41,6 +43,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net452'">
<Reference Include="System.Net.Http.WebRequest" />
<PackageReference Include="System.ValueTuple" Version="4.4.0" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>

</Project>
65 changes: 65 additions & 0 deletions src/KubernetesClient/Versioning/KubernetesVersionComparitor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace k8s.Versioning
{
public class KubernetesVersionComparer : IComparer<string>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

System.Version?
you may want to reuse it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this to support api call to lower version k8s server?

Yes, there's an open PR for creating and installed CRDs from me. I've ran into this problem when installing into my test cluster which is actually using an older version of CRDs. I'm refactoring that code to be based on this one, and automatically do conversion based on server version. I'm partly blocked on that one until we figure out what to do with #417

{
public static KubernetesVersionComparer Instance { get; private set; }
static readonly Regex _kubernetesVersionRegex;

static KubernetesVersionComparer()
{
_kubernetesVersionRegex = new Regex(@"^v(?<major>[0-9]+)((?<stream>alpha|beta)(?<minor>[0-9]+))?$", RegexOptions.Compiled);
Instance = new KubernetesVersionComparer();
}

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
}
}
}
Loading