diff --git a/examples/clientset/Program.cs b/examples/clientset/Program.cs index 9c2cac46..0d6db4be 100644 --- a/examples/clientset/Program.cs +++ b/examples/clientset/Program.cs @@ -12,15 +12,21 @@ 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) { System.Console.WriteLine(item.Metadata.Name); } - var pod = await clientSet.CoreV1.Pod.GetAsync("test","default").ConfigureAwait(false); + 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); + } } } } diff --git a/examples/watch/Program.cs b/examples/watch/Program.cs index f21f8f88..b1f54d0f 100644 --- a/examples/watch/Program.cs +++ b/examples/watch/Program.cs @@ -1,5 +1,4 @@ using k8s; -using k8s.Models; using System; using System.Threading; using System.Threading.Tasks; @@ -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().ConfigureAwait(false)) +await foreach (var (type, item) in podlistResp.ConfigureAwait(false)) { Console.WriteLine("==on watch event=="); Console.WriteLine(type); @@ -22,14 +22,24 @@ void WatchUsingCallback(IKubernetes client) #pragma warning restore CS8321 // Remove unused private members { - var podlistResp = client.CoreV1.ListNamespacedPodWithHttpMessagesAsync("default", watch: true); - using (podlistResp.Watch((type, item) => + using var podlistResp = client.CoreV1.WatchListNamespacedPod("default"); + podlistResp.OnEvent += (type, item) => { Console.WriteLine("==on watch event=="); Console.WriteLine(type); Console.WriteLine(item.Metadata.Name); Console.WriteLine("==on watch event=="); - })) + }; + podlistResp.OnError += (error) => + { + Console.WriteLine("==on watch error=="); + Console.WriteLine(error.Message); + Console.WriteLine("==on watch error=="); + }; + podlistResp.OnClosed += () => + { + Console.WriteLine("==on watch closed=="); + }; { Console.WriteLine("press ctrl + c to stop watching"); diff --git a/src/KubernetesClient.Aot/KubernetesClient.Aot.csproj b/src/KubernetesClient.Aot/KubernetesClient.Aot.csproj index ced07414..8ceca3b9 100644 --- a/src/KubernetesClient.Aot/KubernetesClient.Aot.csproj +++ b/src/KubernetesClient.Aot/KubernetesClient.Aot.csproj @@ -89,8 +89,8 @@ - - + + diff --git a/src/LibKubernetesGenerator/ParamHelper.cs b/src/LibKubernetesGenerator/ParamHelper.cs index 216c2f35..2ddd74b4 100644 --- a/src/LibKubernetesGenerator/ParamHelper.cs +++ b/src/LibKubernetesGenerator/ParamHelper.cs @@ -3,6 +3,7 @@ using Scriban.Runtime; using System; using System.Linq; +using System.Collections.Generic; namespace LibKubernetesGenerator { @@ -21,6 +22,8 @@ public void RegisterHelper(ScriptObject scriptObject) { scriptObject.Import(nameof(GetModelCtorParam), new Func(GetModelCtorParam)); scriptObject.Import(nameof(IfParamContains), IfParamContains); + scriptObject.Import(nameof(FilterParameters), FilterParameters); + scriptObject.Import(nameof(GetParameterValueForWatch), new Func(GetParameterValueForWatch)); } public static bool IfParamContains(OpenApiOperation operation, string name) @@ -39,6 +42,23 @@ public static bool IfParamContains(OpenApiOperation operation, string name) return found; } + public static IEnumerable 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 diff --git a/src/LibKubernetesGenerator/TypeHelper.cs b/src/LibKubernetesGenerator/TypeHelper.cs index 057a363a..69248041 100644 --- a/src/LibKubernetesGenerator/TypeHelper.cs +++ b/src/LibKubernetesGenerator/TypeHelper.cs @@ -245,6 +245,14 @@ string toType() } break; + case "T": + // Return single item type from list type (e.g., V1Pod from V1PodList) + return !string.IsNullOrEmpty(t) && t.EndsWith("List", StringComparison.Ordinal) + ? t.Substring(0, t.Length - 4) + : t; + case "TList": + // Return list type as-is + return t; } return t; diff --git a/src/LibKubernetesGenerator/templates/Client.cs.template b/src/LibKubernetesGenerator/templates/Client.cs.template index 02c55d7b..efdc0734 100644 --- a/src/LibKubernetesGenerator/templates/Client.cs.template +++ b/src/LibKubernetesGenerator/templates/Client.cs.template @@ -17,10 +17,11 @@ public partial class {{name}}Client : ResourceClient } {{for api in apis }} + {{~ $filteredParams = FilterParameters api.operation "watch" ~}} /// /// {{ToXmlDoc api.operation.description}} /// - {{ for parameter in api.operation.parameters}} + {{ for parameter in $filteredParams}} /// /// {{ToXmlDoc parameter.description}} /// @@ -29,7 +30,7 @@ public partial class {{name}}Client : ResourceClient /// A which can be used to cancel the asynchronous operation. /// 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)) @@ -37,7 +38,7 @@ public partial class {{name}}Client : ResourceClient {{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); @@ -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)) @@ -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)) @@ -71,7 +72,7 @@ public partial class {{name}}Client : ResourceClient /// /// {{ToXmlDoc api.operation.description}} /// - {{ for parameter in api.operation.parameters}} + {{ for parameter in $filteredParams}} /// /// {{ToXmlDoc parameter.description}} /// @@ -80,14 +81,14 @@ public partial class {{name}}Client : ResourceClient /// A which can be used to cancel the asynchronous operation. /// public async Task {{GetActionName api.operation name "Async"}}( - {{ 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"}}( {{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{GetParameterValueForWatch parameter false}}, {{end}} null, cancellationToken).ConfigureAwait(false)) @@ -96,5 +97,68 @@ public partial class {{name}}Client : ResourceClient } } {{end}} + + {{if IfParamContains api.operation "watch"}} + /// + /// Watch {{ToXmlDoc api.operation.description}} + /// + {{ for parameter in $filteredParams}} + /// + /// {{ToXmlDoc parameter.description}} + /// + {{ end }} + /// Callback when any event raised from api server + /// Callback when any exception was caught during watching + /// Callback when the server closes the connection + public Watcher<{{GetReturnType api.operation "T"}}> Watch{{GetActionName api.operation name ""}}( + {{ for parameter in $filteredParams}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, + {{ end }} + Action onEvent = null, + Action 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); + } + + /// + /// Watch {{ToXmlDoc api.operation.description}} as async enumerable + /// + {{ for parameter in $filteredParams}} + /// + /// {{ToXmlDoc parameter.description}} + /// + {{ end }} + /// Callback when any exception was caught during watching + /// Cancellation token + public IAsyncEnumerable<(WatchEventType, {{GetReturnType api.operation "T"}})> Watch{{GetActionName api.operation name "Async"}}( + {{ for parameter in $filteredParams}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, + {{ end }} + Action 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}} + + {{end}} } diff --git a/src/LibKubernetesGenerator/templates/OperationsExtensions.cs.template b/src/LibKubernetesGenerator/templates/OperationsExtensions.cs.template index b05f0e24..990a8a9d 100644 --- a/src/LibKubernetesGenerator/templates/OperationsExtensions.cs.template +++ b/src/LibKubernetesGenerator/templates/OperationsExtensions.cs.template @@ -12,26 +12,27 @@ namespace k8s; public static partial class {{name}}OperationsExtensions { {{for api in apis }} + {{~ $filteredParams = FilterParameters api.operation "watch" ~}} /// /// {{ToXmlDoc api.operation.description}} /// /// /// The operations group for this extension method. /// - {{ for parameter in api.operation.parameters}} + {{ for parameter in $filteredParams}} /// - /// {{ToXmlDoc api.description}} + /// {{ToXmlDoc parameter.description}} /// {{ end }} public static {{GetReturnType api.operation "void"}} {{GetOperationId api.operation ""}}( this I{{name}}Operations operations -{{ for parameter in api.operation.parameters}} +{{ for parameter in $filteredParams}} ,{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}} {{end}} ) { {{GetReturnType api.operation "return"}} operations.{{GetOperationId api.operation "Async"}}( -{{ for parameter in api.operation.parameters}} +{{ for parameter in $filteredParams}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} CancellationToken.None @@ -45,20 +46,20 @@ public static partial class {{name}}OperationsExtensions /// /// The operations group for this extension method. /// - {{ for parameter in api.operation.parameters}} + {{ for parameter in $filteredParams}} /// /// {{ToXmlDoc parameter.description}} /// {{end}} public static T {{GetOperationId api.operation ""}}( this I{{name}}Operations operations -{{ for parameter in api.operation.parameters}} +{{ for parameter in $filteredParams}} ,{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}} {{end}} ) { return operations.{{GetOperationId api.operation "Async"}}( -{{ for parameter in api.operation.parameters}} +{{ for parameter in $filteredParams}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} CancellationToken.None @@ -72,7 +73,7 @@ public static partial class {{name}}OperationsExtensions /// /// The operations group for this extension method. /// - {{ for parameter in api.operation.parameters}} + {{ for parameter in $filteredParams}} /// /// {{ToXmlDoc parameter.description}} /// @@ -82,7 +83,7 @@ public static partial class {{name}}OperationsExtensions /// public static async Task{{GetReturnType api.operation "<>"}} {{GetOperationId api.operation "Async"}}( this I{{name}}Operations operations, -{{ for parameter in api.operation.parameters}} +{{ for parameter in $filteredParams}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, {{ end }} CancellationToken cancellationToken = default(CancellationToken)) @@ -90,7 +91,7 @@ public static partial class {{name}}OperationsExtensions {{if IfReturnType api.operation "stream"}} var _result = await operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{GetParameterValueForWatch parameter false}}, {{end}} null, cancellationToken); @@ -100,7 +101,7 @@ public static partial class {{name}}OperationsExtensions {{if IfReturnType api.operation "obj"}} using (var _result = await operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{GetParameterValueForWatch parameter false}}, {{end}} null, cancellationToken).ConfigureAwait(false)) @@ -111,7 +112,7 @@ public static partial class {{name}}OperationsExtensions {{if IfReturnType api.operation "void"}} using (var _result = await operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{GetParameterValueForWatch parameter false}}, {{end}} null, cancellationToken).ConfigureAwait(false)) @@ -127,7 +128,7 @@ public static partial class {{name}}OperationsExtensions /// /// The operations group for this extension method. /// - {{ for parameter in api.operation.parameters}} + {{ for parameter in $filteredParams}} /// /// {{ToXmlDoc parameter.description}} /// @@ -137,14 +138,14 @@ public static partial class {{name}}OperationsExtensions /// public static async Task {{GetOperationId api.operation "Async"}}( this I{{name}}Operations operations, -{{ for parameter in api.operation.parameters}} +{{ for parameter in $filteredParams}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, {{ end }} CancellationToken cancellationToken = default(CancellationToken)) { using (var _result = await operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{GetParameterValueForWatch parameter false}}, {{end}} null, cancellationToken).ConfigureAwait(false)) @@ -154,5 +155,76 @@ public static partial class {{name}}OperationsExtensions } {{end}} +{{if IfParamContains api.operation "watch"}} +{{~ $filteredParams = FilterParameters api.operation "watch" ~}} +/// +/// Watch {{ToXmlDoc api.operation.description}} +/// +/// +/// The operations group for this extension method. +/// +{{ for parameter in $filteredParams}} +/// +/// {{ToXmlDoc parameter.description}} +/// +{{ end }} +/// Callback when any event raised from api server +/// Callback when any exception was caught during watching +/// Callback when the server closes the connection +public static Watcher<{{GetReturnType api.operation "T"}}> Watch{{GetOperationId api.operation ""}}( + this I{{name}}Operations operations, +{{ for parameter in $filteredParams}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, +{{end}} + Action onEvent = null, + Action onError = null, + Action onClosed = null) +{ + if (onEvent == null) throw new ArgumentNullException(nameof(onEvent)); + + var responseTask = operations.{{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); +} + +/// +/// Watch {{ToXmlDoc api.operation.description}} as async enumerable +/// +/// +/// The operations group for this extension method. +/// +{{ for parameter in $filteredParams}} +/// +/// {{ToXmlDoc parameter.description}} +/// +{{ end }} +/// Callback when any exception was caught during watching +/// Cancellation token +public static IAsyncEnumerable<(WatchEventType, {{GetReturnType api.operation "T"}})> Watch{{GetOperationId api.operation "Async"}}( + this I{{name}}Operations operations, +{{ for parameter in $filteredParams}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, +{{end}} + Action onError = null, + CancellationToken cancellationToken = default) +{ + var responseTask = operations.{{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}} + {{end}} }