Skip to content

Commit bf4a704

Browse files
qmfrederikbrendandburns
authored andcommitted
Add a common IKubernetesObject interface, expose type metadata (#152)
* - Implement common interface across all Kubernetes elements - Add ApiVersion, Kind and Group properties * Add IKubernetesObject interface * Regenerate code * Fixup after rebase * Fixes after rebase, make method names consistent
1 parent 0f2832b commit bf4a704

File tree

8 files changed

+1200
-42
lines changed

8 files changed

+1200
-42
lines changed

gen/KubernetesWatchGenerator/IKubernetes.Watch.cs.template

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ namespace k8s
4141
/// <returns>
4242
/// A <see cref="Task"/> which represents the asynchronous operation, and returns a new watcher.
4343
/// </returns>
44-
Task<Watcher<{{ClassName operation}}>> {{MethodName operation}}(
44+
Task<Watcher<{{GetClassName operation}}>> {{GetMethodName operation}}(
4545
{{#operation.actualParameters}}
4646
{{#isRequired}}
4747
{{GetDotNetType type name isRequired}} {{GetDotNetName name}},
@@ -53,7 +53,7 @@ namespace k8s
5353
{{/isRequired}}
5454
{{/operation.actualParameters}}
5555
Dictionary<string, List<string>> customHeaders = null,
56-
Action<WatchEventType, {{ClassName operation}}> onEvent = null,
56+
Action<WatchEventType, {{GetClassName operation}}> onEvent = null,
5757
Action<Exception> onError = null,
5858
CancellationToken cancellationToken = default(CancellationToken));
5959

gen/KubernetesWatchGenerator/Kubernetes.Watch.cs.template

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ namespace k8s
1010
{
1111
{{#.}}
1212
/// <inheritdoc>
13-
public Task<Watcher<{{ClassName operation}}>> {{MethodName operation}}(
13+
public Task<Watcher<{{GetClassName operation}}>> {{GetMethodName operation}}(
1414
{{#operation.actualParameters}}
1515
{{#isRequired}}
1616
{{GetDotNetType type name isRequired}} {{GetDotNetName name}},
@@ -22,12 +22,12 @@ namespace k8s
2222
{{/isRequired}}
2323
{{/operation.actualParameters}}
2424
Dictionary<string, List<string>> customHeaders = null,
25-
Action<WatchEventType, {{ClassName operation}}> onEvent = null,
25+
Action<WatchEventType, {{GetClassName operation}}> onEvent = null,
2626
Action<Exception> onError = null,
2727
CancellationToken cancellationToken = default(CancellationToken))
2828
{
2929
string path = $"{{GetPathExpression .}}";
30-
return WatchObjectAsync<{{ClassName operation}}>(path: path, @continue: @continue, fieldSelector: fieldSelector, includeUninitialized: includeUninitialized, labelSelector: labelSelector, limit: limit, pretty: pretty, timeoutSeconds: timeoutSeconds, resourceVersion: resourceVersion, customHeaders: customHeaders, onEvent: onEvent, onError: onError, cancellationToken: cancellationToken);
30+
return WatchObjectAsync<{{GetClassName operation}}>(path: path, @continue: @continue, fieldSelector: fieldSelector, includeUninitialized: includeUninitialized, labelSelector: labelSelector, limit: limit, pretty: pretty, timeoutSeconds: timeoutSeconds, resourceVersion: resourceVersion, customHeaders: customHeaders, onEvent: onEvent, onError: onError, cancellationToken: cancellationToken);
3131
}
3232

3333
{{/.}}

gen/KubernetesWatchGenerator/KubernetesWatchGenerator.csproj

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
<PropertyGroup>
44
<OutputType>Exe</OutputType>
55
<TargetFramework>netcoreapp2.0</TargetFramework>
6-
</PropertyGroup>
7-
8-
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
96
<LangVersion>latest</LangVersion>
107
</PropertyGroup>
118

@@ -16,10 +13,9 @@
1613
</ItemGroup>
1714

1815
<ItemGroup>
19-
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
20-
</ItemGroup>
21-
22-
<ItemGroup>
16+
<None Update="ModelExtensions.cs.template">
17+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
18+
</None>
2319
<None Update="Kubernetes.Watch.cs.template">
2420
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2521
</None>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace k8s.Models
2+
{
3+
{{#.}}
4+
public partial class {{GetClassName . }} : IKubernetesObject
5+
{
6+
public const string KubeApiVersion = "{{GetApiVersion . }}";
7+
public const string KubeKind = "{{GetKind . }}";
8+
public const string KubeGroup = "{{GetGroup . }}";
9+
}
10+
11+
{{/.}}
12+
}

gen/KubernetesWatchGenerator/Program.cs

Lines changed: 125 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,19 @@ static async Task Main(string[] args)
4545
// Read the spec
4646
var swagger = await SwaggerDocument.FromFileAsync(specPath);
4747

48+
49+
// Register helpers used in the templating.
50+
Helpers.Register(nameof(ToXmlDoc), ToXmlDoc);
51+
Helpers.Register(nameof(GetClassName), GetClassName);
52+
Helpers.Register(nameof(GetMethodName), GetMethodName);
53+
Helpers.Register(nameof(GetDotNetName), GetDotNetName);
54+
Helpers.Register(nameof(GetDotNetType), GetDotNetType);
55+
Helpers.Register(nameof(GetPathExpression), GetPathExpression);
56+
Helpers.Register(nameof(GetGroup), GetGroup);
57+
Helpers.Register(nameof(GetApiVersion), GetApiVersion);
58+
Helpers.Register(nameof(GetKind), GetKind);
59+
60+
// Generate the Watcher operations
4861
// We skip operations where the name of the class in the C# client could not be determined correctly.
4962
// That's usually because there are different version of the same object (e.g. for deployments).
5063
Collection<string> blacklistedOperations = new Collection<string>()
@@ -62,17 +75,31 @@ static async Task Main(string[] args)
6275
&& o.Operation.ActualParameters.Any(p => p.Name == "name")
6376
&& !blacklistedOperations.Contains(o.Operation.OperationId)).ToArray();
6477

65-
// Register helpers used in the templating.
66-
Helpers.Register(nameof(ToXmlDoc), ToXmlDoc);
67-
Helpers.Register(nameof(ClassName), ClassName);
68-
Helpers.Register(nameof(MethodName), MethodName);
69-
Helpers.Register(nameof(GetDotNetName), GetDotNetName);
70-
Helpers.Register(nameof(GetDotNetType), GetDotNetType);
71-
Helpers.Register(nameof(GetPathExpression), GetPathExpression);
72-
7378
// Render.
7479
Render.FileToFile("IKubernetes.Watch.cs.template", watchOperations, $"{outputDirectory}IKubernetes.Watch.cs");
7580
Render.FileToFile("Kubernetes.Watch.cs.template", watchOperations, $"{outputDirectory}Kubernetes.Watch.cs");
81+
82+
// Generate the interface declarations
83+
var skippedTypes = new Collection<string>()
84+
{
85+
"V1beta1Deployment",
86+
"V1beta1DeploymentList",
87+
"V1beta1DeploymentRollback",
88+
"V1beta1DeploymentRollback",
89+
"V1beta1Scale",
90+
"V1beta1PodSecurityPolicy",
91+
"V1beta1PodSecurityPolicyList",
92+
"V1WatchEvent",
93+
};
94+
95+
var definitions = swagger.Definitions.Values
96+
.Where(
97+
d => d.ExtensionData != null
98+
&& d.ExtensionData.ContainsKey("x-kubernetes-group-version-kind")
99+
&& !skippedTypes.Contains(GetClassName(d)));
100+
101+
// Render.
102+
Render.FileToFile("ModelExtensions.cs.template", definitions, $"{outputDirectory}ModelExtensions.cs");
76103
}
77104

78105
static void ToXmlDoc(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
@@ -101,44 +128,92 @@ static void ToXmlDoc(RenderContext context, IList<object> arguments, IDictionary
101128
}
102129
}
103130

104-
static void MethodName(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
131+
static void GetClassName(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
105132
{
106133
if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is SwaggerOperation)
107134
{
108-
context.Write(MethodName(arguments[0] as SwaggerOperation));
135+
context.Write(GetClassName(arguments[0] as SwaggerOperation));
136+
}
137+
else if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4)
138+
{
139+
context.Write(GetClassName(arguments[0] as JsonSchema4));
109140
}
110141
}
111142

112-
static string MethodName(SwaggerOperation watchOperation)
143+
static string GetClassName(SwaggerOperation watchOperation)
113144
{
114-
var tag = watchOperation.Tags[0];
115-
tag = tag.Replace("_", string.Empty);
145+
var groupVersionKind = (Dictionary<string, object>)watchOperation.ExtensionData["x-kubernetes-group-version-kind"];
146+
var group = (string)groupVersionKind["group"];
147+
var kind = (string)groupVersionKind["kind"];
148+
var version = (string)groupVersionKind["version"];
116149

117-
var methodName = ToPascalCase(watchOperation.OperationId);
150+
var className = $"{ToPascalCase(version)}{kind}";
151+
return className;
152+
}
118153

119-
// This tries to remove the version from the method name, e.g. watchCoreV1NamespacedPod => WatchNamespacedPod
120-
methodName = methodName.Replace(tag, string.Empty, StringComparison.OrdinalIgnoreCase);
121-
methodName += "Async";
122-
return methodName;
154+
private static string GetClassName(JsonSchema4 definition)
155+
{
156+
var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"];
157+
var groupVersionKind = (Dictionary<string, object>)groupVersionKindElements[0];
158+
159+
var group = groupVersionKind["group"] as string;
160+
var version = groupVersionKind["version"] as string;
161+
var kind = groupVersionKind["kind"] as string;
162+
163+
return $"{ToPascalCase(version)}{ToPascalCase(kind)}";
164+
}
165+
166+
static void GetKind(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
167+
{
168+
if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4)
169+
{
170+
context.Write(GetKind(arguments[0] as JsonSchema4));
171+
}
172+
}
173+
174+
private static string GetKind(JsonSchema4 definition)
175+
{
176+
var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"];
177+
var groupVersionKind = (Dictionary<string, object>)groupVersionKindElements[0];
178+
179+
return groupVersionKind["kind"] as string;
180+
}
181+
182+
static void GetGroup(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
183+
{
184+
if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4)
185+
{
186+
context.Write(GetGroup(arguments[0] as JsonSchema4));
187+
}
123188
}
124189

125-
static void ClassName(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
190+
private static string GetGroup(JsonSchema4 definition)
191+
{
192+
var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"];
193+
var groupVersionKind = (Dictionary<string, object>)groupVersionKindElements[0];
194+
195+
return groupVersionKind["group"] as string;
196+
}
197+
198+
static void GetMethodName(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
126199
{
127200
if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is SwaggerOperation)
128201
{
129-
context.Write(ClassName(arguments[0] as SwaggerOperation));
202+
context.Write(GetMethodName(arguments[0] as SwaggerOperation));
130203
}
131204
}
132205

133-
static string ClassName(SwaggerOperation watchOperation)
206+
static string GetMethodName(SwaggerOperation watchOperation)
134207
{
135-
var groupVersionKind = (Dictionary<string, object>)watchOperation.ExtensionData["x-kubernetes-group-version-kind"];
136-
var group = (string)groupVersionKind["group"];
137-
var kind = (string)groupVersionKind["kind"];
138-
var version = (string)groupVersionKind["version"];
208+
var tag = watchOperation.Tags[0];
209+
tag = tag.Replace("_", string.Empty);
139210

140-
var className = $"{ToPascalCase(version)}{kind}";
141-
return className;
211+
var methodName = ToPascalCase(watchOperation.OperationId);
212+
213+
// This tries to remove the version from the method name, e.g. watchCoreV1NamespacedPod => WatchNamespacedPod
214+
methodName = methodName.Replace(tag, string.Empty, StringComparison.OrdinalIgnoreCase);
215+
methodName += "Async";
216+
return methodName;
142217
}
143218

144219
static void GetDotNetType(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
@@ -148,11 +223,11 @@ static void GetDotNetType(RenderContext context, IList<object> arguments, IDicti
148223
var parameter = arguments[0] as SwaggerParameter;
149224
context.Write(GetDotNetType(parameter.Type, parameter.Name, parameter.IsRequired));
150225
}
151-
else if(arguments != null && arguments.Count > 2 && arguments[0] != null && arguments[1] != null && arguments[2] != null && arguments[0] is JsonObjectType && arguments[1] is string && arguments[2] is bool)
226+
else if (arguments != null && arguments.Count > 2 && arguments[0] != null && arguments[1] != null && arguments[2] != null && arguments[0] is JsonObjectType && arguments[1] is string && arguments[2] is bool)
152227
{
153228
context.Write(GetDotNetType((JsonObjectType)arguments[0], (string)arguments[1], (bool)arguments[2]));
154229
}
155-
else if(arguments != null && arguments.Count > 0 && arguments[0] != null)
230+
else if (arguments != null && arguments.Count > 0 && arguments[0] != null)
156231
{
157232
context.Write($"ERROR: Expected SwaggerParameter but got {arguments[0].GetType().FullName}");
158233
}
@@ -243,8 +318,29 @@ private static string GetPathExpression(SwaggerOperationDescription operation)
243318
return pathExpression;
244319
}
245320

321+
static void GetApiVersion(RenderContext context, IList<object> arguments, IDictionary<string, object> options, RenderBlock fn, RenderBlock inverse)
322+
{
323+
if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4)
324+
{
325+
context.Write(GetApiVersion(arguments[0] as JsonSchema4));
326+
}
327+
}
328+
329+
private static string GetApiVersion(JsonSchema4 definition)
330+
{
331+
var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"];
332+
var groupVersionKind = (Dictionary<string, object>)groupVersionKindElements[0];
333+
334+
return groupVersionKind["version"] as string;
335+
}
336+
246337
private static string ToPascalCase(string name)
247338
{
339+
if (string.IsNullOrWhiteSpace(name))
340+
{
341+
return name;
342+
}
343+
248344
return char.ToUpper(name[0]) + name.Substring(1);
249345
}
250346
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using Newtonsoft.Json;
2+
3+
namespace k8s
4+
{
5+
/// <summary>
6+
/// Represents a generic Kubernetes object.
7+
/// </summary>
8+
/// <remarks>
9+
/// You can use the <see cref="KubernetesObject"/> if you receive JSON from a Kubernetes API server but
10+
/// are unsure which object the API server is about to return. You can parse the JSON as a <see cref="KubernetesObject"/>
11+
/// and use the <see cref="ApiVersion"/> and <see cref="Kind"/> properties to get basic metadata about any Kubernetes object.
12+
/// You can then
13+
/// </remarks>
14+
public interface IKubernetesObject
15+
{
16+
/// <summary>
17+
/// Gets or sets aPIVersion defines the versioned schema of this
18+
/// representation of an object. Servers should convert recognized
19+
/// schemas to the latest internal value, and may reject unrecognized
20+
/// values. More info:
21+
/// https://git.k8s.io/community/contributors/devel/api-conventions.md#resources
22+
/// </summary>
23+
[JsonProperty(PropertyName = "apiVersion")]
24+
string ApiVersion { get; set; }
25+
26+
/// <summary>
27+
/// Gets or sets kind is a string value representing the REST resource
28+
/// this object represents. Servers may infer this from the endpoint
29+
/// the client submits requests to. Cannot be updated. In CamelCase.
30+
/// More info:
31+
/// https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds
32+
/// </summary>
33+
[JsonProperty(PropertyName = "kind")]
34+
string Kind { get; set; }
35+
}
36+
}

src/KubernetesClient/KubernetesObject.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace k8s
1111
/// and use the <see cref="ApiVersion"/> and <see cref="Kind"/> properties to get basic metadata about any Kubernetes object.
1212
/// You can then
1313
/// </remarks>
14-
public class KubernetesObject
14+
public class KubernetesObject : IKubernetesObject
1515
{
1616
/// <summary>
1717
/// Gets or sets aPIVersion defines the versioned schema of this

0 commit comments

Comments
 (0)