Skip to content
Open
Show file tree
Hide file tree
Changes from 12 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();
}
}
}
3 changes: 3 additions & 0 deletions src/KubernetesClient/WatcherExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public static class WatcherExt
/// The action to invoke when the server closes the connection.
/// </param>
/// <returns>a watch object</returns>
[Obsolete("This method will be deprecated in future versions.")]
public static Watcher<T> Watch<T, L>(
this Task<HttpOperationResponse<L>> responseTask,
Action<WatchEventType, T> onEvent,
Expand Down Expand Up @@ -52,6 +53,7 @@ private static Func<Task<TextReader>> MakeStreamReaderCreator<T, L>(Task<HttpOpe
/// The action to invoke when the server closes the connection.
/// </param>
/// <returns>a watch object</returns>
[Obsolete("This method will be deprecated in future versions.")]
public static Watcher<T> Watch<T, L>(
this HttpOperationResponse<L> response,
Action<WatchEventType, T> onEvent,
Expand All @@ -71,6 +73,7 @@ public static Watcher<T> Watch<T, L>(
/// <param name="onError">a callback when any exception was caught during watching</param>
/// <param name="cancellationToken">cancellation token</param>
/// <returns>IAsyncEnumerable of watch events</returns>
[Obsolete("This method will be deprecated in future versions.")]
public static IAsyncEnumerable<(WatchEventType, T)> WatchAsync<T, L>(
this Task<HttpOperationResponse<L>> responseTask,
Copy link
Member

Choose a reason for hiding this comment

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

i think we should not filter bool watch in list at the moment
otherwise, this WatchExt cant work anymore for old code

so what do you think, make those internal or keep compatible until next version?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's best to just change it to internal so we don't forget to remove it in a future version.

Copy link
Member

Choose a reason for hiding this comment

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

cc @brendanburns for ideas
internal it will create a big breaking change and users have to to migrate their code to new WatchXXX
do you think if it is nessary to wait one more big version?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cc @brendanburns for ideas internal it will create a big breaking change and users have to to migrate their code to new WatchXXX do you think if it is nessary to wait one more big version?

It seems that @ is wrong. It should be @brendandburns

Action<Exception> onError = null,
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)
}));
}
}
}
}
33 changes: 32 additions & 1 deletion src/LibKubernetesGenerator/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,16 @@
}

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 +293,26 @@

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);
}

Check warning on line 309 in src/LibKubernetesGenerator/TypeHelper.cs

View workflow job for this annotation

GitHub Actions / MSBuild build

Check warning on line 309 in src/LibKubernetesGenerator/TypeHelper.cs

View workflow job for this annotation

GitHub Actions / Dotnet build (ubuntu-latest)

Check warning on line 309 in src/LibKubernetesGenerator/TypeHelper.cs

View workflow job for this annotation

GitHub Actions / Dotnet build (macOS-latest)

Check warning on line 309 in src/LibKubernetesGenerator/TypeHelper.cs

View workflow job for this annotation

GitHub Actions / Dotnet build (windows-latest)

Check warning on line 309 in src/LibKubernetesGenerator/TypeHelper.cs

View workflow job for this annotation

GitHub Actions / e2e

Check warning on line 309 in src/LibKubernetesGenerator/TypeHelper.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

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