Fix up YAML serialisation to understand AutoRest property renaming (#209)

This commit is contained in:
itowlson
2019-02-12 18:07:00 +13:00
committed by Kubernetes Prow Robot
parent 9bbe42201f
commit 9f1669b0cb
2 changed files with 210 additions and 0 deletions

View File

@@ -1,4 +1,8 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using YamlDotNet.Core; using YamlDotNet.Core;
@@ -29,6 +33,7 @@ namespace k8s
var deserializer = var deserializer =
new DeserializerBuilder() new DeserializerBuilder()
.WithNamingConvention(new CamelCaseNamingConvention()) .WithNamingConvention(new CamelCaseNamingConvention())
.WithTypeInspector(ti => new AutoRestTypeInspector(ti))
.Build(); .Build();
var obj = deserializer.Deserialize<T>(content); var obj = deserializer.Deserialize<T>(content);
return obj; return obj;
@@ -43,6 +48,7 @@ namespace k8s
var serializer = var serializer =
new SerializerBuilder() new SerializerBuilder()
.WithNamingConvention(new CamelCaseNamingConvention()) .WithNamingConvention(new CamelCaseNamingConvention())
.WithTypeInspector(ti => new AutoRestTypeInspector(ti))
.BuildValueSerializer(); .BuildValueSerializer();
emitter.Emit(new StreamStart()); emitter.Emit(new StreamStart());
emitter.Emit(new DocumentStart()); emitter.Emit(new DocumentStart());
@@ -50,5 +56,88 @@ namespace k8s
return stringBuilder.ToString(); return stringBuilder.ToString();
} }
private class AutoRestTypeInspector : ITypeInspector
{
private readonly ITypeInspector _inner;
public AutoRestTypeInspector(ITypeInspector inner)
{
_inner = inner;
}
public IEnumerable<IPropertyDescriptor> GetProperties(Type type, object container)
{
var pds = _inner.GetProperties(type, container);
return pds.Select(pd => TrimPropertySuffix(pd, type)).ToList();
}
public IPropertyDescriptor GetProperty(Type type, object container, string name, bool ignoreUnmatched)
{
try
{
return _inner.GetProperty(type, container, name, ignoreUnmatched);
}
catch (System.Runtime.Serialization.SerializationException)
{
return _inner.GetProperty(type, container, name + "Property", ignoreUnmatched);
}
}
private IPropertyDescriptor TrimPropertySuffix(IPropertyDescriptor pd, Type type)
{
if (!pd.Name.EndsWith("Property"))
{
return pd;
}
// This might have been renamed by AutoRest. See if there is a
// JsonPropertyAttribute.PropertyName and use that instead if there is.
var jpa = pd.GetCustomAttribute<JsonPropertyAttribute>();
if (jpa == null || String.IsNullOrEmpty(jpa.PropertyName))
{
return pd;
}
return new RenamedPropertyDescriptor(pd, jpa.PropertyName);
}
private class RenamedPropertyDescriptor : IPropertyDescriptor
{
private readonly IPropertyDescriptor _inner;
private readonly string _name;
public RenamedPropertyDescriptor(IPropertyDescriptor inner, string name)
{
_inner = inner;
_name = name;
}
public string Name => _name;
public bool CanWrite => _inner.CanWrite;
public Type Type => _inner.Type;
public Type TypeOverride { get => _inner.TypeOverride; set => _inner.TypeOverride = value; }
public int Order { get => _inner.Order; set => _inner.Order = value; }
public ScalarStyle ScalarStyle { get => _inner.ScalarStyle; set => _inner.ScalarStyle = value; }
public T GetCustomAttribute<T>() where T : Attribute
{
return _inner.GetCustomAttribute<T>();
}
public IObjectDescriptor Read(object target)
{
return _inner.Read(target);
}
public void Write(object target, object value)
{
_inner.Write(target, value);
}
}
}
} }
} }

View File

@@ -22,6 +22,48 @@ metadata:
Assert.Equal("foo", obj.Metadata.Name); Assert.Equal("foo", obj.Metadata.Name);
} }
[Fact]
public void LoadNamespacedFromString()
{
var content = @"apiVersion: v1
kind: Pod
metadata:
namespace: bar
name: foo
";
var obj = Yaml.LoadFromString<V1Pod>(content);
Assert.Equal("foo", obj.Metadata.Name);
Assert.Equal("bar", obj.Metadata.NamespaceProperty);
}
[Fact]
public void LoadPropertyNamedReadOnlyFromString()
{
var content = @"apiVersion: v1
kind: Pod
metadata:
namespace: bar
name: foo
spec:
containers:
- image: nginx
volumeMounts:
- name: vm1
mountPath: /vm1
readOnly: true
- name: vm2
mountPath: /vm2
readOnly: false
";
var obj = Yaml.LoadFromString<V1Pod>(content);
Assert.True(obj.Spec.Containers[0].VolumeMounts[0].ReadOnlyProperty);
Assert.False(obj.Spec.Containers[0].VolumeMounts[1].ReadOnlyProperty);
}
[Fact] [Fact]
public void LoadFromStream() public void LoadFromStream()
{ {
@@ -59,6 +101,85 @@ metadata:
name: foo").SequenceEqual(ToLines(yaml))); name: foo").SequenceEqual(ToLines(yaml)));
} }
[Fact]
public void WriteNamespacedToString()
{
var pod = new V1Pod()
{
ApiVersion = "v1",
Kind = "Pod",
Metadata = new V1ObjectMeta()
{
Name = "foo",
NamespaceProperty = "bar"
}
};
var yaml = Yaml.SaveToString(pod);
Assert.True(ToLines(@"apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: bar").SequenceEqual(ToLines(yaml)));
}
[Fact]
public void WritePropertyNamedReadOnlyToString()
{
var pod = new V1Pod()
{
ApiVersion = "v1",
Kind = "Pod",
Metadata = new V1ObjectMeta()
{
Name = "foo",
NamespaceProperty = "bar"
},
Spec = new V1PodSpec()
{
Containers = new[]
{
new V1Container()
{
Image = "nginx",
VolumeMounts = new[]
{
new V1VolumeMount
{
Name = "vm1",
MountPath = "/vm1",
ReadOnlyProperty = true
},
new V1VolumeMount
{
Name = "vm2",
MountPath = "/vm2",
ReadOnlyProperty = false
},
}
}
}
}
};
var yaml = Yaml.SaveToString(pod);
Assert.True(ToLines(@"apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: bar
spec:
containers:
- image: nginx
volumeMounts:
- mountPath: /vm1
name: vm1
readOnly: true
- mountPath: /vm2
name: vm2
readOnly: false").SequenceEqual(ToLines(yaml)));
}
private static IEnumerable<string> ToLines(string s) private static IEnumerable<string> ToLines(string s)
{ {
using (var reader = new StringReader(s)) using (var reader = new StringReader(s))