Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions examples/clientset/Program.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// See https://aka.ms/new-console-template for more information
using k8s;
using k8s.ClientSets;
using k8s;
using k8s.Models;
using k8s.ClientSets;
using System.Threading.Tasks;

namespace clientset
Expand All @@ -13,7 +12,7 @@ private static async Task Main(string[] args)
var config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
var client = new Kubernetes(config);

ClientSet clientSet = new ClientSet(client);
var clientSet = new ClientSet(client);
var list = await clientSet.CoreV1.Pod.ListAsync("default").ConfigureAwait(false);
foreach (var item in list)
{
Expand All @@ -22,6 +21,12 @@ private static async Task Main(string[] args)

var pod = await clientSet.CoreV1.Pod.GetAsync("test", "default").ConfigureAwait(false);
System.Console.WriteLine(pod?.Metadata?.Name);

var watch = clientSet.CoreV1.Pod.WatchListAsync("default");
await foreach (var (_, item) in watch.ConfigureAwait(false))
{
System.Console.WriteLine(item.Metadata.Name);
}
}
}
}
}
11 changes: 5 additions & 6 deletions examples/watch/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using k8s;
using k8s.Models;
using System;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -8,9 +7,10 @@

IKubernetes client = new Kubernetes(config);

var podlistResp = client.CoreV1.ListNamespacedPodWithHttpMessagesAsync("default", watch: true);
var podlistResp = client.CoreV1.WatchListNamespacedPodAsync("default");

// C# 8 required https://docs.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8
await foreach (var (type, item) in podlistResp.WatchAsync<V1Pod, V1PodList>().ConfigureAwait(false))
await foreach (var (type, item) in podlistResp.ConfigureAwait(false))
{
Console.WriteLine("==on watch event==");
Console.WriteLine(type);
Expand All @@ -22,8 +22,7 @@
void WatchUsingCallback(IKubernetes client)
#pragma warning restore CS8321 // Remove unused private members
{
var podlistResp = client.CoreV1.ListNamespacedPodWithHttpMessagesAsync("default", watch: true);
using (podlistResp.Watch<V1Pod, V1PodList>((type, item) =>
using (var podlistResp = client.CoreV1.WatchListNamespacedPod("default", onEvent: (type, item) =>
{
Console.WriteLine("==on watch event==");
Console.WriteLine(type);
Expand All @@ -37,4 +36,4 @@ void WatchUsingCallback(IKubernetes client)
Console.CancelKeyPress += (sender, eventArgs) => ctrlc.Set();
ctrlc.Wait();
}
}
}
2 changes: 1 addition & 1 deletion src/KubernetesClient/WatcherExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace k8s
{
public static class WatcherExt
internal static class WatcherExt
{
/// <summary>
/// create a watch object from a call to api server with watch=true
Expand Down
22 changes: 21 additions & 1 deletion src/LibKubernetesGenerator/ParamHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Scriban.Runtime;
using System;
using System.Linq;
using System.Collections.Generic;

namespace LibKubernetesGenerator
{
Expand All @@ -21,6 +22,8 @@ public void RegisterHelper(ScriptObject scriptObject)
{
scriptObject.Import(nameof(GetModelCtorParam), new Func<JsonSchema, string>(GetModelCtorParam));
scriptObject.Import(nameof(IfParamContains), IfParamContains);
scriptObject.Import(nameof(FilterParameters), FilterParameters);
scriptObject.Import(nameof(GetParameterValueForWatch), new Func<OpenApiParameter, bool, string, string>(GetParameterValueForWatch));
}

public static bool IfParamContains(OpenApiOperation operation, string name)
Expand All @@ -39,6 +42,23 @@ public static bool IfParamContains(OpenApiOperation operation, string name)
return found;
}

public static IEnumerable<OpenApiParameter> FilterParameters(OpenApiOperation operation, string excludeParam)
{
return operation.Parameters.Where(p => p.Name != excludeParam);
}

public string GetParameterValueForWatch(OpenApiParameter parameter, bool watch, string init = "false")
{
if (parameter.Name == "watch")
{
return watch ? "true" : "false";
}
else
{
return generalNameHelper.GetDotNetNameOpenApiParameter(parameter, init);
}
}

public string GetModelCtorParam(JsonSchema schema)
{
return string.Join(", ", schema.Properties.Values
Expand All @@ -57,4 +77,4 @@ public string GetModelCtorParam(JsonSchema schema)
}));
}
}
}
}
34 changes: 32 additions & 2 deletions src/LibKubernetesGenerator/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ private string GetDotNetType(JsonSchema schema, JsonSchemaProperty parent)
return $"IDictionary<string, {GetDotNetType(schema.AdditionalPropertiesSchema, parent)}>";
}


