Skip to content

Commit b32f9d1

Browse files
authored
Quoted non-string values remain quoted after serialization (#442)
* add failing test for environment variable and annotation yaml serialization * simplify the test case * initial tests passing with StringQuotingEmitter * cleanup * expand test * add attribution * run dotnet format
1 parent 4c85bc1 commit b32f9d1

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-0
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.Text.RegularExpressions;
3+
using YamlDotNet.Core;
4+
using YamlDotNet.Serialization;
5+
using YamlDotNet.Serialization.EventEmitters;
6+
7+
namespace k8s
8+
{
9+
// adapted from https://github.com/cloudbase/powershell-yaml/blob/master/powershell-yaml.psm1
10+
public class StringQuotingEmitter : ChainedEventEmitter
11+
{
12+
// Patterns from https://yaml.org/spec/1.2/spec.html#id2804356
13+
private static readonly Regex quotedRegex =
14+
new Regex(@"^(\~|null|true|false|-?(0|[1-9][0-9]*)(\.[0-9]*)?([eE][-+]?[0-9]+)?)?$");
15+
16+
public StringQuotingEmitter(IEventEmitter next) : base(next)
17+
{
18+
}
19+
20+
/// <inheritdoc/>
21+
public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)
22+
{
23+
var typeCode = eventInfo.Source.Value != null
24+
? Type.GetTypeCode(eventInfo.Source.Type)
25+
: TypeCode.Empty;
26+
switch (typeCode)
27+
{
28+
case TypeCode.Char:
29+
if (char.IsDigit((char)eventInfo.Source.Value))
30+
{
31+
eventInfo.Style = ScalarStyle.DoubleQuoted;
32+
}
33+
34+
break;
35+
case TypeCode.String:
36+
var val = eventInfo.Source.Value.ToString();
37+
if (quotedRegex.IsMatch(val))
38+
{
39+
eventInfo.Style = ScalarStyle.DoubleQuoted;
40+
}
41+
else if (val.IndexOf('\n') > -1)
42+
{
43+
eventInfo.Style = ScalarStyle.Literal;
44+
}
45+
46+
break;
47+
}
48+
49+
base.Emit(eventInfo, emitter);
50+
}
51+
}
52+
}

src/KubernetesClient/Yaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ public static string SaveToString<T>(T value)
135135
.WithNamingConvention(new CamelCaseNamingConvention())
136136
.WithTypeInspector(ti => new AutoRestTypeInspector(ti))
137137
.WithTypeConverter(new IntOrStringYamlConverter())
138+
.WithEventEmitter(e => new StringQuotingEmitter(e))
138139
.BuildValueSerializer();
139140
emitter.Emit(new StreamStart());
140141
emitter.Emit(new DocumentStart());

tests/KubernetesClient.Tests/YamlTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,5 +291,35 @@ public void SerializeIntOrString()
291291
var output = Yaml.SaveToString<V1Service>(obj);
292292
Assert.True(ToLines(output).SequenceEqual(ToLines(content)));
293293
}
294+
295+
[Fact]
296+
public void QuotedValuesShouldRemainQuotedAfterSerialization()
297+
{
298+
var content = @"apiVersion: v1
299+
kind: Pod
300+
metadata:
301+
annotations:
302+
custom.annotation: ""null""
303+
name: cpu-demo
304+
spec:
305+
containers:
306+
- env:
307+
- name: PORT
308+
value: ""3000""
309+
- name: NUM_RETRIES
310+
value: ""3""
311+
- name: ENABLE_CACHE
312+
value: ""true""
313+
- name: ENABLE_OTHER
314+
value: ""false""
315+
image: vish/stress
316+
name: cpu-demo-ctr";
317+
var obj = Yaml.LoadFromString<V1Pod>(content);
318+
Assert.NotNull(obj?.Spec?.Containers);
319+
var container = Assert.Single(obj.Spec.Containers);
320+
Assert.NotNull(container.Env);
321+
var objStr = Yaml.SaveToString(obj);
322+
Assert.Equal(content, objStr);
323+
}
294324
}
295325
}

0 commit comments

Comments
 (0)