Add interfaces for generated Kubernetes objects (#378)
* Add interfaces for generated Kubernetes objects that can allow working with them without using concrete types. This work is needed for future shared informers / controllers components being developed * Add metadata for plural names. This opens up a path for many generic operations as plural name is needed to construct path
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,3 +9,7 @@ bin/
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
namespace k8s.Models
|
||||
{
|
||||
{{#.}}
|
||||
public partial class {{GetClassName . }} : IKubernetesObject
|
||||
[KubernetesEntity(Group="{{GetGroup . }}", Kind="{{GetKind . }}", ApiVersion="{{GetApiVersion . }}", PluralName={{GetPlural .}})]
|
||||
public partial class {{GetClassName . }} : {{GetInterfaceName . }}
|
||||
{
|
||||
public const string KubeApiVersion = "{{GetApiVersion . }}";
|
||||
public const string KubeKind = "{{GetKind . }}";
|
||||
|
||||
@@ -4,6 +4,7 @@ using Nustache.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
@@ -13,7 +14,11 @@ namespace KubernetesWatchGenerator
|
||||
{
|
||||
class Program
|
||||
{
|
||||
private static HashSet<string> _classesWithValidation;
|
||||
static readonly Dictionary<string, string> ClassNameMap = new Dictionary<string, string>();
|
||||
private static Dictionary<JsonSchema4, string> _schemaToNameMap;
|
||||
private static HashSet<string> _schemaDefinitionsInMultipleGroups;
|
||||
private static Dictionary<string, string> _classNameToPluralMap;
|
||||
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
@@ -45,10 +50,39 @@ namespace KubernetesWatchGenerator
|
||||
|
||||
// gen project removed all watch operations, so here we switch back to unprocessed version
|
||||
swagger = await SwaggerDocument.FromFileAsync(Path.Combine(args[1], "swagger.json.unprocessed"));
|
||||
_schemaToNameMap = swagger.Definitions.ToDictionary(x => x.Value, x => x.Key);
|
||||
_schemaDefinitionsInMultipleGroups = _schemaToNameMap.Values.Select(x =>
|
||||
{
|
||||
var parts = x.Split(".");
|
||||
return new {FullName = x, Name = parts[parts.Length - 1], Version = parts[parts.Length - 2], Group = parts[parts.Length - 3]};
|
||||
})
|
||||
.GroupBy(x => new {x.Name, x.Version})
|
||||
.Where(x => x.Count() > 1)
|
||||
.SelectMany(x => x)
|
||||
.Select(x => x.FullName)
|
||||
.ToHashSet();
|
||||
|
||||
_classNameToPluralMap = swagger.Operations
|
||||
.Where(x => x.Operation.OperationId.StartsWith("list"))
|
||||
.Select(x => { return new {PluralName = x.Path.Split("/").Last(), ClassName = GetClassNameForSchemaDefinition(x.Operation.Responses["200"].ActualResponseSchema)}; })
|
||||
.Distinct()
|
||||
.ToDictionary(x => x.ClassName, x => x.PluralName);
|
||||
|
||||
// dictionary only contains "list" plural maps. assign the same plural names to entities those lists support
|
||||
_classNameToPluralMap = _classNameToPluralMap
|
||||
.Where(x => x.Key.EndsWith("List"))
|
||||
.Select(x =>
|
||||
new {ClassName = x.Key.Remove(x.Key.Length - 4), PluralName = x.Value})
|
||||
.ToDictionary(x => x.ClassName, x => x.PluralName)
|
||||
.Union(_classNameToPluralMap)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
|
||||
|
||||
// Register helpers used in the templating.
|
||||
Helpers.Register(nameof(ToXmlDoc), ToXmlDoc);
|
||||
Helpers.Register(nameof(GetClassName), GetClassName);
|
||||
Helpers.Register(nameof(GetInterfaceName), GetInterfaceName);
|
||||
Helpers.Register(nameof(GetMethodName), GetMethodName);
|
||||
Helpers.Register(nameof(GetDotNetName), GetDotNetName);
|
||||
Helpers.Register(nameof(GetDotNetType), GetDotNetType);
|
||||
@@ -56,6 +90,7 @@ namespace KubernetesWatchGenerator
|
||||
Helpers.Register(nameof(GetGroup), GetGroup);
|
||||
Helpers.Register(nameof(GetApiVersion), GetApiVersion);
|
||||
Helpers.Register(nameof(GetKind), GetKind);
|
||||
Helpers.Register(nameof(GetPlural), GetPlural);
|
||||
|
||||
// Generate the Watcher operations
|
||||
// We skip operations where the name of the class in the C# client could not be determined correctly.
|
||||
@@ -85,6 +120,13 @@ namespace KubernetesWatchGenerator
|
||||
&& d.ExtensionData.ContainsKey("x-kubernetes-group-version-kind")
|
||||
&& !skippedTypes.Contains(GetClassName(d)));
|
||||
|
||||
var modelsDir = Path.Combine(outputDirectory, "Models");
|
||||
_classesWithValidation = Directory.EnumerateFiles(modelsDir)
|
||||
.Select(x => new {Class = Path.GetFileNameWithoutExtension(x), Content = File.ReadAllText(x)})
|
||||
.Where(x => x.Content.Contains("public virtual void Validate()"))
|
||||
.Select(x => x.Class)
|
||||
.ToHashSet();
|
||||
|
||||
Render.FileToFile("ModelExtensions.cs.template", definitions, Path.Combine(outputDirectory, "ModelExtensions.cs"));
|
||||
}
|
||||
|
||||
@@ -148,6 +190,66 @@ namespace KubernetesWatchGenerator
|
||||
|
||||
return GetClassName(groupVersionKind);
|
||||
}
|
||||
private static void GetInterfaceName(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
|
||||
{
|
||||
|
||||
if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4)
|
||||
{
|
||||
context.Write(GetInterfaceName(arguments[0] as JsonSchema4));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static string GetClassNameForSchemaDefinition(JsonSchema4 definition)
|
||||
{
|
||||
if (definition.ExtensionData != null && definition.ExtensionData.ContainsKey("x-kubernetes-group-version-kind"))
|
||||
return GetClassName(definition);
|
||||
|
||||
var schemaName = _schemaToNameMap[definition];
|
||||
|
||||
var parts = schemaName.Split(".");
|
||||
var group = parts[parts.Length - 3];
|
||||
var version = parts[parts.Length - 2];
|
||||
var entityName = parts[parts.Length - 1];
|
||||
if (!_schemaDefinitionsInMultipleGroups.Contains(schemaName))
|
||||
group = null;
|
||||
var className = ToPascalCase($"{group}{version}{entityName}");
|
||||
return className;
|
||||
|
||||
}
|
||||
static string GetInterfaceName(JsonSchema4 definition)
|
||||
{
|
||||
var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"];
|
||||
var groupVersionKind = (Dictionary<string, object>)groupVersionKindElements[0];
|
||||
|
||||
var group = groupVersionKind["group"] as string;
|
||||
var version = groupVersionKind["version"] as string;
|
||||
var kind = groupVersionKind["kind"] as string;
|
||||
var className = GetClassName(definition);
|
||||
var interfaces = new List<string>();
|
||||
interfaces.Add("IKubernetesObject");
|
||||
if (definition.Properties.TryGetValue("metadata", out var metadataProperty))
|
||||
{
|
||||
interfaces.Add($"IMetadata<{GetClassNameForSchemaDefinition(metadataProperty.Reference)}>");
|
||||
}
|
||||
|
||||
if (definition.Properties.TryGetValue("items", out var itemsProperty))
|
||||
{
|
||||
var schema = itemsProperty.Type == JsonObjectType.Object ? itemsProperty.Reference : itemsProperty.Item.Reference;
|
||||
interfaces.Add($"IItems<{GetClassNameForSchemaDefinition(schema)}>");
|
||||
}
|
||||
|
||||
if (definition.Properties.TryGetValue("spec", out var specProperty))
|
||||
{
|
||||
interfaces.Add($"ISpec<{GetClassNameForSchemaDefinition(specProperty.Reference)}>");
|
||||
}
|
||||
|
||||
if(_classesWithValidation.Contains(className))
|
||||
interfaces.Add("IValidate");
|
||||
var result = string.Join(", ", interfaces);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static void GetKind(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
|
||||
{
|
||||
@@ -165,6 +267,24 @@ namespace KubernetesWatchGenerator
|
||||
return groupVersionKind["kind"] as string;
|
||||
}
|
||||
|
||||
static void GetPlural(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
|
||||
{
|
||||
if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4)
|
||||
{
|
||||
var plural = GetPlural(arguments[0] as JsonSchema4);
|
||||
if(plural != null)
|
||||
context.Write($"\"{plural}\"");
|
||||
else
|
||||
context.Write("null");
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPlural(JsonSchema4 definition)
|
||||
{
|
||||
var className = GetClassNameForSchemaDefinition(definition);
|
||||
return _classNameToPluralMap.GetValueOrDefault(className, null);
|
||||
}
|
||||
|
||||
static void GetGroup(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
|
||||
{
|
||||
if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4)
|
||||
|
||||
17
src/KubernetesClient/IItems.cs
Normal file
17
src/KubernetesClient/IItems.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace k8s
|
||||
{
|
||||
/// <summary>
|
||||
/// Kubernetes object that exposes list of objects
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public interface IItems<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets list of objects. More info:
|
||||
/// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md
|
||||
/// </summary>
|
||||
IList<T> Items { get; set; }
|
||||
}
|
||||
}
|
||||
18
src/KubernetesClient/IMetadata.cs
Normal file
18
src/KubernetesClient/IMetadata.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using k8s.Models;
|
||||
|
||||
namespace k8s
|
||||
{
|
||||
/// <summary>
|
||||
/// Kubernetes object that exposes metadata
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of metadata exposed. Usually this will be either
|
||||
/// <see cref="V1ListMeta"/> for lists or <see cref="V1ObjectMeta"/> for objects</typeparam>
|
||||
public interface IMetadata<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets standard object's metadata. More info:
|
||||
/// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
/// </summary>
|
||||
T Metadata { get; set; }
|
||||
}
|
||||
}
|
||||
17
src/KubernetesClient/ISpec.cs
Normal file
17
src/KubernetesClient/ISpec.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace k8s
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Kubernetes object that has a spec
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public interface ISpec<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets specification of the desired behavior of the entity. More
|
||||
/// info:
|
||||
/// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
T Spec { get; set; }
|
||||
}
|
||||
}
|
||||
17
src/KubernetesClient/IStatus.cs
Normal file
17
src/KubernetesClient/IStatus.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace k8s
|
||||
{
|
||||
/// <summary>
|
||||
/// Kubernetes object that exposes status
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of status object</typeparam>
|
||||
public interface IStatus<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets most recently observed status of the object. This data
|
||||
/// may not be up to date. Populated by the system. Read-only. More
|
||||
/// info:
|
||||
/// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
|
||||
/// </summary>
|
||||
T Status { get; set; }
|
||||
}
|
||||
}
|
||||
14
src/KubernetesClient/IValidate.cs
Normal file
14
src/KubernetesClient/IValidate.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace k8s
|
||||
{
|
||||
/// <summary>
|
||||
/// Object that allows self validation
|
||||
/// </summary>
|
||||
public interface IValidate
|
||||
{
|
||||
/// <summary>
|
||||
/// Validate the object.
|
||||
/// </summary>
|
||||
void Validate();
|
||||
|
||||
}
|
||||
}
|
||||
27
src/KubernetesClient/KubernetesEntityAttribute.cs
Normal file
27
src/KubernetesClient/KubernetesEntityAttribute.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace k8s.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes object type in Kubernetes
|
||||
/// </summary>
|
||||
public class KubernetesEntityAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The Kubernetes named schema this object is based on
|
||||
/// </summary>
|
||||
public string Kind { get; set; }
|
||||
/// <summary>
|
||||
/// The Group this Kubernetes type belongs to
|
||||
/// </summary>
|
||||
public string Group { get; set; }
|
||||
/// <summary>
|
||||
/// The API Version this Kubernetes type belongs to
|
||||
/// </summary>
|
||||
public string ApiVersion { get; set; }
|
||||
/// <summary>
|
||||
/// The plural name of the entity
|
||||
/// </summary>
|
||||
public string PluralName { get; set; }
|
||||
}
|
||||
}
|
||||
72
src/KubernetesClient/KubernetesList.cs
Normal file
72
src/KubernetesClient/KubernetesList.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using k8s.Models;
|
||||
using Microsoft.Rest;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace k8s.Models
|
||||
{
|
||||
|
||||
public class KubernetesList<T> : IMetadata<V1ListMeta>, IItems<T> where T : IKubernetesObject
|
||||
{
|
||||
|
||||
public KubernetesList(IList<T> items, string apiVersion = default(string), string kind = default(string), V1ListMeta metadata = default(V1ListMeta))
|
||||
{
|
||||
ApiVersion = apiVersion;
|
||||
Items = items;
|
||||
Kind = kind;
|
||||
Metadata = metadata;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets aPIVersion defines the versioned schema of this
|
||||
/// representation of an object. Servers should convert recognized
|
||||
/// schemas to the latest internal value, and may reject unrecognized
|
||||
/// values. More info:
|
||||
/// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "apiVersion")]
|
||||
public string ApiVersion { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "items")]
|
||||
public IList<T> Items { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets kind is a string value representing the REST resource
|
||||
/// this object represents. Servers may infer this from the endpoint
|
||||
/// the client submits requests to. Cannot be updated. In CamelCase.
|
||||
/// More info:
|
||||
/// https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "kind")]
|
||||
public string Kind { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets standard object's metadata.
|
||||
/// </summary>
|
||||
[JsonProperty(PropertyName = "metadata")]
|
||||
public V1ListMeta Metadata { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Validate the object.
|
||||
/// </summary>
|
||||
/// <exception cref="ValidationException">
|
||||
/// Thrown if validation fails
|
||||
/// </exception>
|
||||
public void Validate()
|
||||
{
|
||||
if (Items == null)
|
||||
{
|
||||
throw new ValidationException(ValidationRules.CannotBeNull, "Items");
|
||||
}
|
||||
if (Items != null)
|
||||
{
|
||||
foreach (var element in Items.OfType<IValidate>())
|
||||
{
|
||||
element.Validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user