Default type map for YAML deserialization (#599)
* Affects Yaml.LoadAllFrom* methods * Doesn't require user to explicitly pass a mapping for known types * Allows user to specify a mapping for custom types, if required
This commit is contained in:
@@ -39,6 +39,19 @@ namespace k8s
|
|||||||
.WithOverridesFromJsonPropertyAttributes()
|
.WithOverridesFromJsonPropertyAttributes()
|
||||||
.BuildValueSerializer();
|
.BuildValueSerializer();
|
||||||
|
|
||||||
|
private static readonly IDictionary<string, Type> ModelTypeMap = typeof(KubernetesEntityAttribute).Assembly
|
||||||
|
.GetTypes()
|
||||||
|
.Where(t => t.GetCustomAttributes(typeof(KubernetesEntityAttribute), true).Any())
|
||||||
|
.ToDictionary(
|
||||||
|
t =>
|
||||||
|
{
|
||||||
|
var attr = (KubernetesEntityAttribute)t.GetCustomAttribute(
|
||||||
|
typeof(KubernetesEntityAttribute), true);
|
||||||
|
var groupPrefix = string.IsNullOrEmpty(attr.Group) ? "" : $"{attr.Group}/";
|
||||||
|
return $"{groupPrefix}{attr.ApiVersion}/{attr.Kind}";
|
||||||
|
},
|
||||||
|
t => t);
|
||||||
|
|
||||||
public class ByteArrayStringYamlConverter : IYamlTypeConverter
|
public class ByteArrayStringYamlConverter : IYamlTypeConverter
|
||||||
{
|
{
|
||||||
public bool Accepts(Type type)
|
public bool Accepts(Type type)
|
||||||
@@ -84,10 +97,11 @@ namespace k8s
|
|||||||
/// The stream to load the objects from.
|
/// The stream to load the objects from.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="typeMap">
|
/// <param name="typeMap">
|
||||||
/// A map from apiVersion/kind to Type. For example "v1/Pod" -> typeof(V1Pod)
|
/// A map from apiVersion/kind to Type. For example "v1/Pod" -> typeof(V1Pod). If null, a default mapping will
|
||||||
|
/// be used.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>collection of objects</returns>
|
/// <returns>collection of objects</returns>
|
||||||
public static async Task<List<object>> LoadAllFromStreamAsync(Stream stream, Dictionary<string, Type> typeMap)
|
public static async Task<List<object>> LoadAllFromStreamAsync(Stream stream, IDictionary<string, Type> typeMap = null)
|
||||||
{
|
{
|
||||||
var reader = new StreamReader(stream);
|
var reader = new StreamReader(stream);
|
||||||
var content = await reader.ReadToEndAsync().ConfigureAwait(false);
|
var content = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||||
@@ -99,9 +113,12 @@ namespace k8s
|
|||||||
/// Load a collection of objects from a file asynchronously
|
/// Load a collection of objects from a file asynchronously
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fileName">The name of the file to load from.</param>
|
/// <param name="fileName">The name of the file to load from.</param>
|
||||||
/// <param name="typeMap">A map from apiVersion/kind to Type. For example "v1/Pod" -> typeof(V1Pod)</param>
|
/// <param name="typeMap">
|
||||||
|
/// A map from apiVersion/kind to Type. For example "v1/Pod" -> typeof(V1Pod). If null, a default mapping will
|
||||||
|
/// be used.
|
||||||
|
/// </param>
|
||||||
/// <returns>collection of objects</returns>
|
/// <returns>collection of objects</returns>
|
||||||
public static async Task<List<object>> LoadAllFromFileAsync(string fileName, Dictionary<string, Type> typeMap)
|
public static async Task<List<object>> LoadAllFromFileAsync(string fileName, IDictionary<string, Type> typeMap = null)
|
||||||
{
|
{
|
||||||
using (var fileStream = File.OpenRead(fileName))
|
using (var fileStream = File.OpenRead(fileName))
|
||||||
{
|
{
|
||||||
@@ -116,15 +133,15 @@ namespace k8s
|
|||||||
/// The string to load the objects from.
|
/// The string to load the objects from.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="typeMap">
|
/// <param name="typeMap">
|
||||||
/// A map from apiVersion/kind to Type. For example "v1/Pod" -> typeof(V1Pod)
|
/// A map from apiVersion/kind to Type. For example "v1/Pod" -> typeof(V1Pod). If null, a default mapping will
|
||||||
|
/// be used.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>collection of objects</returns>
|
/// <returns>collection of objects</returns>
|
||||||
public static List<object> LoadAllFromString(string content, Dictionary<string, Type> typeMap)
|
public static List<object> LoadAllFromString(string content, IDictionary<string, Type> typeMap = null)
|
||||||
{
|
{
|
||||||
if (typeMap == null)
|
var mergedTypeMap = new Dictionary<string, Type>(ModelTypeMap);
|
||||||
{
|
// merge in KVPs from typeMap, overriding any in ModelTypeMap
|
||||||
throw new ArgumentNullException(nameof(typeMap));
|
typeMap?.ToList().ForEach(x => mergedTypeMap[x.Key] = x.Value);
|
||||||
}
|
|
||||||
|
|
||||||
var types = new List<Type>();
|
var types = new List<Type>();
|
||||||
var parser = new Parser(new StringReader(content));
|
var parser = new Parser(new StringReader(content));
|
||||||
@@ -132,7 +149,7 @@ namespace k8s
|
|||||||
while (parser.Accept<DocumentStart>(out _))
|
while (parser.Accept<DocumentStart>(out _))
|
||||||
{
|
{
|
||||||
var obj = Deserializer.Deserialize<KubernetesObject>(parser);
|
var obj = Deserializer.Deserialize<KubernetesObject>(parser);
|
||||||
types.Add(typeMap[obj.ApiVersion + "/" + obj.Kind]);
|
types.Add(mergedTypeMap[obj.ApiVersion + "/" + obj.Kind]);
|
||||||
}
|
}
|
||||||
|
|
||||||
parser = new Parser(new StringReader(content));
|
parser = new Parser(new StringReader(content));
|
||||||
|
|||||||
@@ -23,11 +23,7 @@ kind: Namespace
|
|||||||
metadata:
|
metadata:
|
||||||
name: ns";
|
name: ns";
|
||||||
|
|
||||||
var types = new Dictionary<string, Type>();
|
var objs = Yaml.LoadAllFromString(content);
|
||||||
types.Add("v1/Pod", typeof(V1Pod));
|
|
||||||
types.Add("v1/Namespace", typeof(V1Namespace));
|
|
||||||
|
|
||||||
var objs = Yaml.LoadAllFromString(content, types);
|
|
||||||
Assert.Equal(2, objs.Count);
|
Assert.Equal(2, objs.Count);
|
||||||
Assert.IsType<V1Pod>(objs[0]);
|
Assert.IsType<V1Pod>(objs[0]);
|
||||||
Assert.IsType<V1Namespace>(objs[1]);
|
Assert.IsType<V1Namespace>(objs[1]);
|
||||||
@@ -35,6 +31,36 @@ metadata:
|
|||||||
Assert.Equal("ns", ((V1Namespace)objs[1]).Metadata.Name);
|
Assert.Equal("ns", ((V1Namespace)objs[1]).Metadata.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CA1812 // Class is used for YAML deserialization tests
|
||||||
|
private class MyPod : V1Pod
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#pragma warning restore CA1812
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LoadAllFromStringWithTypes()
|
||||||
|
{
|
||||||
|
var types = new Dictionary<string, Type>();
|
||||||
|
types.Add("v1/Pod", typeof(MyPod));
|
||||||
|
|
||||||
|
var content = @"apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: ns";
|
||||||
|
|
||||||
|
var objs = Yaml.LoadAllFromString(content, types);
|
||||||
|
Assert.Equal(2, objs.Count);
|
||||||
|
Assert.IsType<MyPod>(objs[0]);
|
||||||
|
Assert.IsType<V1Namespace>(objs[1]);
|
||||||
|
Assert.Equal("foo", ((MyPod)objs[0]).Metadata.Name);
|
||||||
|
Assert.Equal("ns", ((V1Namespace)objs[1]).Metadata.Name);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void LoadAllFromStringWithAdditionalProperties()
|
public void LoadAllFromStringWithAdditionalProperties()
|
||||||
{
|
{
|
||||||
@@ -51,11 +77,7 @@ metadata:
|
|||||||
name: ns
|
name: ns
|
||||||
youDontKnow: Me";
|
youDontKnow: Me";
|
||||||
|
|
||||||
var types = new Dictionary<string, Type>();
|
var objs = Yaml.LoadAllFromString(content);
|
||||||
types.Add("v1/Pod", typeof(V1Pod));
|
|
||||||
types.Add("v1/Namespace", typeof(V1Namespace));
|
|
||||||
|
|
||||||
var objs = Yaml.LoadAllFromString(content, types);
|
|
||||||
Assert.Equal(2, objs.Count);
|
Assert.Equal(2, objs.Count);
|
||||||
Assert.IsType<V1Pod>(objs[0]);
|
Assert.IsType<V1Pod>(objs[0]);
|
||||||
Assert.IsType<V1Namespace>(objs[1]);
|
Assert.IsType<V1Namespace>(objs[1]);
|
||||||
@@ -64,6 +86,33 @@ metadata:
|
|||||||
Assert.Equal("ns", ((V1Namespace)objs[1]).Metadata.Name);
|
Assert.Equal("ns", ((V1Namespace)objs[1]).Metadata.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LoadAllFromStringWithAdditionalPropertiesAndTypes()
|
||||||
|
{
|
||||||
|
var types = new Dictionary<string, Type>();
|
||||||
|
types.Add("v1/Pod", typeof(MyPod));
|
||||||
|
var content = @"apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
namespace: ns
|
||||||
|
youDontKnow: Me
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: ns
|
||||||
|
youDontKnow: Me";
|
||||||
|
|
||||||
|
var objs = Yaml.LoadAllFromString(content, types);
|
||||||
|
Assert.Equal(2, objs.Count);
|
||||||
|
Assert.IsType<MyPod>(objs[0]);
|
||||||
|
Assert.IsType<V1Namespace>(objs[1]);
|
||||||
|
Assert.Equal("foo", ((MyPod)objs[0]).Metadata.Name);
|
||||||
|
Assert.Equal("ns", ((MyPod)objs[0]).Metadata.NamespaceProperty);
|
||||||
|
Assert.Equal("ns", ((V1Namespace)objs[1]).Metadata.Name);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task LoadAllFromFile()
|
public async Task LoadAllFromFile()
|
||||||
{
|
{
|
||||||
@@ -77,9 +126,42 @@ kind: Namespace
|
|||||||
metadata:
|
metadata:
|
||||||
name: ns";
|
name: ns";
|
||||||
|
|
||||||
|
var tempFileName = Path.GetTempFileName();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await File.WriteAllTextAsync(tempFileName, content).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var objs = await Yaml.LoadAllFromFileAsync(tempFileName).ConfigureAwait(false);
|
||||||
|
Assert.Equal(2, objs.Count);
|
||||||
|
Assert.IsType<V1Pod>(objs[0]);
|
||||||
|
Assert.IsType<V1Namespace>(objs[1]);
|
||||||
|
Assert.Equal("foo", ((V1Pod)objs[0]).Metadata.Name);
|
||||||
|
Assert.Equal("ns", ((V1Namespace)objs[1]).Metadata.Name);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (File.Exists(tempFileName))
|
||||||
|
{
|
||||||
|
File.Delete(tempFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task LoadAllFromFileWithTypes()
|
||||||
|
{
|
||||||
var types = new Dictionary<string, Type>();
|
var types = new Dictionary<string, Type>();
|
||||||
types.Add("v1/Pod", typeof(V1Pod));
|
types.Add("v1/Pod", typeof(MyPod));
|
||||||
types.Add("v1/Namespace", typeof(V1Namespace));
|
|
||||||
|
var content = @"apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: ns";
|
||||||
|
|
||||||
var tempFileName = Path.GetTempFileName();
|
var tempFileName = Path.GetTempFileName();
|
||||||
try
|
try
|
||||||
@@ -88,9 +170,9 @@ metadata:
|
|||||||
|
|
||||||
var objs = await Yaml.LoadAllFromFileAsync(tempFileName, types).ConfigureAwait(false);
|
var objs = await Yaml.LoadAllFromFileAsync(tempFileName, types).ConfigureAwait(false);
|
||||||
Assert.Equal(2, objs.Count);
|
Assert.Equal(2, objs.Count);
|
||||||
Assert.IsType<V1Pod>(objs[0]);
|
Assert.IsType<MyPod>(objs[0]);
|
||||||
Assert.IsType<V1Namespace>(objs[1]);
|
Assert.IsType<V1Namespace>(objs[1]);
|
||||||
Assert.Equal("foo", ((V1Pod)objs[0]).Metadata.Name);
|
Assert.Equal("foo", ((MyPod)objs[0]).Metadata.Name);
|
||||||
Assert.Equal("ns", ((V1Namespace)objs[1]).Metadata.Name);
|
Assert.Equal("ns", ((V1Namespace)objs[1]).Metadata.Name);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -131,6 +213,21 @@ metadata:
|
|||||||
Assert.Equal("foo", obj.Metadata.Name);
|
Assert.Equal("foo", obj.Metadata.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void LoadFromStringWithAdditionalPropertiesAndCustomType()
|
||||||
|
{
|
||||||
|
var content = @"apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
youDontKnow: Me
|
||||||
|
";
|
||||||
|
|
||||||
|
var obj = Yaml.LoadFromString<V1Pod>(content);
|
||||||
|
|
||||||
|
Assert.Equal("foo", obj.Metadata.Name);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void LoadNamespacedFromString()
|
public void LoadNamespacedFromString()
|
||||||
{
|
{
|
||||||
@@ -442,7 +539,7 @@ spec:
|
|||||||
var container = Assert.Single(obj.Spec.Containers);
|
var container = Assert.Single(obj.Spec.Containers);
|
||||||
Assert.NotNull(container.Env);
|
Assert.NotNull(container.Env);
|
||||||
var objStr = Yaml.SaveToString(obj);
|
var objStr = Yaml.SaveToString(obj);
|
||||||
Assert.Equal(content, objStr);
|
Assert.Equal(content.Replace("\r\n", "\n"), objStr.Replace("\r\n", "\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
Reference in New Issue
Block a user