Skip to content

Commit 39f5a29

Browse files
maulebbuehler
authored andcommitted
fix: Transpiler inconsistencies for CRDs (#688)
Fix inconsistently thrown errors that system int (or other types) are not correct kubernetes types.
1 parent 9b46f76 commit 39f5a29

File tree

2 files changed

+166
-161
lines changed

2 files changed

+166
-161
lines changed

src/KubeOps.Transpiler/Crds.cs

Lines changed: 128 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public static class Crds
2929
private const string Int64 = "int64";
3030
private const string Float = "float";
3131
private const string Double = "double";
32+
private const string Decimal = "decimal";
3233
private const string DateTime = "date-time";
3334

3435
private static readonly string[] IgnoredToplevelProperties = { "metadata", "apiversion", "kind" };
@@ -77,8 +78,8 @@ public static V1CustomResourceDefinition Transpile(this MetadataLoadContext cont
7778
Description =
7879
type.GetCustomAttributeData<DescriptionAttribute>()?.GetCustomAttributeCtorArg<string>(context, 0),
7980
Properties = type.GetProperties()
80-
.Where(p => !IgnoredToplevelProperties.Contains(p.Name.ToLowerInvariant()))
81-
.Where(p => p.GetCustomAttributeData<IgnoreAttribute>() == null)
81+
.Where(p => !IgnoredToplevelProperties.Contains(p.Name.ToLowerInvariant())
82+
&& p.GetCustomAttributeData<IgnoreAttribute>() == null)
8283
.Select(p => (Name: p.GetPropertyName(context), Schema: context.Map(p)))
8384
.ToDictionary(t => t.Name, t => t.Schema),
8485
});
@@ -105,9 +106,9 @@ public static IEnumerable<V1CustomResourceDefinition> Transpile(
105106
IEnumerable<Type> types)
106107
=> types
107108
.Select(context.GetContextType)
108-
.Where(type => type.Assembly != context.GetContextType<KubernetesEntityAttribute>().Assembly)
109-
.Where(type => type.GetCustomAttributesData<KubernetesEntityAttribute>().Any())
110-
.Where(type => !type.GetCustomAttributesData<IgnoreAttribute>().Any())
109+
.Where(type => type.Assembly != context.GetContextType<KubernetesEntityAttribute>().Assembly
110+
&& type.GetCustomAttributesData<KubernetesEntityAttribute>().Any()
111+
&& !type.GetCustomAttributesData<IgnoreAttribute>().Any())
111112
.Select(type => (Props: context.Transpile(type),
112113
IsStorage: type.GetCustomAttributesData<StorageVersionAttribute>().Any()))
113114
.GroupBy(grp => grp.Props.Metadata.Name)
@@ -278,199 +279,165 @@ private static V1JSONSchemaProps Map(this MetadataLoadContext context, PropertyI
278279

279280
private static V1JSONSchemaProps Map(this MetadataLoadContext context, Type type)
280281
{
281-
if (type == context.GetContextType<V1ObjectMeta>())
282+
if (type.FullName == "System.String")
282283
{
283-
return new V1JSONSchemaProps { Type = Object };
284+
return new V1JSONSchemaProps { Type = String, Nullable = false };
284285
}
285286

286-
if (type.IsArray && type.GetElementType() != null)
287+
if (type.Name == typeof(Nullable<>).Name && type.GenericTypeArguments.Length == 1)
287288
{
288-
var items = context.Map(type.GetElementType()!);
289-
items.Nullable = type.GetElementType()!.IsNullable();
290-
return new V1JSONSchemaProps { Type = Array, Items = items };
289+
var props = context.Map(type.GenericTypeArguments[0]);
290+
props.Nullable = true;
291+
return props;
291292
}
292293

293-
if (!IsSimpleType(type)
294-
&& type.IsGenericType
295-
&& type.GetGenericTypeDefinition() == context.GetContextType(typeof(IDictionary<,>))
296-
&& type.GenericTypeArguments.Contains(context.GetContextType(typeof(ResourceQuantity))))
297-
{
298-
return new V1JSONSchemaProps { Type = Object, XKubernetesPreserveUnknownFields = true };
299-
}
300-
301-
if (!IsSimpleType(type) &&
302-
type.IsGenericType &&
303-
type.GetGenericTypeDefinition() == context.GetContextType(typeof(IEnumerable<>)) &&
304-
type.GenericTypeArguments.Length == 1 &&
305-
type.GenericTypeArguments.Single().IsGenericType &&
306-
type.GenericTypeArguments.Single().GetGenericTypeDefinition() ==
307-
context.GetContextType(typeof(KeyValuePair<,>)))
308-
{
309-
var props = context.Map(type.GenericTypeArguments.Single().GenericTypeArguments[1]);
310-
props.Nullable = type.GenericTypeArguments.Single().GenericTypeArguments[1].IsNullable();
311-
return new V1JSONSchemaProps { Type = Object, AdditionalProperties = props, };
312-
}
313-
314-
if (!IsSimpleType(type)
315-
&& type.IsGenericType
316-
&& type.GetGenericTypeDefinition() == context.GetContextType(typeof(IDictionary<,>)))
317-
{
318-
var props = context.Map(type.GenericTypeArguments[1]);
319-
props.Nullable = type.GenericTypeArguments[1].IsNullable();
320-
return new V1JSONSchemaProps { Type = Object, AdditionalProperties = props, };
321-
}
294+
var interfaces = type.IsInterface
295+
? type.GetInterfaces().Append(type)
296+
: type.GetInterfaces();
322297

323-
if (!IsSimpleType(type) &&
324-
(context.GetContextType<IDictionary>().IsAssignableFrom(type) ||
325-
(type.IsGenericType &&
326-
type.GetGenericArguments().FirstOrDefault()?.IsGenericType == true &&
327-
type.GetGenericArguments().FirstOrDefault()?.GetGenericTypeDefinition() ==
328-
context.GetContextType(typeof(KeyValuePair<,>)))))
329-
{
330-
return new V1JSONSchemaProps { Type = Object, XKubernetesPreserveUnknownFields = true };
331-
}
298+
var interfaceNames = interfaces.Select(i =>
299+
i.IsGenericType
300+
? i.GetGenericTypeDefinition().FullName
301+
: i.FullName);
332302

333-
if (!IsSimpleType(type) && IsGenericEnumerableType(type, out Type? closingType))
303+
if (interfaceNames.Contains(typeof(IDictionary<,>).FullName))
334304
{
335-
var items = context.Map(closingType);
336-
items.Nullable = closingType.IsNullable();
337-
return new V1JSONSchemaProps { Type = Array, Items = items };
338-
}
305+
var dictionaryImpl = interfaces
306+
.First(i => i.IsGenericType
307+
&& i.GetGenericTypeDefinition().FullName == typeof(IDictionary<,>).FullName);
339308

340-
if (type == context.GetContextType<IntstrIntOrString>())
341-
{
342-
return new V1JSONSchemaProps { XKubernetesIntOrString = true };
309+
var addlProps = context.Map(dictionaryImpl.GenericTypeArguments[1]);
310+
return new V1JSONSchemaProps
311+
{
312+
Type = Object,
313+
AdditionalProperties = addlProps,
314+
Nullable = false,
315+
};
343316
}
344317

345-
if (context.GetContextType<IKubernetesObject>().IsAssignableFrom(type) &&
346-
type is { IsAbstract: false, IsInterface: false } &&
347-
type.Assembly == context.GetContextType<IKubernetesObject>().Assembly)
318+
if (interfaceNames.Contains(typeof(IDictionary).FullName))
348319
{
349320
return new V1JSONSchemaProps
350321
{
351322
Type = Object,
352-
Properties = null,
353323
XKubernetesPreserveUnknownFields = true,
354-
XKubernetesEmbeddedResource = true,
324+
Nullable = false,
355325
};
356326
}
357327

358-
if (type == context.GetContextType<int>() ||
359-
type == context.GetContextType<int?>())
360-
{
361-
return new V1JSONSchemaProps { Type = Integer, Format = Int32 };
362-
}
363-
364-
if (type == context.GetContextType<long>() ||
365-
type == context.GetContextType<long?>())
366-
{
367-
return new V1JSONSchemaProps { Type = Integer, Format = Int64 };
368-
}
369-
370-
if (type == context.GetContextType<float>() ||
371-
type == context.GetContextType<float?>())
328+
if (interfaceNames.Contains(typeof(IEnumerable<>).FullName))
372329
{
373-
return new V1JSONSchemaProps { Type = Number, Format = Float };
330+
return context.MapEnumerationType(type, interfaces);
374331
}
375332

376-
if (type == context.GetContextType<double>() ||
377-
type == context.GetContextType<double?>())
333+
switch (type.BaseType?.FullName)
378334
{
379-
return new V1JSONSchemaProps { Type = Number, Format = Double };
335+
case "System.Object":
336+
return context.MapObjectType(type);
337+
case "System.ValueType":
338+
return context.MapValueType(type);
339+
case "System.Enum":
340+
return new V1JSONSchemaProps { Type = String, EnumProperty = Enum.GetNames(type).Cast<object>().ToList() };
341+
default:
342+
throw InvalidType(type);
380343
}
344+
}
381345

382-
if (type == context.GetContextType<string>() ||
383-
type == context.GetContextType<string?>() ||
384-
type.FullName == "System.String")
385-
{
386-
return new V1JSONSchemaProps { Type = String };
346+
private static V1JSONSchemaProps MapObjectType(this MetadataLoadContext context, Type type)
347+
{
348+
switch (type.FullName)
349+
{
350+
case "k8s.Models.V1ObjectMeta":
351+
return new V1JSONSchemaProps { Type = Object, Nullable = false };
352+
case "k8s.Models.IntstrIntOrString":
353+
return new V1JSONSchemaProps { XKubernetesIntOrString = true, Nullable = false };
354+
default:
355+
if (context.GetContextType<IKubernetesObject>().IsAssignableFrom(type) &&
356+
type is { IsAbstract: false, IsInterface: false } &&
357+
type.Assembly == context.GetContextType<IKubernetesObject>().Assembly)
358+
{
359+
return new V1JSONSchemaProps
360+
{
361+
Type = Object,
362+
Properties = null,
363+
XKubernetesPreserveUnknownFields = true,
364+
XKubernetesEmbeddedResource = true,
365+
Nullable = false,
366+
};
367+
}
368+
369+
return new V1JSONSchemaProps
370+
{
371+
Type = Object,
372+
Description =
373+
type.GetCustomAttributeData<DescriptionAttribute>()?.GetCustomAttributeCtorArg<string>(context, 0),
374+
Properties = type
375+
.GetProperties()
376+
.Where(p => p.GetCustomAttributeData<IgnoreAttribute>() == null)
377+
.Select(p => (Name: p.GetPropertyName(context), Schema: context.Map(p)))
378+
.ToDictionary(t => t.Name, t => t.Schema),
379+
Required = type.GetProperties()
380+
.Where(p => p.GetCustomAttributeData<RequiredAttribute>() != null
381+
&& p.GetCustomAttributeData<IgnoreAttribute>() == null)
382+
.Select(p => p.GetPropertyName(context))
383+
.ToList() switch
384+
{
385+
{ Count: > 0 } p => p,
386+
_ => null,
387+
},
388+
};
387389
}
390+
}
388391

389-
if (type == context.GetContextType<bool>() ||
390-
type == context.GetContextType<bool?>())
391-
{
392-
return new V1JSONSchemaProps { Type = Boolean };
393-
}
392+
private static V1JSONSchemaProps MapEnumerationType(this MetadataLoadContext context, Type type, IEnumerable<Type> interfaces)
393+
{
394+
Type? enumerableType = interfaces
395+
.FirstOrDefault(i => i.IsGenericType
396+
&& i.GetGenericTypeDefinition().FullName == typeof(IEnumerable<>).FullName
397+
&& i.GenericTypeArguments.Length == 1);
394398

395-
if (type == context.GetContextType<DateTime>() ||
396-
type == context.GetContextType<DateTime?>())
399+
if (enumerableType == null)
397400
{
398-
return new V1JSONSchemaProps { Type = String, Format = DateTime };
401+
throw InvalidType(type);
399402
}
400403

401-
if (type.IsEnum)
404+
Type listType = enumerableType.GenericTypeArguments[0];
405+
if (listType.IsGenericType && listType.GetGenericTypeDefinition().FullName == typeof(KeyValuePair<,>).FullName)
402406
{
403-
return new V1JSONSchemaProps { Type = String, EnumProperty = Enum.GetNames(type).Cast<object>().ToList() };
407+
var addlProps = context.Map(listType.GenericTypeArguments[1]);
408+
return new V1JSONSchemaProps { Type = Object, AdditionalProperties = addlProps, Nullable = false };
404409
}
405410

406-
if (type.IsGenericType && type.FullName?.Contains("Nullable") == true && type.GetGenericArguments()[0].IsEnum)
407-
{
408-
return new V1JSONSchemaProps
409-
{
410-
Type = String,
411-
EnumProperty = Enum.GetNames(type.GetGenericArguments()[0]).Cast<object>().ToList(),
412-
};
413-
}
411+
var items = context.Map(listType);
412+
return new V1JSONSchemaProps { Type = Array, Items = items, Nullable = false };
413+
}
414414

415-
if (!IsSimpleType(type))
416-
{
417-
return new V1JSONSchemaProps
418-
{
419-
Type = Object,
420-
Description =
421-
type.GetCustomAttributeData<DescriptionAttribute>()?.GetCustomAttributeCtorArg<string>(context, 0),
422-
Properties = type
423-
.GetProperties()
424-
.Where(p => p.GetCustomAttributeData<IgnoreAttribute>() == null)
425-
.Select(p => (Name: p.GetPropertyName(context), Schema: context.Map(p)))
426-
.ToDictionary(t => t.Name, t => t.Schema),
427-
Required = type.GetProperties()
428-
.Where(p => p.GetCustomAttributeData<RequiredAttribute>() != null)
429-
.Where(p => p.GetCustomAttributeData<IgnoreAttribute>() == null)
430-
.Select(p => p.GetPropertyName(context))
431-
.ToList() switch
432-
{
433-
{ Count: > 0 } p => p,
434-
_ => null,
435-
},
436-
};
415+
private static V1JSONSchemaProps MapValueType(this MetadataLoadContext context, Type type)
416+
{
417+
switch (type.FullName)
418+
{
419+
case "System.Int32":
420+
return new V1JSONSchemaProps { Type = Integer, Format = Int32, Nullable = false };
421+
case "System.Int64":
422+
return new V1JSONSchemaProps { Type = Integer, Format = Int64, Nullable = false };
423+
case "System.Single":
424+
return new V1JSONSchemaProps { Type = Number, Format = Float, Nullable = false };
425+
case "System.Double":
426+
return new V1JSONSchemaProps { Type = Number, Format = Double, Nullable = false };
427+
case "System.Decimal":
428+
return new V1JSONSchemaProps { Type = Number, Format = Decimal, Nullable = false };
429+
case "System.Boolean":
430+
return new V1JSONSchemaProps { Type = Boolean, Nullable = false };
431+
case "System.DateTime":
432+
case "System.DateTimeOffset":
433+
return new V1JSONSchemaProps { Type = String, Format = DateTime, Nullable = false };
434+
default:
435+
throw InvalidType(type);
437436
}
437+
}
438438

439-
throw new ArgumentException($"The given type {type.FullName} is not a valid Kubernetes entity.");
440-
441-
bool IsSimpleType(Type t) =>
442-
t.IsPrimitive ||
443-
new[]
444-
{
445-
context.GetContextType<string>(), context.GetContextType<decimal>(),
446-
context.GetContextType<DateTime>(), context.GetContextType<DateTimeOffset>(),
447-
context.GetContextType<TimeSpan>(), context.GetContextType<Guid>(),
448-
}.Contains(t) ||
449-
t.IsEnum ||
450-
Convert.GetTypeCode(t) != TypeCode.Object ||
451-
(t.IsGenericType &&
452-
t.GetGenericTypeDefinition() == context.GetContextType(typeof(Nullable<>)) &&
453-
IsSimpleType(t.GetGenericArguments()[0]));
454-
455-
bool IsGenericEnumerableType(
456-
Type theType,
457-
[NotNullWhen(true)] out Type? enclosingType)
458-
{
459-
if (theType.IsGenericType && context.GetContextType(typeof(IEnumerable<>))
460-
.IsAssignableFrom(theType.GetGenericTypeDefinition()))
461-
{
462-
enclosingType = theType.GetGenericArguments()[0];
463-
return true;
464-
}
465-
466-
enclosingType = theType
467-
.GetInterfaces()
468-
.Where(t => t.IsGenericType &&
469-
t.GetGenericTypeDefinition() == context.GetContextType(typeof(IEnumerable<>)))
470-
.Select(t => t.GetGenericArguments()[0])
471-
.FirstOrDefault();
472-
473-
return enclosingType != null;
474-
}
439+
private static ArgumentException InvalidType(Type type)
440+
{
441+
return new ArgumentException($"The given type {type.FullName} is not a valid Kubernetes entity.");
475442
}
476443
}

0 commit comments

Comments
 (0)