if (schema?.Reference != null)
{
return classNameHelper.GetClassNameForSchemaDefinition(schema.Reference);
Expand Down Expand Up @@ -245,6 +244,16 @@ string toType()
}

break;
case "T":
var itemType = TryGetItemTypeFromSchema(response);
if (itemType != null)
{
return itemType;
}

break;
case "TList":
return t;
}

return t;
Expand Down Expand Up @@ -283,5 +292,26 @@ public static bool IfType(JsonSchemaProperty property, string type)

return false;
}

private string TryGetItemTypeFromSchema(OpenApiResponse response)
{
var listSchema = response?.Schema?.Reference;
if (listSchema?.Properties?.TryGetValue("items", out var itemsProperty) != true)
Comment on lines +298 to +299
Copy link
Preview

Copilot AI Sep 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code attempts to access Properties on Reference which is incorrect. The Reference property is a string reference to a schema definition, not the actual schema object. This should be accessing the resolved schema object instead of the reference.

Suggested change
var listSchema = response?.Schema?.Reference;
if (listSchema?.Properties?.TryGetValue("items", out var itemsProperty) != true)
// Resolve the schema reference to the actual schema object
var referencedSchema = response?.Schema?.ActualSchema;
if (referencedSchema?.Properties?.TryGetValue("items", out var itemsProperty) != true)

Copilot uses AI. Check for mistakes.

{
return null;
}
Comment on lines +298 to +302
Copy link
Preview

Copilot AI Sep 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method attempts to access Properties on a JsonReference object, but JsonReference doesn't have a Properties property. This will always return null. You should resolve the reference first to get the actual schema.

Suggested change
var listSchema = response?.Schema?.Reference;
if (listSchema?.Properties?.TryGetValue("items", out var itemsProperty) != true)
{
return null;
}
// Resolve the reference to the actual schema before accessing Properties
var referencedSchema = response?.Schema?.Reference as JsonSchema;
if (referencedSchema == null)
{
return null;
}
if (!referencedSchema.Properties.TryGetValue("items", out var itemsProperty))
{
return null;
}

Copilot uses AI. Check for mistakes.


if (itemsProperty.Reference != null)
{
return classNameHelper.GetClassNameForSchemaDefinition(itemsProperty.Reference);
}

if (itemsProperty.Item?.Reference != null)
{
return classNameHelper.GetClassNameForSchemaDefinition(itemsProperty.Item.Reference);
}

return null;
}
}
}
}
83 changes: 74 additions & 9 deletions src/LibKubernetesGenerator/templates/Client.cs.template
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ public partial class {{name}}Client : ResourceClient
}

