diff --git a/src/KubernetesClient/Yaml.cs b/src/KubernetesClient/Yaml.cs index a26f87a..e3bf0d9 100644 --- a/src/KubernetesClient/Yaml.cs +++ b/src/KubernetesClient/Yaml.cs @@ -1,4 +1,8 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using YamlDotNet.Core; @@ -29,6 +33,7 @@ namespace k8s var deserializer = new DeserializerBuilder() .WithNamingConvention(new CamelCaseNamingConvention()) + .WithTypeInspector(ti => new AutoRestTypeInspector(ti)) .Build(); var obj = deserializer.Deserialize(content); return obj; @@ -43,6 +48,7 @@ namespace k8s var serializer = new SerializerBuilder() .WithNamingConvention(new CamelCaseNamingConvention()) + .WithTypeInspector(ti => new AutoRestTypeInspector(ti)) .BuildValueSerializer(); emitter.Emit(new StreamStart()); emitter.Emit(new DocumentStart()); @@ -50,5 +56,88 @@ namespace k8s return stringBuilder.ToString(); } + + private class AutoRestTypeInspector : ITypeInspector + { + private readonly ITypeInspector _inner; + + public AutoRestTypeInspector(ITypeInspector inner) + { + _inner = inner; + } + + public IEnumerable 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(); + 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() where T : Attribute + { + return _inner.GetCustomAttribute(); + } + + public IObjectDescriptor Read(object target) + { + return _inner.Read(target); + } + + public void Write(object target, object value) + { + _inner.Write(target, value); + } + } + } } } diff --git a/tests/KubernetesClient.Tests/YamlTests.cs b/tests/KubernetesClient.Tests/YamlTests.cs index 2da6801..e6a5cf1 100644 --- a/tests/KubernetesClient.Tests/YamlTests.cs +++ b/tests/KubernetesClient.Tests/YamlTests.cs @@ -22,6 +22,48 @@ metadata: 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(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(content); + + Assert.True(obj.Spec.Containers[0].VolumeMounts[0].ReadOnlyProperty); + Assert.False(obj.Spec.Containers[0].VolumeMounts[1].ReadOnlyProperty); + } + [Fact] public void LoadFromStream() { @@ -59,6 +101,85 @@ metadata: 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 ToLines(string s) { using (var reader = new StringReader(s))