Skip to content

Commit d1ba0aa

Browse files
fix: yaml with merge (#1332)
* fix: yaml with merge * chore: cleanup
1 parent 66ad77d commit d1ba0aa

File tree

2 files changed

+202
-5
lines changed

2 files changed

+202
-5
lines changed

src/KubernetesClient.Models/KubernetesYaml.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ namespace k8s
1515
/// </summary>
1616
public static class KubernetesYaml
1717
{
18-
private static readonly DeserializerBuilder CommonDeserializerBuilder =
18+
private static DeserializerBuilder CommonDeserializerBuilder =>
1919
new DeserializerBuilder()
2020
.WithNamingConvention(CamelCaseNamingConvention.Instance)
2121
.WithTypeConverter(new IntOrStringYamlConverter())
2222
.WithTypeConverter(new ByteArrayStringYamlConverter())
2323
.WithTypeConverter(new ResourceQuantityYamlConverter())
2424
.WithAttemptingUnquotedStringTypeDeserialization()
2525
.WithOverridesFromJsonPropertyAttributes();
26+
2627
private static readonly IDeserializer StrictDeserializer =
2728
CommonDeserializerBuilder
2829
.WithDuplicateKeyChecking()
@@ -154,15 +155,15 @@ public static List<object> LoadAllFromString(string content, IDictionary<string,
154155
typeMap?.ToList().ForEach(x => mergedTypeMap[x.Key] = x.Value);
155156

156157
var types = new List<Type>();
157-
var parser = new Parser(new StringReader(content));
158+
var parser = new MergingParser(new Parser(new StringReader(content)));
158159
parser.Consume<StreamStart>();
159160
while (parser.Accept<DocumentStart>(out _))
160161
{
161162
var dict = GetDeserializer(strict).Deserialize<Dictionary<object, object>>(parser);
162163
types.Add(mergedTypeMap[dict["apiVersion"] + "/" + dict["kind"]]);
163164
}
164165

165-
parser = new Parser(new StringReader(content));
166+
parser = new MergingParser(new Parser(new StringReader(content)));
166167
parser.Consume<StreamStart>();
167168
var ix = 0;
168169
var results = new List<object>();
@@ -205,12 +206,14 @@ public static string SaveToString<T>(T value)
205206

206207
public static TValue Deserialize<TValue>(string yaml, bool strict = false)
207208
{
208-
return GetDeserializer(strict).Deserialize<TValue>(yaml);
209+
using var reader = new StringReader(yaml);
210+
return GetDeserializer(strict).Deserialize<TValue>(new MergingParser(new Parser(reader)));
209211
}
210212

211213
public static TValue Deserialize<TValue>(Stream yaml, bool strict = false)
212214
{
213-
return GetDeserializer(strict).Deserialize<TValue>(new StreamReader(yaml));
215+
using var reader = new StreamReader(yaml);
216+
return GetDeserializer(strict).Deserialize<TValue>(new MergingParser(new Parser(reader)));
214217
}
215218

216219
public static string SerializeAll(IEnumerable<object> values)

tests/KubernetesClient.Tests/KubernetesYamlTests.cs

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -901,5 +901,199 @@ public void LoadAllFromStringWithTypeMapGenericCRD()
901901
Assert.Equal(12.0, Assert.IsType<float>(v1AlphaFoo.Spec["float"]), 3);
902902
Assert.Equal(content.Replace("\r\n", "\n"), KubernetesYaml.SerializeAll(objs).Replace("\r\n", "\n"));
903903
}
904+
905+
[Fact]
906+
public void LoadFromStringCRDMerge()
907+
{
908+
var content = @"apiVersion: apiextensions.k8s.io/v1
909+
kind: CustomResourceDefinition
910+
metadata:
911+
labels:
912+
eventing.knative.dev/source: ""true""
913+
duck.knative.dev/source: ""true""
914+
knative.dev/crd-install: ""true""
915+
app.kubernetes.io/version: ""1.10.1""
916+
app.kubernetes.io/name: knative-eventing
917+
annotations:
918+
# TODO add schemas and descriptions
919+
registry.knative.dev/eventTypes: |
920+
[
921+
{ ""type"": ""dev.knative.sources.ping"" }
922+
]
923+
name: pingsources.sources.knative.dev
924+
spec:
925+
group: sources.knative.dev
926+
versions:
927+
- &version
928+
name: v1beta2
929+
served: true
930+
storage: false
931+
subresources:
932+
status: {}
933+
schema:
934+
openAPIV3Schema:
935+
type: object
936+
description: 'PingSource describes an event source with a fixed payload produced on a specified cron schedule.'
937+
properties:
938+
spec:
939+
type: object
940+
description: 'PingSourceSpec defines the desired state of the PingSource (from the client).'
941+
properties:
942+
ceOverrides:
943+
description: 'CloudEventOverrides defines overrides to control the output format and modifications of the event sent to the sink.'
944+
type: object
945+
properties:
946+
extensions:
947+
description: 'Extensions specify what attribute are added or overridden on the outbound event. Each `Extensions` key-value pair are set on the event as an attribute extension independently.'
948+
type: object
949+
additionalProperties:
950+
type: string
951+
x-kubernetes-preserve-unknown-fields: true
952+
contentType:
953+
description: 'ContentType is the media type of `data` or `dataBase64`. Default is empty.'
954+
type: string
955+
data:
956+
description: 'Data is data used as the body of the event posted to the sink. Default is empty. Mutually exclusive with `dataBase64`.'
957+
type: string
958+
dataBase64:
959+
description: ""DataBase64 is the base64-encoded string of the actual event's body posted to the sink. Default is empty. Mutually exclusive with `data`.""
960+
type: string
961+
schedule:
962+
description: 'Schedule is the cron schedule. Defaults to `* * * * *`.'
963+
type: string
964+
sink:
965+
description: 'Sink is a reference to an object that will resolve to a uri to use as the sink.'
966+
type: object
967+
properties:
968+
ref:
969+
description: 'Ref points to an Addressable.'
970+
type: object
971+
properties:
972+
apiVersion:
973+
description: 'API version of the referent.'
974+
type: string
975+
kind:
976+
description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
977+
type: string
978+
name:
979+
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'
980+
type: string
981+
namespace:
982+
description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ This is optional field, it gets defaulted to the object holding it if left out.'
983+
type: string
984+
uri:
985+
description: 'URI can be an absolute URL(non-empty scheme and non-empty host) pointing to the target or a relative URI. Relative URIs will be resolved using the base URI retrieved from Ref.'
986+
type: string
987+
timezone:
988+
description: 'Timezone modifies the actual time relative to the specified timezone. Defaults to the system time zone. More general information about time zones: https://www.iana.org/time-zones List of valid timezone values: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones'
989+
type: string
990+
status:
991+
type: object
992+
description: 'PingSourceStatus defines the observed state of PingSource (from the controller).'
993+
properties:
994+
annotations:
995+
description: 'Annotations is additional Status fields for the Resource to save some additional State as well as convey more information to the user. This is roughly akin to Annotations on any k8s resource, just the reconciler conveying richer information outwards.'
996+
type: object
997+
x-kubernetes-preserve-unknown-fields: true
998+
ceAttributes:
999+
description: 'CloudEventAttributes are the specific attributes that the Source uses as part of its CloudEvents.'
1000+
type: array
1001+
items:
1002+
type: object
1003+
properties:
1004+
source:
1005+
description: 'Source is the CloudEvents source attribute.'
1006+
type: string
1007+
type:
1008+
description: 'Type refers to the CloudEvent type attribute.'
1009+
type: string
1010+
conditions:
1011+
description: 'Conditions the latest available observations of a resource''s current state.'
1012+
type: array
1013+
items:
1014+
type: object
1015+
required:
1016+
- type
1017+
- status
1018+
properties:
1019+
lastTransitionTime:
1020+
description: 'LastTransitionTime is the last time the condition transitioned from one status to another. We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic differences (all other things held constant).'
1021+
type: string
1022+
message:
1023+
description: 'A human readable message indicating details about the transition.'
1024+
type: string
1025+
reason:
1026+
description: 'The reason for the condition''s last transition.'
1027+
type: string
1028+
severity:
1029+
description: 'Severity with which to treat failures of this type of condition. When this is not specified, it defaults to Error.'
1030+
type: string
1031+
status:
1032+
description: 'Status of the condition, one of True, False, Unknown.'
1033+
type: string
1034+
type:
1035+
description: 'Type of condition.'
1036+
type: string
1037+
observedGeneration:
1038+
description: 'ObservedGeneration is the ""Generation"" of the Service that was last processed by the controller.'
1039+
type: integer
1040+
format: int64
1041+
sinkUri:
1042+
description: 'SinkURI is the current active sink URI that has been configured for the Source.'
1043+
type: string
1044+
additionalPrinterColumns:
1045+
- name: Sink
1046+
type: string
1047+
jsonPath: .status.sinkUri
1048+
- name: Schedule
1049+
type: string
1050+
jsonPath: .spec.schedule
1051+
- name: Age
1052+
type: date
1053+
jsonPath: .metadata.creationTimestamp
1054+
- name: Ready
1055+
type: string
1056+
jsonPath: "".status.conditions[?(@.type=='Ready')].status""
1057+
- name: Reason
1058+
type: string
1059+
jsonPath: "".status.conditions[?(@.type=='Ready')].reason""
1060+
- !!merge <<: *version
1061+
name: v1
1062+
served: true
1063+
storage: true
1064+
# v1 schema is identical to the v1beta2 schema
1065+
names:
1066+
categories:
1067+
- all
1068+
- knative
1069+
- sources
1070+
kind: PingSource
1071+
plural: pingsources
1072+
singular: pingsource
1073+
scope: Namespaced
1074+
conversion:
1075+
strategy: Webhook
1076+
webhook:
1077+
conversionReviewVersions: [""v1"", ""v1beta1""]
1078+
clientConfig:
1079+
service:
1080+
name: eventing-webhook
1081+
namespace: knative-eventing
1082+
";
1083+
1084+
var obj = KubernetesYaml.Deserialize<V1CustomResourceDefinition>(content);
1085+
1086+
Assert.Equal("pingsources.sources.knative.dev", obj.Metadata.Name);
1087+
Assert.Equal("v1beta2", obj.Spec.Versions[0].Name);
1088+
Assert.Equal("v1", obj.Spec.Versions[1].Name);
1089+
1090+
var obj2 = KubernetesYaml.LoadAllFromString(content);
1091+
1092+
var crd = obj2[0] as V1CustomResourceDefinition;
1093+
1094+
Assert.Equal("pingsources.sources.knative.dev", crd.Metadata.Name);
1095+
Assert.Equal("v1beta2", crd.Spec.Versions[0].Name);
1096+
Assert.Equal("v1", crd.Spec.Versions[1].Name);
1097+
}
9041098
}
9051099
}

0 commit comments

Comments
 (0)