{{for api in apis }}
{{~ $filteredParams = FilterParameters api.operation "watch" ~}}
/// <summary>
/// {{ToXmlDoc api.operation.description}}
/// </summary>
{{ for parameter in api.operation.parameters}}
{{ for parameter in $filteredParams}}
/// <param name="{{GetDotNetNameOpenApiParameter parameter "false"}}">
/// {{ToXmlDoc parameter.description}}
/// </param>
Expand All @@ -29,15 +30,15 @@ public partial class {{name}}Client : ResourceClient
/// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation.
/// </param>
public async Task{{GetReturnType api.operation "<>"}} {{GetActionName api.operation name "Async"}}(
{{ for parameter in api.operation.parameters}}
{{ for parameter in $filteredParams}}
{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}},
{{ end }}
CancellationToken cancellationToken = default(CancellationToken))
{
{{if IfReturnType api.operation "stream"}}
var _result = await Client.{{group}}.{{GetOperationId api.operation "WithHttpMessagesAsync"}}(
{{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}},
{{GetParameterValueForWatch parameter false}},
{{end}}
null,
cancellationToken);
Expand All @@ -47,7 +48,7 @@ public partial class {{name}}Client : ResourceClient
{{if IfReturnType api.operation "obj"}}
using (var _result = await Client.{{group}}.{{GetOperationId api.operation "WithHttpMessagesAsync"}}(
{{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}},
{{GetParameterValueForWatch parameter false}},
{{end}}
null,
cancellationToken).ConfigureAwait(false))
Expand All @@ -58,7 +59,7 @@ public partial class {{name}}Client : ResourceClient
{{if IfReturnType api.operation "void"}}
using (var _result = await Client.{{group}}.{{GetOperationId api.operation "WithHttpMessagesAsync"}}(
{{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}},
{{GetParameterValueForWatch parameter false}},
{{end}}
null,
cancellationToken).ConfigureAwait(false))
Expand All @@ -71,7 +72,7 @@ public partial class {{name}}Client : ResourceClient
/// <summary>
/// {{ToXmlDoc api.operation.description}}
/// </summary>
{{ for parameter in api.operation.parameters}}
{{ for parameter in $filteredParams}}
/// <param name="{{GetDotNetNameOpenApiParameter parameter "false"}}">
/// {{ToXmlDoc parameter.description}}
/// </param>
Expand All @@ -80,14 +81,14 @@ public partial class {{name}}Client : ResourceClient
/// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation.
/// </param>
public async Task<T> {{GetActionName api.operation name "Async"}}<T>(
{{ for parameter in api.operation.parameters}}
{{ for parameter in $filteredParams}}
{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}},
{{ end }}
CancellationToken cancellationToken = default(CancellationToken))
{
using (var _result = await Client.{{group}}.{{GetOperationId api.operation "WithHttpMessagesAsync"}}<T>(
{{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}},
{{GetParameterValueForWatch parameter false}},
{{end}}
null,
cancellationToken).ConfigureAwait(false))
Expand All @@ -96,5 +97,69 @@ public partial class {{name}}Client : ResourceClient
}
}
{{end}}

#if !K8S_AOT
{{if IfParamContains api.operation "watch"}}
/// <summary>
/// Watch {{ToXmlDoc api.operation.description}}
/// </summary>
{{ for parameter in $filteredParams}}
/// <param name="{{GetDotNetNameOpenApiParameter parameter "false"}}">
/// {{ToXmlDoc parameter.description}}
/// </param>
{{ end }}
/// <param name="onEvent">Callback when any event raised from api server</param>
/// <param name="onError">Callback when any exception was caught during watching</param>
/// <param name="onClosed">Callback when the server closes the connection</param>
public Watcher<{{GetReturnType api.operation "T"}}> Watch{{GetActionName api.operation name ""}}(
{{ for parameter in $filteredParams}}
{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}},
{{ end }}
Action<WatchEventType, {{GetReturnType api.operation "T"}}> onEvent = null,
Action<Exception> onError = null,
Action onClosed = null)
{
if (onEvent == null) throw new ArgumentNullException(nameof(onEvent));

var responseTask = Client.{{group}}.{{GetOperationId api.operation "WithHttpMessagesAsync"}}(
{{ for parameter in api.operation.parameters}}
{{GetParameterValueForWatch parameter true}},
{{ end }}
null,
CancellationToken.None);

return responseTask.Watch<{{GetReturnType api.operation "T"}}, {{GetReturnType api.operation "TList"}}>(
onEvent, onError, onClosed);
}

/// <summary>
/// Watch {{ToXmlDoc api.operation.description}} as async enumerable
/// </summary>
{{ for parameter in $filteredParams}}
/// <param name="{{GetDotNetNameOpenApiParameter parameter "false"}}">
/// {{ToXmlDoc parameter.description}}
/// </param>
{{ end }}
/// <param name="onError">Callback when any exception was caught during watching</param>
/// <param name="cancellationToken">Cancellation token</param>
public IAsyncEnumerable<(WatchEventType, {{GetReturnType api.operation "T"}})> Watch{{GetActionName api.operation name "Async"}}(
{{ for parameter in $filteredParams}}
{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}},
{{ end }}
Action<Exception> onError = null,
CancellationToken cancellationToken = default)
{
var responseTask = Client.{{group}}.{{GetOperationId api.operation "WithHttpMessagesAsync"}}(
{{ for parameter in api.operation.parameters}}
{{GetParameterValueForWatch parameter true}},
{{ end }}
null,
cancellationToken);

return responseTask.WatchAsync<{{GetReturnType api.operation "T"}}, {{GetReturnType api.operation "TList"}}>(
onError, cancellationToken);
}
{{end}}
}
#endif
{{end}}
}
Loading
Loading