From 128bc43f142aeb2b2f900b7c9dae74e7fd258ee1 Mon Sep 17 00:00:00 2001 From: endless001 Date: Fri, 18 Apr 2025 17:44:14 +0800 Subject: [PATCH 01/12] fix typo --- src/KubernetesClient/WatcherExt.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/KubernetesClient/WatcherExt.cs b/src/KubernetesClient/WatcherExt.cs index 7b2fe9308..f3047f8ca 100644 --- a/src/KubernetesClient/WatcherExt.cs +++ b/src/KubernetesClient/WatcherExt.cs @@ -11,7 +11,7 @@ public static class WatcherExt /// type of the HttpOperationResponse object /// the api response /// a callback when any event raised from api server - /// a callbak when any exception was caught during watching + /// a callback when any exception was caught during watching /// /// The action to invoke when the server closes the connection. /// @@ -47,7 +47,7 @@ private static Func> MakeStreamReaderCreator(Tasktype of the HttpOperationResponse object /// the api response /// a callback when any event raised from api server - /// a callbak when any exception was caught during watching + /// a callback when any exception was caught during watching /// /// The action to invoke when the server closes the connection. /// @@ -68,7 +68,7 @@ public static Watcher Watch( /// type of the event object /// type of the HttpOperationResponse object /// the api response - /// a callbak when any exception was caught during watching + /// a callback when any exception was caught during watching /// cancellation token /// IAsyncEnumerable of watch events public static IAsyncEnumerable<(WatchEventType, T)> WatchAsync( From 2cf1a5358a61dfa09ea34bba92553f740aba015b Mon Sep 17 00:00:00 2001 From: endless001 Date: Mon, 28 Apr 2025 18:07:52 +0800 Subject: [PATCH 02/12] Add modular API --- examples/clientset/Program.cs | 24 +++ examples/clientset/clientset.csproj | 6 + .../KubernetesClient.Aot.csproj | 6 +- .../KubernetesClient.Classic.csproj | 5 +- src/KubernetesClient/AbstractKubernetes.cs | 8 +- src/KubernetesClient/ClientSets/ClientSet.cs | 6 + .../ClientSets/ResourceClient.cs | 11 ++ src/KubernetesClient/GenericClient.cs | 1 + src/KubernetesClient/Kubernetes.WebSocket.cs | 14 +- src/KubernetesClient/Kubernetes.cs | 4 +- .../ClientSetGenerator.cs | 104 ++++++++++++ .../KubernetesClientSourceGenerator.cs | 29 ++-- .../templates/Client.cs.template | 154 +++++++++++++++++ .../templates/ClientExtensions.cs.template | 158 ++++++++++++++++++ .../templates/ClientSet.cs.template | 23 +++ .../templates/GroupClient.cs.template | 24 +++ tests/KubernetesClient.Tests/PodExecTests.cs | 2 +- 17 files changed, 545 insertions(+), 34 deletions(-) create mode 100644 examples/clientset/Program.cs create mode 100644 examples/clientset/clientset.csproj create mode 100644 src/KubernetesClient/ClientSets/ClientSet.cs create mode 100644 src/KubernetesClient/ClientSets/ResourceClient.cs create mode 100644 src/LibKubernetesGenerator/ClientSetGenerator.cs create mode 100644 src/LibKubernetesGenerator/templates/Client.cs.template create mode 100644 src/LibKubernetesGenerator/templates/ClientExtensions.cs.template create mode 100644 src/LibKubernetesGenerator/templates/ClientSet.cs.template create mode 100644 src/LibKubernetesGenerator/templates/GroupClient.cs.template diff --git a/examples/clientset/Program.cs b/examples/clientset/Program.cs new file mode 100644 index 000000000..b36790d19 --- /dev/null +++ b/examples/clientset/Program.cs @@ -0,0 +1,24 @@ +// See https://aka.ms/new-console-template for more information +using k8s; +using k8s.ClientSets; +using System.Threading.Tasks; + +namespace clientset +{ + internal class Program + { + private static async Task Main(string[] args) + { + var config = KubernetesClientConfiguration.BuildConfigFromConfigFile(); + IKubernetes client = new Kubernetes(config); + + ClientSet clientSet = new ClientSet(client); + var list = await clientSet.CoreV1.Pod.ListNamespacedPodAsync("default").ConfigureAwait(false); + foreach (var item in list) + { + System.Console.WriteLine(item.Metadata.Name); + } + } + } + +} diff --git a/examples/clientset/clientset.csproj b/examples/clientset/clientset.csproj new file mode 100644 index 000000000..4274ceb02 --- /dev/null +++ b/examples/clientset/clientset.csproj @@ -0,0 +1,6 @@ + + + Exe + + + diff --git a/src/KubernetesClient.Aot/KubernetesClient.Aot.csproj b/src/KubernetesClient.Aot/KubernetesClient.Aot.csproj index 226f818e6..88fe09488 100644 --- a/src/KubernetesClient.Aot/KubernetesClient.Aot.csproj +++ b/src/KubernetesClient.Aot/KubernetesClient.Aot.csproj @@ -46,6 +46,10 @@ + + + + @@ -107,4 +111,4 @@ - \ No newline at end of file + diff --git a/src/KubernetesClient.Classic/KubernetesClient.Classic.csproj b/src/KubernetesClient.Classic/KubernetesClient.Classic.csproj index bc8ad033a..254e745f3 100644 --- a/src/KubernetesClient.Classic/KubernetesClient.Classic.csproj +++ b/src/KubernetesClient.Classic/KubernetesClient.Classic.csproj @@ -75,7 +75,10 @@ - + + + + diff --git a/src/KubernetesClient/AbstractKubernetes.cs b/src/KubernetesClient/AbstractKubernetes.cs index 1d6ed2ae7..6e55bb9ac 100644 --- a/src/KubernetesClient/AbstractKubernetes.cs +++ b/src/KubernetesClient/AbstractKubernetes.cs @@ -5,7 +5,7 @@ namespace k8s; public abstract partial class AbstractKubernetes { - private static class HttpMethods + internal static class HttpMethods { public static readonly HttpMethod Delete = HttpMethod.Delete; public static readonly HttpMethod Get = HttpMethod.Get; @@ -23,7 +23,7 @@ private static class HttpMethods } - private sealed class QueryBuilder + internal sealed class QueryBuilder { private readonly List parameters = new List(); @@ -99,7 +99,7 @@ private MediaTypeHeaderValue GetHeader(V1Patch body) } } - protected abstract Task> CreateResultAsync(HttpRequestMessage httpRequest, HttpResponseMessage httpResponse, bool? watch, CancellationToken cancellationToken); + internal abstract Task> CreateResultAsync(HttpRequestMessage httpRequest, HttpResponseMessage httpResponse, bool? watch, CancellationToken cancellationToken); - protected abstract Task SendRequest(string relativeUri, HttpMethod method, IReadOnlyDictionary> customHeaders, T body, CancellationToken cancellationToken); + internal abstract Task SendRequest(string relativeUri, HttpMethod method, IReadOnlyDictionary> customHeaders, T body, CancellationToken cancellationToken); } diff --git a/src/KubernetesClient/ClientSets/ClientSet.cs b/src/KubernetesClient/ClientSets/ClientSet.cs new file mode 100644 index 000000000..c17a35829 --- /dev/null +++ b/src/KubernetesClient/ClientSets/ClientSet.cs @@ -0,0 +1,6 @@ +namespace k8s.ClientSets; + +public partial class ClientSet +{ + +} diff --git a/src/KubernetesClient/ClientSets/ResourceClient.cs b/src/KubernetesClient/ClientSets/ResourceClient.cs new file mode 100644 index 000000000..1ef6ee579 --- /dev/null +++ b/src/KubernetesClient/ClientSets/ResourceClient.cs @@ -0,0 +1,11 @@ +namespace k8s.ClientSets; + +public abstract class ResourceClient +{ + protected Kubernetes Client { get; } + + public ResourceClient(IKubernetes kubernetes) + { + Client = (Kubernetes)kubernetes; + } +} diff --git a/src/KubernetesClient/GenericClient.cs b/src/KubernetesClient/GenericClient.cs index a250aad22..8a03fafec 100644 --- a/src/KubernetesClient/GenericClient.cs +++ b/src/KubernetesClient/GenericClient.cs @@ -31,6 +31,7 @@ public GenericClient(IKubernetes kubernetes, string group, string version, strin public async Task CreateAsync(T obj, CancellationToken cancel = default) where T : IKubernetesObject { + var resp = await kubernetes.CustomObjects.CreateClusterCustomObjectWithHttpMessagesAsync(obj, group, version, plural, cancellationToken: cancel).ConfigureAwait(false); return resp.Body; } diff --git a/src/KubernetesClient/Kubernetes.WebSocket.cs b/src/KubernetesClient/Kubernetes.WebSocket.cs index a3a5bc908..aeca29708 100644 --- a/src/KubernetesClient/Kubernetes.WebSocket.cs +++ b/src/KubernetesClient/Kubernetes.WebSocket.cs @@ -18,11 +18,11 @@ public partial class Kubernetes /// public Task WebSocketNamespacedPodExecAsync(string name, string @namespace = "default", string command = null, string container = null, bool stderr = true, bool stdin = true, bool stdout = true, - bool tty = true, string webSocketSubProtol = null, Dictionary> customHeaders = null, + bool tty = true, string webSocketSubProtocol = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) { return WebSocketNamespacedPodExecAsync(name, @namespace, new string[] { command }, container, stderr, stdin, - stdout, tty, webSocketSubProtol, customHeaders, cancellationToken); + stdout, tty, webSocketSubProtocol, customHeaders, cancellationToken); } /// @@ -30,7 +30,7 @@ public virtual async Task MuxedStreamNamespacedPodExecAsync( string name, string @namespace = "default", IEnumerable command = null, string container = null, bool stderr = true, bool stdin = true, bool stdout = true, bool tty = true, - string webSocketSubProtol = WebSocketProtocol.V4BinaryWebsocketProtocol, + string webSocketSubProtocol = WebSocketProtocol.V4BinaryWebsocketProtocol, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) { @@ -45,7 +45,7 @@ public virtual async Task MuxedStreamNamespacedPodExecAsync( public virtual Task WebSocketNamespacedPodExecAsync(string name, string @namespace = "default", IEnumerable command = null, string container = null, bool stderr = true, bool stdin = true, bool stdout = true, bool tty = true, - string webSocketSubProtol = WebSocketProtocol.V4BinaryWebsocketProtocol, + string webSocketSubProtocol = WebSocketProtocol.V4BinaryWebsocketProtocol, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) { @@ -114,7 +114,7 @@ public virtual Task WebSocketNamespacedPodExecAsync(string name, stri uriBuilder.Query = query.ToString(1, query.Length - 1); // UriBuilder.Query doesn't like leading '?' chars, so trim it - return StreamConnectAsync(uriBuilder.Uri, webSocketSubProtol, customHeaders, + return StreamConnectAsync(uriBuilder.Uri, webSocketSubProtocol, customHeaders, cancellationToken); } @@ -173,7 +173,7 @@ public Task WebSocketNamespacedPodPortForwardAsync(string name, strin /// public Task WebSocketNamespacedPodAttachAsync(string name, string @namespace, string container = default, bool stderr = true, bool stdin = false, bool stdout = true, - bool tty = false, string webSocketSubProtol = null, Dictionary> customHeaders = null, + bool tty = false, string webSocketSubProtocol = null, Dictionary> customHeaders = null, CancellationToken cancellationToken = default) { if (name == null) @@ -208,7 +208,7 @@ public Task WebSocketNamespacedPodAttachAsync(string name, string @na uriBuilder.Query = query.ToString(1, query.Length - 1); // UriBuilder.Query doesn't like leading '?' chars, so trim it - return StreamConnectAsync(uriBuilder.Uri, webSocketSubProtol, customHeaders, + return StreamConnectAsync(uriBuilder.Uri, webSocketSubProtocol, customHeaders, cancellationToken); } diff --git a/src/KubernetesClient/Kubernetes.cs b/src/KubernetesClient/Kubernetes.cs index 5e5021356..72dadcd4b 100644 --- a/src/KubernetesClient/Kubernetes.cs +++ b/src/KubernetesClient/Kubernetes.cs @@ -56,7 +56,7 @@ private void Initialize() BaseUri = new Uri("http://localhost"); } - protected override async Task> CreateResultAsync(HttpRequestMessage httpRequest, HttpResponseMessage httpResponse, bool? watch, CancellationToken cancellationToken) + internal override async Task> CreateResultAsync(HttpRequestMessage httpRequest, HttpResponseMessage httpResponse, bool? watch, CancellationToken cancellationToken) { if (httpRequest == null) { @@ -96,7 +96,7 @@ protected override async Task> CreateResultAsync(Htt return result; } - protected override Task SendRequest(string relativeUri, HttpMethod method, IReadOnlyDictionary> customHeaders, T body, CancellationToken cancellationToken) + internal override Task SendRequest(string relativeUri, HttpMethod method, IReadOnlyDictionary> customHeaders, T body, CancellationToken cancellationToken) { var httpRequest = new HttpRequestMessage { diff --git a/src/LibKubernetesGenerator/ClientSetGenerator.cs b/src/LibKubernetesGenerator/ClientSetGenerator.cs new file mode 100644 index 000000000..40649fac1 --- /dev/null +++ b/src/LibKubernetesGenerator/ClientSetGenerator.cs @@ -0,0 +1,104 @@ +using CaseExtensions; +using Microsoft.CodeAnalysis; +using NSwag; +using System.Collections.Generic; +using System.Linq; + +namespace LibKubernetesGenerator +{ + internal class ClientSetGenerator + { + private readonly ScriptObjectFactory _scriptObjectFactory; + + public ClientSetGenerator(ScriptObjectFactory scriptObjectFactory) + { + _scriptObjectFactory = scriptObjectFactory; + } + + public void Generate(OpenApiDocument swagger, IncrementalGeneratorPostInitializationContext context) + { + var data = swagger.Operations + .Where(o => o.Method != OpenApiOperationMethod.Options) + .Select(o => + { + var ps = o.Operation.ActualParameters.OrderBy(p => !p.IsRequired).ToArray(); + + o.Operation.Parameters.Clear(); + + var name = new HashSet(); + + var i = 1; + foreach (var p in ps) + { + if (name.Contains(p.Name)) + { + p.Name += i++; + } + + o.Operation.Parameters.Add(p); + name.Add(p.Name); + } + + return o; + }) + .Select(o => + { + o.Path = o.Path.TrimStart('/'); + o.Method = char.ToUpper(o.Method[0]) + o.Method.Substring(1); + return o; + }) + .ToArray(); + + var sc = _scriptObjectFactory.CreateScriptObject(); + + var groups = new List(); + var apiGroups = new Dictionary(); + + foreach (var grouped in data.Where(d => HasKubernetesAction(d.Operation?.ExtensionData)) + .GroupBy(d => d.Operation.Tags.First())) + { + var clients = new List(); + var name = grouped.Key.ToPascalCase(); + groups.Add(name); + var apis = grouped.Select(x => + { + var groupVersionKindElements = x.Operation?.ExtensionData?["x-kubernetes-group-version-kind"]; + var groupVersionKind = (Dictionary)groupVersionKindElements; + + return new { Kind = groupVersionKind?["kind"], Api = x }; + + }); + + foreach (var item in apis.GroupBy(x => x.Kind)) + { + var kind = item.Key as string; + apiGroups[kind] = item.Select(x => x.Api).ToArray(); + clients.Add(kind); + } + + sc.SetValue("clients", clients, true); + sc.SetValue("name", name, true); + context.RenderToContext("GroupClient.cs.template", sc, $"{name}GroupClient.g.cs"); + } + + foreach (var apiGroup in apiGroups) + { + var name = apiGroup.Key; + var apis = apiGroup.Value.ToArray(); + + sc.SetValue("apis", apis, true); + sc.SetValue("name", name, true); + context.RenderToContext("Client.cs.template", sc, $"{name}Client.g.cs"); + context.RenderToContext("ClientExtensions.cs.template", sc, $"{name}ClientExtensions.g.cs"); + } + + sc = _scriptObjectFactory.CreateScriptObject(); + sc.SetValue("groups", groups, true); + + context.RenderToContext("ClientSet.cs.template", sc, $"ClientSet.g.cs"); + } + + private bool HasKubernetesAction(IDictionary extensionData) => + extensionData?.ContainsKey("x-kubernetes-action") ?? false; + } +} diff --git a/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs b/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs index 0e6278fb4..0fe7df491 100644 --- a/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs +++ b/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs @@ -21,46 +21,38 @@ private static IContainer BuildContainer(OpenApiDocument swagger) builder.RegisterType() .WithParameter(new NamedParameter(nameof(swagger), swagger)) .AsSelf() - .AsImplementedInterfaces() - ; + .AsImplementedInterfaces(); builder.RegisterType() - .AsImplementedInterfaces() - ; + .AsImplementedInterfaces(); builder.RegisterType() - .AsImplementedInterfaces() - ; + .AsImplementedInterfaces(); builder.RegisterType() .WithParameter(new TypedParameter(typeof(OpenApiDocument), swagger)) - .AsImplementedInterfaces() - ; + .AsImplementedInterfaces(); builder.RegisterType() .AsSelf() - .AsImplementedInterfaces() - ; + .AsImplementedInterfaces(); builder.RegisterType() .AsSelf() - .AsImplementedInterfaces() - ; + .AsImplementedInterfaces(); builder.RegisterType() - .AsImplementedInterfaces() - ; + .AsImplementedInterfaces(); builder.RegisterType() - .AsImplementedInterfaces() - ; + .AsImplementedInterfaces(); - builder.RegisterType() - ; + builder.RegisterType(); builder.RegisterType(); builder.RegisterType(); builder.RegisterType(); + builder.RegisterType(); builder.RegisterType(); builder.RegisterType(); @@ -80,6 +72,7 @@ public void Initialize(IncrementalGeneratorInitializationContext generatorContex container.Resolve().Generate(swagger, ctx); container.Resolve().Generate(swagger, ctx); container.Resolve().Generate(swagger, ctx); + container.Resolve().Generate(swagger, ctx); }); #endif diff --git a/src/LibKubernetesGenerator/templates/Client.cs.template b/src/LibKubernetesGenerator/templates/Client.cs.template new file mode 100644 index 000000000..75bd04357 --- /dev/null +++ b/src/LibKubernetesGenerator/templates/Client.cs.template @@ -0,0 +1,154 @@ +// +// Code generated by https://github.com/kubernetes-client/csharp/tree/master/src/LibKubernetesGenerator +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// +using System.Net.Http; +using System.Net.Http.Headers; + +namespace k8s.ClientSets; + + +/// +/// +public partial class {{name}}Client : ResourceClient +{ + public {{name}}Client(IKubernetes kubernetes) : base(kubernetes) + { + } + + {{for api in apis }} + {{if IfReturnType api.operation "void"}} + private async Task {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + {{end}} + {{if IfReturnType api.operation "obj"}} + private async Task> {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + {{end}} + {{if IfReturnType api.operation "stream"}} + private async Task> {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + {{end}} +{{ for parameter in api.operation.parameters}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + IReadOnlyDictionary> customHeaders, + CancellationToken cancellationToken) + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + cts.CancelAfter(Client.HttpClientTimeout); + {{if IfParamContains api.operation "watch"}} + if (watch == true) + { + cts.CancelAfter(Timeout.InfiniteTimeSpan); + } + {{end}} + cancellationToken = cts.Token; + + {{ for parameter in api.operation.parameters}} + {{ if parameter.IsRequired}} + if ({{GetDotNetName parameter.name}} == null) + { + throw new ArgumentNullException("{{GetDotNetName parameter.name}}"); + } + {{end}} + {{end}} + + // Construct URL + var url = $"{{ToInterpolationPathString api.path}}"; + {{if IfGroupPathParamContainsGroup api.path}} + url = url.Replace("apis//", "api/"); + {{end}} + {{if (array.size api.operation.parameters) > 0}} + var q = new AbstractKubernetes.QueryBuilder(); + {{ for parameter in api.operation.parameters}} + {{if IfKindIs parameter "query"}} + q.Append("{{parameter.name}}", {{GetDotNetName parameter.name}}); + {{end}} + {{end}} + url += q.ToString(); + {{end}} + + // Create HTTP transport + {{if IfParamContains api.operation "body"}} + var httpResponse = await Client.SendRequest(url, AbstractKubernetes.HttpMethods.{{api.method}}, customHeaders, body, cancellationToken); + {{ else }} + var httpResponse = await Client.SendRequest(url, AbstractKubernetes.HttpMethods.{{api.method}}, customHeaders, null, cancellationToken); + {{end}} + // Create Result + var httpRequest = httpResponse.RequestMessage; + {{if IfReturnType api.operation "void"}} + HttpOperationResponse result = new HttpOperationResponse() { Request = httpRequest, Response = httpResponse }; + {{end}} + {{if IfReturnType api.operation "obj"}} + var result = await Client.CreateResultAsync( + httpRequest, + httpResponse, + {{if IfParamContains api.operation "watch"}} + watch, + {{else}} + false, + {{end}} + cancellationToken); + {{end}} + {{if IfReturnType api.operation "stream"}} + var result = new HttpOperationResponse() { + Request = httpRequest, + Response = httpResponse, + Body = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false) }; + {{end}} + return result; + } + + /// + public async Task"}}> {{GetMethodName api.operation "WithHttpMessagesAsync"}}( +{{ for parameter in api.operation.parameters}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + IReadOnlyDictionary> customHeaders, + CancellationToken cancellationToken) + { + {{if IfReturnType api.operation "void"}} + return await {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( +{{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + customHeaders, + cancellationToken).ConfigureAwait(false); + {{end}} + {{if IfReturnType api.operation "obj"}} + return await {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}{{GetReturnType api.operation "<>"}}( +{{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + customHeaders, + cancellationToken).ConfigureAwait(false); + {{end}} + {{if IfReturnType api.operation "stream"}} + return await {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( +{{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + customHeaders, + cancellationToken).ConfigureAwait(false); + {{end}} + } + + {{if IfReturnType api.operation "object"}} + /// + public async Task> {{GetMethodName api.operation "WithHttpMessagesAsync"}}( +{{ for parameter in api.operation.parameters}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + IReadOnlyDictionary> customHeaders, + CancellationToken cancellationToken) + { + return await {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( +{{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + customHeaders, + cancellationToken).ConfigureAwait(false); + } + {{end}} + {{end}} + +} diff --git a/src/LibKubernetesGenerator/templates/ClientExtensions.cs.template b/src/LibKubernetesGenerator/templates/ClientExtensions.cs.template new file mode 100644 index 000000000..c0b0669d6 --- /dev/null +++ b/src/LibKubernetesGenerator/templates/ClientExtensions.cs.template @@ -0,0 +1,158 @@ +// +// Code generated by https://github.com/kubernetes-client/csharp/tree/master/src/LibKubernetesGenerator +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace k8s.ClientSets; + +/// +/// Extension methods for Kubernetes. +/// +public static partial class {{name}}ClientExtensions +{ + {{for api in apis }} + /// + /// {{ToXmlDoc api.operation.description}} + /// + /// + /// The client group for this extension method. + /// + {{ for parameter in api.operation.parameters}} + /// + /// {{ToXmlDoc api.description}} + /// + {{ end }} + public static {{GetReturnType api.operation "void"}} {{GetMethodName api.operation ""}}( + this {{name}}Client client +{{ for parameter in api.operation.parameters}} + ,{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}} +{{end}} + ) + { + {{GetReturnType api.operation "return"}} client.{{GetMethodName api.operation "Async"}}( +{{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + CancellationToken.None + ).GetAwaiter().GetResult(); + } + + {{if IfReturnType api.operation "object"}} + /// + /// {{ToXmlDoc api.operation.description}} + /// + /// + /// The client group for this extension method. + /// + {{ for parameter in api.operation.parameters}} + /// + /// {{ToXmlDoc parameter.description}} + /// + {{end}} + public static T {{GetMethodName api.operation ""}}( + this {{name}}Client client +{{ for parameter in api.operation.parameters}} + ,{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}} +{{end}} + ) + { + return client.{{GetMethodName api.operation "Async"}}( +{{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + CancellationToken.None + ).GetAwaiter().GetResult(); + } + {{end}} + + /// + /// {{ToXmlDoc api.operation.description}} + /// + /// + /// The client group for this extension method. + /// + {{ for parameter in api.operation.parameters}} + /// + /// {{ToXmlDoc parameter.description}} + /// + {{end}} + /// + /// A which can be used to cancel the asynchronous operation. + /// + public static async Task{{GetReturnType api.operation "<>"}} {{GetMethodName api.operation "Async"}}( + this {{name}}Client client, +{{ for parameter in api.operation.parameters}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, +{{ end }} + CancellationToken cancellationToken = default(CancellationToken)) + { + {{if IfReturnType api.operation "stream"}} + var _result = await client.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( +{{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + null, + cancellationToken); + _result.Request.Dispose(); + {{GetReturnType api.operation "_result.Body"}}; + {{end}} + {{if IfReturnType api.operation "obj"}} + using (var _result = await client.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( +{{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + null, + cancellationToken).ConfigureAwait(false)) + { + {{GetReturnType api.operation "_result.Body"}}; + } + {{end}} + {{if IfReturnType api.operation "void"}} + using (var _result = await client.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( +{{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + null, + cancellationToken).ConfigureAwait(false)) + { + } + {{end}} + } + + {{if IfReturnType api.operation "object"}} + /// + /// {{ToXmlDoc api.operation.description}} + /// + /// + /// The client group for this extension method. + /// + {{ for parameter in api.operation.parameters}} + /// + /// {{ToXmlDoc parameter.description}} + /// + {{end}} + /// + /// A which can be used to cancel the asynchronous operation. + /// + public static async Task {{GetMethodName api.operation "Async"}}( + this {{name}}Client client, +{{ for parameter in api.operation.parameters}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, +{{ end }} + CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await client.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( +{{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, +{{end}} + null, + cancellationToken).ConfigureAwait(false)) + { + return _result.Body; + } + } + {{end}} + + {{end}} +} diff --git a/src/LibKubernetesGenerator/templates/ClientSet.cs.template b/src/LibKubernetesGenerator/templates/ClientSet.cs.template new file mode 100644 index 000000000..81a9d8151 --- /dev/null +++ b/src/LibKubernetesGenerator/templates/ClientSet.cs.template @@ -0,0 +1,23 @@ +// +// Code generated by https://github.com/kubernetes-client/csharp/tree/master/src/LibKubernetesGenerator +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace k8s.ClientSets; + +/// +/// +public partial class ClientSet +{ + {{for group in groups}} + public {{group}}GroupClient {{group}}; + {{end}} + + public ClientSet(IKubernetes kubernetes) + { + {{for group in groups}} + {{group}} = new {{group}}GroupClient(kubernetes); + {{end}} + } +} diff --git a/src/LibKubernetesGenerator/templates/GroupClient.cs.template b/src/LibKubernetesGenerator/templates/GroupClient.cs.template new file mode 100644 index 000000000..d12a24773 --- /dev/null +++ b/src/LibKubernetesGenerator/templates/GroupClient.cs.template @@ -0,0 +1,24 @@ +// +// Code generated by https://github.com/kubernetes-client/csharp/tree/master/src/LibKubernetesGenerator +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. +// + +namespace k8s.ClientSets; + + +/// +/// +public partial class {{name}}GroupClient +{ + {{for client in clients}} + public {{client}}Client {{client}}; + {{end}} + + public {{name}}GroupClient(IKubernetes kubernetes) + { + {{for client in clients}} + {{client}} = new {{client}}Client(kubernetes); + {{end}} + } +} diff --git a/tests/KubernetesClient.Tests/PodExecTests.cs b/tests/KubernetesClient.Tests/PodExecTests.cs index 5a6276e8d..597be477b 100644 --- a/tests/KubernetesClient.Tests/PodExecTests.cs +++ b/tests/KubernetesClient.Tests/PodExecTests.cs @@ -66,7 +66,7 @@ public async Task ExecDefaultContainerStdOut() false, false, true, - webSocketSubProtol: WebSocketProtocol.ChannelWebSocketProtocol, + webSocketSubProtocol: WebSocketProtocol.ChannelWebSocketProtocol, cancellationToken: TestCancellation).ConfigureAwait(true); Assert.Equal( WebSocketProtocol.ChannelWebSocketProtocol, From 0bd3dadd89d614e7b75784066e1976ca0d2ee96a Mon Sep 17 00:00:00 2001 From: endless001 Date: Tue, 29 Apr 2025 16:18:22 +0800 Subject: [PATCH 03/12] Use x-kubernetes-action instead of operationId to generate method names --- Directory.Packages.props | 3 +- examples/clientset/Program.cs | 6 +- src/KubernetesClient/AbstractKubernetes.cs | 8 +- src/KubernetesClient/GenericClient.cs | 1 - src/KubernetesClient/Kubernetes.cs | 4 +- .../ClientSetGenerator.cs | 10 +- .../GeneralNameHelper.cs | 60 ++++- .../KubernetesClientSourceGenerator.cs | 27 ++- .../LibKubernetesGenerator.target | 5 + .../templates/Client.cs.template | 222 +++++++----------- .../templates/ClientExtensions.cs.template | 158 ------------- 11 files changed, 189 insertions(+), 315 deletions(-) delete mode 100644 src/LibKubernetesGenerator/templates/ClientExtensions.cs.template diff --git a/Directory.Packages.props b/Directory.Packages.props index 45f27d6cf..005a782cc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -33,6 +33,7 @@ + @@ -50,4 +51,4 @@ - \ No newline at end of file + diff --git a/examples/clientset/Program.cs b/examples/clientset/Program.cs index b36790d19..c011dbcbc 100644 --- a/examples/clientset/Program.cs +++ b/examples/clientset/Program.cs @@ -9,15 +9,19 @@ internal class Program { private static async Task Main(string[] args) { + var config = KubernetesClientConfiguration.BuildConfigFromConfigFile(); IKubernetes client = new Kubernetes(config); ClientSet clientSet = new ClientSet(client); - var list = await clientSet.CoreV1.Pod.ListNamespacedPodAsync("default").ConfigureAwait(false); + var list = await clientSet.CoreV1.Pods.ListAsync("default").ConfigureAwait(false); foreach (var item in list) { System.Console.WriteLine(item.Metadata.Name); } + + var pod = await clientSet.CoreV1.Pods.GetAsync("test","default").ConfigureAwait(false); + System.Console.WriteLine(pod?.Metadata?.Name); } } diff --git a/src/KubernetesClient/AbstractKubernetes.cs b/src/KubernetesClient/AbstractKubernetes.cs index 6e55bb9ac..1d6ed2ae7 100644 --- a/src/KubernetesClient/AbstractKubernetes.cs +++ b/src/KubernetesClient/AbstractKubernetes.cs @@ -5,7 +5,7 @@ namespace k8s; public abstract partial class AbstractKubernetes { - internal static class HttpMethods + private static class HttpMethods { public static readonly HttpMethod Delete = HttpMethod.Delete; public static readonly HttpMethod Get = HttpMethod.Get; @@ -23,7 +23,7 @@ internal static class HttpMethods } - internal sealed class QueryBuilder + private sealed class QueryBuilder { private readonly List parameters = new List(); @@ -99,7 +99,7 @@ private MediaTypeHeaderValue GetHeader(V1Patch body) } } - internal abstract Task> CreateResultAsync(HttpRequestMessage httpRequest, HttpResponseMessage httpResponse, bool? watch, CancellationToken cancellationToken); + protected abstract Task> CreateResultAsync(HttpRequestMessage httpRequest, HttpResponseMessage httpResponse, bool? watch, CancellationToken cancellationToken); - internal abstract Task SendRequest(string relativeUri, HttpMethod method, IReadOnlyDictionary> customHeaders, T body, CancellationToken cancellationToken); + protected abstract Task SendRequest(string relativeUri, HttpMethod method, IReadOnlyDictionary> customHeaders, T body, CancellationToken cancellationToken); } diff --git a/src/KubernetesClient/GenericClient.cs b/src/KubernetesClient/GenericClient.cs index 8a03fafec..a250aad22 100644 --- a/src/KubernetesClient/GenericClient.cs +++ b/src/KubernetesClient/GenericClient.cs @@ -31,7 +31,6 @@ public GenericClient(IKubernetes kubernetes, string group, string version, strin public async Task CreateAsync(T obj, CancellationToken cancel = default) where T : IKubernetesObject { - var resp = await kubernetes.CustomObjects.CreateClusterCustomObjectWithHttpMessagesAsync(obj, group, version, plural, cancellationToken: cancel).ConfigureAwait(false); return resp.Body; } diff --git a/src/KubernetesClient/Kubernetes.cs b/src/KubernetesClient/Kubernetes.cs index 72dadcd4b..5e5021356 100644 --- a/src/KubernetesClient/Kubernetes.cs +++ b/src/KubernetesClient/Kubernetes.cs @@ -56,7 +56,7 @@ private void Initialize() BaseUri = new Uri("http://localhost"); } - internal override async Task> CreateResultAsync(HttpRequestMessage httpRequest, HttpResponseMessage httpResponse, bool? watch, CancellationToken cancellationToken) + protected override async Task> CreateResultAsync(HttpRequestMessage httpRequest, HttpResponseMessage httpResponse, bool? watch, CancellationToken cancellationToken) { if (httpRequest == null) { @@ -96,7 +96,7 @@ internal override async Task> CreateResultAsync(Http return result; } - internal override Task SendRequest(string relativeUri, HttpMethod method, IReadOnlyDictionary> customHeaders, T body, CancellationToken cancellationToken) + protected override Task SendRequest(string relativeUri, HttpMethod method, IReadOnlyDictionary> customHeaders, T body, CancellationToken cancellationToken) { var httpRequest = new HttpRequestMessage { diff --git a/src/LibKubernetesGenerator/ClientSetGenerator.cs b/src/LibKubernetesGenerator/ClientSetGenerator.cs index 40649fac1..7701adc0e 100644 --- a/src/LibKubernetesGenerator/ClientSetGenerator.cs +++ b/src/LibKubernetesGenerator/ClientSetGenerator.cs @@ -3,6 +3,7 @@ using NSwag; using System.Collections.Generic; using System.Linq; +using Humanizer; namespace LibKubernetesGenerator { @@ -65,13 +66,12 @@ public void Generate(OpenApiDocument swagger, IncrementalGeneratorPostInitializa var groupVersionKindElements = x.Operation?.ExtensionData?["x-kubernetes-group-version-kind"]; var groupVersionKind = (Dictionary)groupVersionKindElements; - return new { Kind = groupVersionKind?["kind"], Api = x }; - + return new { Kind = groupVersionKind?["kind"] as string, Api = x }; }); foreach (var item in apis.GroupBy(x => x.Kind)) { - var kind = item.Key as string; + var kind = item.Key.Pluralize(); apiGroups[kind] = item.Select(x => x.Api).ToArray(); clients.Add(kind); } @@ -85,11 +85,11 @@ public void Generate(OpenApiDocument swagger, IncrementalGeneratorPostInitializa { var name = apiGroup.Key; var apis = apiGroup.Value.ToArray(); - + var group = apis.Select(x => x.Operation.Tags[0]).First(); sc.SetValue("apis", apis, true); sc.SetValue("name", name, true); + sc.SetValue("group", group.ToPascalCase(), true); context.RenderToContext("Client.cs.template", sc, $"{name}Client.g.cs"); - context.RenderToContext("ClientExtensions.cs.template", sc, $"{name}ClientExtensions.g.cs"); } sc = _scriptObjectFactory.CreateScriptObject(); diff --git a/src/LibKubernetesGenerator/GeneralNameHelper.cs b/src/LibKubernetesGenerator/GeneralNameHelper.cs index c9b5cf5af..240f247a9 100644 --- a/src/LibKubernetesGenerator/GeneralNameHelper.cs +++ b/src/LibKubernetesGenerator/GeneralNameHelper.cs @@ -22,8 +22,11 @@ public void RegisterHelper(ScriptObject scriptObject) { scriptObject.Import(nameof(GetInterfaceName), new Func(GetInterfaceName)); scriptObject.Import(nameof(GetMethodName), new Func(GetMethodName)); + scriptObject.Import(nameof(GetActionName), + new Func(GetActionName)); scriptObject.Import(nameof(GetDotNetName), new Func(GetDotNetName)); - scriptObject.Import(nameof(GetDotNetNameOpenApiParameter), new Func(GetDotNetNameOpenApiParameter)); + scriptObject.Import(nameof(GetDotNetNameOpenApiParameter), + new Func(GetDotNetNameOpenApiParameter)); } private string GetInterfaceName(JsonSchema definition) @@ -162,5 +165,60 @@ public static string GetMethodName(OpenApiOperation watchOperation, string suffi return methodName; } + + public static string GetActionName(OpenApiOperationDescription apiOperation, string resource, string suffix) + { + var actionType = apiOperation.Operation?.ExtensionData?["x-kubernetes-action"] as string; + + if (string.IsNullOrEmpty(actionType)) + { + return $"{apiOperation.Method.ToPascalCase()}{suffix}"; + } + + var resourceNamespace = ParsePathSegmentAfterParameter(apiOperation.Path, "namespace").ToPascalCase(); + var resourceName = ParsePathSegmentAfterParameter(apiOperation.Path, "name").ToPascalCase(); + var actionMappings = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "get", "Get" }, + { "list", "List" }, + { "put", "Put" }, + { "patch", "Patch" }, + { "post", "Post" }, + { "delete", "Delete" }, + { "deletecollection", "DeleteCollection" }, + { "watch", "Watch" }, + { "watchlist", "WatchList" }, + { "proxy", "Proxy" }, + }; + + if (actionMappings.TryGetValue(actionType, out var actionPrefix)) + { + return Regex.Replace($"{actionPrefix}{resourceNamespace}{resourceName}{suffix}", resource, string.Empty, + RegexOptions.IgnoreCase); + } + + if (string.Equals("connect", actionType, StringComparison.OrdinalIgnoreCase)) + { + return Regex.Replace($"Connect{apiOperation.Method}{resourceNamespace}{resourceName}{suffix}", resource, + string.Empty, + RegexOptions.IgnoreCase); + } + + return $"{actionType.ToPascalCase()}{suffix}"; + } + + private static string ParsePathSegmentAfterParameter(string path, string variableName = "namespace") + { + var pattern = $@"/\{{{variableName}\}}/([^/]+)/?"; + + var match = Regex.Match(path, pattern); + + if (match.Success && match.Groups.Count > 1) + { + return match.Groups[1].Value; + } + + return string.Empty; + } } } diff --git a/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs b/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs index 0fe7df491..6ea216f28 100644 --- a/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs +++ b/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs @@ -21,33 +21,42 @@ private static IContainer BuildContainer(OpenApiDocument swagger) builder.RegisterType() .WithParameter(new NamedParameter(nameof(swagger), swagger)) .AsSelf() - .AsImplementedInterfaces(); + .AsImplementedInterfaces() + ; builder.RegisterType() - .AsImplementedInterfaces(); + .AsImplementedInterfaces() + ; builder.RegisterType() - .AsImplementedInterfaces(); + .AsImplementedInterfaces() + ; builder.RegisterType() .WithParameter(new TypedParameter(typeof(OpenApiDocument), swagger)) - .AsImplementedInterfaces(); + .AsImplementedInterfaces() + ; builder.RegisterType() .AsSelf() - .AsImplementedInterfaces(); + .AsImplementedInterfaces() + ; builder.RegisterType() .AsSelf() - .AsImplementedInterfaces(); + .AsImplementedInterfaces() + ; builder.RegisterType() - .AsImplementedInterfaces(); + .AsImplementedInterfaces() + ; builder.RegisterType() - .AsImplementedInterfaces(); + .AsImplementedInterfaces() + ; - builder.RegisterType(); + builder.RegisterType() + ; builder.RegisterType(); builder.RegisterType(); diff --git a/src/LibKubernetesGenerator/LibKubernetesGenerator.target b/src/LibKubernetesGenerator/LibKubernetesGenerator.target index d9240bcdd..fd84f9fb0 100644 --- a/src/LibKubernetesGenerator/LibKubernetesGenerator.target +++ b/src/LibKubernetesGenerator/LibKubernetesGenerator.target @@ -16,12 +16,16 @@ + + + + @@ -43,6 +47,7 @@ + diff --git a/src/LibKubernetesGenerator/templates/Client.cs.template b/src/LibKubernetesGenerator/templates/Client.cs.template index 75bd04357..23bb88863 100644 --- a/src/LibKubernetesGenerator/templates/Client.cs.template +++ b/src/LibKubernetesGenerator/templates/Client.cs.template @@ -13,142 +13,98 @@ namespace k8s.ClientSets; /// public partial class {{name}}Client : ResourceClient { - public {{name}}Client(IKubernetes kubernetes) : base(kubernetes) - { - } + public {{name}}Client(IKubernetes kubernetes) : base(kubernetes) + { + } - {{for api in apis }} - {{if IfReturnType api.operation "void"}} - private async Task {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( - {{end}} - {{if IfReturnType api.operation "obj"}} - private async Task> {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( - {{end}} - {{if IfReturnType api.operation "stream"}} - private async Task> {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( - {{end}} -{{ for parameter in api.operation.parameters}} - {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - IReadOnlyDictionary> customHeaders, - CancellationToken cancellationToken) - { - using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - cts.CancelAfter(Client.HttpClientTimeout); - {{if IfParamContains api.operation "watch"}} - if (watch == true) - { - cts.CancelAfter(Timeout.InfiniteTimeSpan); - } - {{end}} - cancellationToken = cts.Token; + {{for api in apis }} - {{ for parameter in api.operation.parameters}} - {{ if parameter.IsRequired}} - if ({{GetDotNetName parameter.name}} == null) - { - throw new ArgumentNullException("{{GetDotNetName parameter.name}}"); - } - {{end}} - {{end}} + /// + /// {{ToXmlDoc api.operation.description}} + /// + /// + /// The client group for this extension method. + /// + {{ for parameter in api.operation.parameters}} + /// + /// {{ToXmlDoc parameter.description}} + /// + {{end}} + /// + /// A which can be used to cancel the asynchronous operation. + /// + public async Task{{GetReturnType api.operation "<>"}} {{GetActionName api name "Async"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, + {{ end }} + CancellationToken cancellationToken = default(CancellationToken)) + { + {{if IfReturnType api.operation "stream"}} + var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{end}} + null, + cancellationToken); + _result.Request.Dispose(); + {{GetReturnType api.operation "_result.Body"}}; + {{end}} + {{if IfReturnType api.operation "obj"}} + using (var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{end}} + null, + cancellationToken).ConfigureAwait(false)) + { + {{GetReturnType api.operation "_result.Body"}}; + } + {{end}} + {{if IfReturnType api.operation "void"}} + using (var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{end}} + null, + cancellationToken).ConfigureAwait(false)) + { + } + {{end}} + } - // Construct URL - var url = $"{{ToInterpolationPathString api.path}}"; - {{if IfGroupPathParamContainsGroup api.path}} - url = url.Replace("apis//", "api/"); - {{end}} - {{if (array.size api.operation.parameters) > 0}} - var q = new AbstractKubernetes.QueryBuilder(); - {{ for parameter in api.operation.parameters}} - {{if IfKindIs parameter "query"}} - q.Append("{{parameter.name}}", {{GetDotNetName parameter.name}}); - {{end}} - {{end}} - url += q.ToString(); - {{end}} + {{if IfReturnType api.operation "object"}} + /// + /// {{ToXmlDoc api.operation.description}} + /// + /// + /// The client group for this extension method. + /// + {{ for parameter in api.operation.parameters}} + /// + /// {{ToXmlDoc parameter.description}} + /// + {{end}} + /// + /// A which can be used to cancel the asynchronous operation. + /// + public async Task {{GetActionName api name "Async"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{ end }} + CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{end}} + null, + cancellationToken).ConfigureAwait(false)) + { + return _result.Body; + } + } + {{end}} - // Create HTTP transport - {{if IfParamContains api.operation "body"}} - var httpResponse = await Client.SendRequest(url, AbstractKubernetes.HttpMethods.{{api.method}}, customHeaders, body, cancellationToken); - {{ else }} - var httpResponse = await Client.SendRequest(url, AbstractKubernetes.HttpMethods.{{api.method}}, customHeaders, null, cancellationToken); - {{end}} - // Create Result - var httpRequest = httpResponse.RequestMessage; - {{if IfReturnType api.operation "void"}} - HttpOperationResponse result = new HttpOperationResponse() { Request = httpRequest, Response = httpResponse }; - {{end}} - {{if IfReturnType api.operation "obj"}} - var result = await Client.CreateResultAsync( - httpRequest, - httpResponse, - {{if IfParamContains api.operation "watch"}} - watch, - {{else}} - false, - {{end}} - cancellationToken); - {{end}} - {{if IfReturnType api.operation "stream"}} - var result = new HttpOperationResponse() { - Request = httpRequest, - Response = httpResponse, - Body = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false) }; - {{end}} - return result; - } - - /// - public async Task"}}> {{GetMethodName api.operation "WithHttpMessagesAsync"}}( -{{ for parameter in api.operation.parameters}} - {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - IReadOnlyDictionary> customHeaders, - CancellationToken cancellationToken) - { - {{if IfReturnType api.operation "void"}} - return await {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( -{{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - customHeaders, - cancellationToken).ConfigureAwait(false); - {{end}} - {{if IfReturnType api.operation "obj"}} - return await {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}{{GetReturnType api.operation "<>"}}( -{{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - customHeaders, - cancellationToken).ConfigureAwait(false); - {{end}} - {{if IfReturnType api.operation "stream"}} - return await {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( -{{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - customHeaders, - cancellationToken).ConfigureAwait(false); - {{end}} - } - - {{if IfReturnType api.operation "object"}} - /// - public async Task> {{GetMethodName api.operation "WithHttpMessagesAsync"}}( -{{ for parameter in api.operation.parameters}} - {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - IReadOnlyDictionary> customHeaders, - CancellationToken cancellationToken) - { - return await {{name}}Client_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( -{{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - customHeaders, - cancellationToken).ConfigureAwait(false); - } - {{end}} - {{end}} + {{end}} } diff --git a/src/LibKubernetesGenerator/templates/ClientExtensions.cs.template b/src/LibKubernetesGenerator/templates/ClientExtensions.cs.template deleted file mode 100644 index c0b0669d6..000000000 --- a/src/LibKubernetesGenerator/templates/ClientExtensions.cs.template +++ /dev/null @@ -1,158 +0,0 @@ -// -// Code generated by https://github.com/kubernetes-client/csharp/tree/master/src/LibKubernetesGenerator -// Changes may cause incorrect behavior and will be lost if the code is -// regenerated. -// - -namespace k8s.ClientSets; - -/// -/// Extension methods for Kubernetes. -/// -public static partial class {{name}}ClientExtensions -{ - {{for api in apis }} - /// - /// {{ToXmlDoc api.operation.description}} - /// - /// - /// The client group for this extension method. - /// - {{ for parameter in api.operation.parameters}} - /// - /// {{ToXmlDoc api.description}} - /// - {{ end }} - public static {{GetReturnType api.operation "void"}} {{GetMethodName api.operation ""}}( - this {{name}}Client client -{{ for parameter in api.operation.parameters}} - ,{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}} -{{end}} - ) - { - {{GetReturnType api.operation "return"}} client.{{GetMethodName api.operation "Async"}}( -{{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - CancellationToken.None - ).GetAwaiter().GetResult(); - } - - {{if IfReturnType api.operation "object"}} - /// - /// {{ToXmlDoc api.operation.description}} - /// - /// - /// The client group for this extension method. - /// - {{ for parameter in api.operation.parameters}} - /// - /// {{ToXmlDoc parameter.description}} - /// - {{end}} - public static T {{GetMethodName api.operation ""}}( - this {{name}}Client client -{{ for parameter in api.operation.parameters}} - ,{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}} -{{end}} - ) - { - return client.{{GetMethodName api.operation "Async"}}( -{{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - CancellationToken.None - ).GetAwaiter().GetResult(); - } - {{end}} - - /// - /// {{ToXmlDoc api.operation.description}} - /// - /// - /// The client group for this extension method. - /// - {{ for parameter in api.operation.parameters}} - /// - /// {{ToXmlDoc parameter.description}} - /// - {{end}} - /// - /// A which can be used to cancel the asynchronous operation. - /// - public static async Task{{GetReturnType api.operation "<>"}} {{GetMethodName api.operation "Async"}}( - this {{name}}Client client, -{{ for parameter in api.operation.parameters}} - {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, -{{ end }} - CancellationToken cancellationToken = default(CancellationToken)) - { - {{if IfReturnType api.operation "stream"}} - var _result = await client.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( -{{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - null, - cancellationToken); - _result.Request.Dispose(); - {{GetReturnType api.operation "_result.Body"}}; - {{end}} - {{if IfReturnType api.operation "obj"}} - using (var _result = await client.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( -{{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - null, - cancellationToken).ConfigureAwait(false)) - { - {{GetReturnType api.operation "_result.Body"}}; - } - {{end}} - {{if IfReturnType api.operation "void"}} - using (var _result = await client.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( -{{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - null, - cancellationToken).ConfigureAwait(false)) - { - } - {{end}} - } - - {{if IfReturnType api.operation "object"}} - /// - /// {{ToXmlDoc api.operation.description}} - /// - /// - /// The client group for this extension method. - /// - {{ for parameter in api.operation.parameters}} - /// - /// {{ToXmlDoc parameter.description}} - /// - {{end}} - /// - /// A which can be used to cancel the asynchronous operation. - /// - public static async Task {{GetMethodName api.operation "Async"}}( - this {{name}}Client client, -{{ for parameter in api.operation.parameters}} - {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, -{{ end }} - CancellationToken cancellationToken = default(CancellationToken)) - { - using (var _result = await client.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( -{{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, -{{end}} - null, - cancellationToken).ConfigureAwait(false)) - { - return _result.Body; - } - } - {{end}} - - {{end}} -} From 7a3f951a04ee51aeebf6d3dcfb9c875c9a2f7561 Mon Sep 17 00:00:00 2001 From: Ayr Loong Date: Wed, 30 Apr 2025 12:24:46 +0800 Subject: [PATCH 04/12] fix --- .../templates/Client.cs.template | 176 +++++++++--------- 1 file changed, 83 insertions(+), 93 deletions(-) diff --git a/src/LibKubernetesGenerator/templates/Client.cs.template b/src/LibKubernetesGenerator/templates/Client.cs.template index 23bb88863..2ec934b83 100644 --- a/src/LibKubernetesGenerator/templates/Client.cs.template +++ b/src/LibKubernetesGenerator/templates/Client.cs.template @@ -8,103 +8,93 @@ using System.Net.Http.Headers; namespace k8s.ClientSets; - /// /// public partial class {{name}}Client : ResourceClient { - public {{name}}Client(IKubernetes kubernetes) : base(kubernetes) - { - } - - {{for api in apis }} - - /// - /// {{ToXmlDoc api.operation.description}} - /// - /// - /// The client group for this extension method. - /// - {{ for parameter in api.operation.parameters}} - /// - /// {{ToXmlDoc parameter.description}} - /// - {{end}} - /// - /// A which can be used to cancel the asynchronous operation. - /// - public async Task{{GetReturnType api.operation "<>"}} {{GetActionName api name "Async"}}( - {{ for parameter in api.operation.parameters}} - {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, - {{ end }} - CancellationToken cancellationToken = default(CancellationToken)) - { - {{if IfReturnType api.operation "stream"}} - var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( - {{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, - {{end}} - null, - cancellationToken); - _result.Request.Dispose(); - {{GetReturnType api.operation "_result.Body"}}; - {{end}} - {{if IfReturnType api.operation "obj"}} - using (var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( - {{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, - {{end}} - null, - cancellationToken).ConfigureAwait(false)) - { - {{GetReturnType api.operation "_result.Body"}}; - } - {{end}} - {{if IfReturnType api.operation "void"}} - using (var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( - {{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, - {{end}} - null, - cancellationToken).ConfigureAwait(false)) - { - } - {{end}} - } - - {{if IfReturnType api.operation "object"}} - /// - /// {{ToXmlDoc api.operation.description}} - /// - /// - /// The client group for this extension method. - /// - {{ for parameter in api.operation.parameters}} - /// - /// {{ToXmlDoc parameter.description}} - /// - {{end}} - /// - /// A which can be used to cancel the asynchronous operation. - /// - public async Task {{GetActionName api name "Async"}}( - {{ for parameter in api.operation.parameters}} - {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, - {{ end }} - CancellationToken cancellationToken = default(CancellationToken)) - { - using (var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( - {{ for parameter in api.operation.parameters}} - {{GetDotNetNameOpenApiParameter parameter "false"}}, - {{end}} - null, - cancellationToken).ConfigureAwait(false)) - { - return _result.Body; - } - } - {{end}} + public {{name}}Client(IKubernetes kubernetes) : base(kubernetes) + { + } - {{end}} + {{for api in apis }} + /// + /// {{ToXmlDoc api.operation.description}} + /// + {{ for parameter in api.operation.parameters}} + /// + /// {{ToXmlDoc parameter.description}} + /// + {{end}} + /// + /// A which can be used to cancel the asynchronous operation. + /// + public async Task{{GetReturnType api.operation "<>"}} {{GetActionName api name "Async"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, + {{ end }} + CancellationToken cancellationToken = default(CancellationToken)) + { + {{if IfReturnType api.operation "stream"}} + var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{end}} + null, + cancellationToken); + _result.Request.Dispose(); + {{GetReturnType api.operation "_result.Body"}}; + {{end}} + {{if IfReturnType api.operation "obj"}} + using (var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{end}} + null, + cancellationToken).ConfigureAwait(false)) + { + {{GetReturnType api.operation "_result.Body"}}; + } + {{end}} + {{if IfReturnType api.operation "void"}} + using (var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{end}} + null, + cancellationToken).ConfigureAwait(false)) + { + } + {{end}} + } + {{if IfReturnType api.operation "object"}} + /// + /// {{ToXmlDoc api.operation.description}} + /// + {{ for parameter in api.operation.parameters}} + /// + /// {{ToXmlDoc parameter.description}} + /// + {{end}} + /// + /// A which can be used to cancel the asynchronous operation. + /// + public async Task {{GetActionName api name "Async"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{ end }} + CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{end}} + null, + cancellationToken).ConfigureAwait(false)) + { + return _result.Body; + } + } + {{end}} + {{end}} } From 13478260edaa1a081693254af2e6fbb3a73525b1 Mon Sep 17 00:00:00 2001 From: endless001 Date: Tue, 6 May 2025 10:26:10 +0800 Subject: [PATCH 05/12] Clean code style warnings --- examples/clientset/Program.cs | 2 -- src/KubernetesClient/ClientSets/ClientSet.cs | 1 - src/KubernetesClient/ClientSets/ResourceClient.cs | 1 - src/LibKubernetesGenerator/GeneralNameHelper.cs | 6 ++---- 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/examples/clientset/Program.cs b/examples/clientset/Program.cs index c011dbcbc..44fd87fba 100644 --- a/examples/clientset/Program.cs +++ b/examples/clientset/Program.cs @@ -9,7 +9,6 @@ internal class Program { private static async Task Main(string[] args) { - var config = KubernetesClientConfiguration.BuildConfigFromConfigFile(); IKubernetes client = new Kubernetes(config); @@ -24,5 +23,4 @@ private static async Task Main(string[] args) System.Console.WriteLine(pod?.Metadata?.Name); } } - } diff --git a/src/KubernetesClient/ClientSets/ClientSet.cs b/src/KubernetesClient/ClientSets/ClientSet.cs index c17a35829..58c87c24d 100644 --- a/src/KubernetesClient/ClientSets/ClientSet.cs +++ b/src/KubernetesClient/ClientSets/ClientSet.cs @@ -2,5 +2,4 @@ namespace k8s.ClientSets; public partial class ClientSet { - } diff --git a/src/KubernetesClient/ClientSets/ResourceClient.cs b/src/KubernetesClient/ClientSets/ResourceClient.cs index 1ef6ee579..75683a885 100644 --- a/src/KubernetesClient/ClientSets/ResourceClient.cs +++ b/src/KubernetesClient/ClientSets/ResourceClient.cs @@ -3,7 +3,6 @@ namespace k8s.ClientSets; public abstract class ResourceClient { protected Kubernetes Client { get; } - public ResourceClient(IKubernetes kubernetes) { Client = (Kubernetes)kubernetes; diff --git a/src/LibKubernetesGenerator/GeneralNameHelper.cs b/src/LibKubernetesGenerator/GeneralNameHelper.cs index 240f247a9..6a59b9f02 100644 --- a/src/LibKubernetesGenerator/GeneralNameHelper.cs +++ b/src/LibKubernetesGenerator/GeneralNameHelper.cs @@ -22,11 +22,9 @@ public void RegisterHelper(ScriptObject scriptObject) { scriptObject.Import(nameof(GetInterfaceName), new Func(GetInterfaceName)); scriptObject.Import(nameof(GetMethodName), new Func(GetMethodName)); - scriptObject.Import(nameof(GetActionName), - new Func(GetActionName)); + scriptObject.Import(nameof(GetActionName), new Func(GetActionName)); scriptObject.Import(nameof(GetDotNetName), new Func(GetDotNetName)); - scriptObject.Import(nameof(GetDotNetNameOpenApiParameter), - new Func(GetDotNetNameOpenApiParameter)); + scriptObject.Import(nameof(GetDotNetNameOpenApiParameter), new Func(GetDotNetNameOpenApiParameter)); } private string GetInterfaceName(JsonSchema definition) From 47b8330152421e72dceda9bedea1384e10ded6e6 Mon Sep 17 00:00:00 2001 From: endless001 Date: Thu, 15 May 2025 18:26:08 +0800 Subject: [PATCH 06/12] Refactor client constructors to use Kubernetes type instead of IKubernetes --- examples/clientset/Program.cs | 2 +- src/KubernetesClient/ClientSets/ResourceClient.cs | 4 ++-- src/LibKubernetesGenerator/GeneralNameHelper.cs | 4 ++-- src/LibKubernetesGenerator/templates/Client.cs.template | 6 +++--- src/LibKubernetesGenerator/templates/ClientSet.cs.template | 4 ++-- .../templates/GroupClient.cs.template | 5 +++-- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/examples/clientset/Program.cs b/examples/clientset/Program.cs index 44fd87fba..8fe2a0b2f 100644 --- a/examples/clientset/Program.cs +++ b/examples/clientset/Program.cs @@ -10,7 +10,7 @@ internal class Program private static async Task Main(string[] args) { var config = KubernetesClientConfiguration.BuildConfigFromConfigFile(); - IKubernetes client = new Kubernetes(config); + var client = new Kubernetes(config); ClientSet clientSet = new ClientSet(client); var list = await clientSet.CoreV1.Pods.ListAsync("default").ConfigureAwait(false); diff --git a/src/KubernetesClient/ClientSets/ResourceClient.cs b/src/KubernetesClient/ClientSets/ResourceClient.cs index 75683a885..9477afe69 100644 --- a/src/KubernetesClient/ClientSets/ResourceClient.cs +++ b/src/KubernetesClient/ClientSets/ResourceClient.cs @@ -3,8 +3,8 @@ namespace k8s.ClientSets; public abstract class ResourceClient { protected Kubernetes Client { get; } - public ResourceClient(IKubernetes kubernetes) + public ResourceClient(Kubernetes kubernetes) { - Client = (Kubernetes)kubernetes; + Client = kubernetes; } } diff --git a/src/LibKubernetesGenerator/GeneralNameHelper.cs b/src/LibKubernetesGenerator/GeneralNameHelper.cs index 6a59b9f02..0a4649c1d 100644 --- a/src/LibKubernetesGenerator/GeneralNameHelper.cs +++ b/src/LibKubernetesGenerator/GeneralNameHelper.cs @@ -22,7 +22,7 @@ public void RegisterHelper(ScriptObject scriptObject) { scriptObject.Import(nameof(GetInterfaceName), new Func(GetInterfaceName)); scriptObject.Import(nameof(GetMethodName), new Func(GetMethodName)); - scriptObject.Import(nameof(GetActionName), new Func(GetActionName)); + scriptObject.Import(nameof(GetActionTypeName), new Func(GetActionTypeName)); scriptObject.Import(nameof(GetDotNetName), new Func(GetDotNetName)); scriptObject.Import(nameof(GetDotNetNameOpenApiParameter), new Func(GetDotNetNameOpenApiParameter)); } @@ -164,7 +164,7 @@ public static string GetMethodName(OpenApiOperation watchOperation, string suffi return methodName; } - public static string GetActionName(OpenApiOperationDescription apiOperation, string resource, string suffix) + public static string GetActionTypeName(OpenApiOperationDescription apiOperation, string resource, string suffix) { var actionType = apiOperation.Operation?.ExtensionData?["x-kubernetes-action"] as string; diff --git a/src/LibKubernetesGenerator/templates/Client.cs.template b/src/LibKubernetesGenerator/templates/Client.cs.template index 2ec934b83..16ce17394 100644 --- a/src/LibKubernetesGenerator/templates/Client.cs.template +++ b/src/LibKubernetesGenerator/templates/Client.cs.template @@ -12,7 +12,7 @@ namespace k8s.ClientSets; /// public partial class {{name}}Client : ResourceClient { - public {{name}}Client(IKubernetes kubernetes) : base(kubernetes) + public {{name}}Client(Kubernetes kubernetes) : base(kubernetes) { } @@ -28,7 +28,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 name "Async"}}( + public async Task{{GetReturnType api.operation "<>"}} {{GetActionTypeName api name "Async"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, {{ end }} @@ -79,7 +79,7 @@ public partial class {{name}}Client : ResourceClient /// /// A which can be used to cancel the asynchronous operation. /// - public async Task {{GetActionName api name "Async"}}( + public async Task {{GetActionTypeName api name "Async"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{ end }} diff --git a/src/LibKubernetesGenerator/templates/ClientSet.cs.template b/src/LibKubernetesGenerator/templates/ClientSet.cs.template index 81a9d8151..6254a9e5f 100644 --- a/src/LibKubernetesGenerator/templates/ClientSet.cs.template +++ b/src/LibKubernetesGenerator/templates/ClientSet.cs.template @@ -11,10 +11,10 @@ namespace k8s.ClientSets; public partial class ClientSet { {{for group in groups}} - public {{group}}GroupClient {{group}}; + public {{group}}GroupClient {{group}} { get; } {{end}} - public ClientSet(IKubernetes kubernetes) + public ClientSet(Kubernetes kubernetes) { {{for group in groups}} {{group}} = new {{group}}GroupClient(kubernetes); diff --git a/src/LibKubernetesGenerator/templates/GroupClient.cs.template b/src/LibKubernetesGenerator/templates/GroupClient.cs.template index d12a24773..228b2895f 100644 --- a/src/LibKubernetesGenerator/templates/GroupClient.cs.template +++ b/src/LibKubernetesGenerator/templates/GroupClient.cs.template @@ -11,11 +11,12 @@ namespace k8s.ClientSets; /// public partial class {{name}}GroupClient { + {{for client in clients}} - public {{client}}Client {{client}}; + public {{client}}Client {{client}} { get; } {{end}} - public {{name}}GroupClient(IKubernetes kubernetes) + public {{name}}GroupClient(Kubernetes kubernetes) { {{for client in clients}} {{client}} = new {{client}}Client(kubernetes); From 05da1c44f99acbd69eb10f1868c1bd186c00da40 Mon Sep 17 00:00:00 2001 From: endless001 Date: Fri, 16 May 2025 10:59:29 +0800 Subject: [PATCH 07/12] Add ClientSet tests for Kubernetes pod operations --- .../GeneralNameHelper.cs | 8 +- .../templates/Client.cs.template | 12 +- .../templates/IOperations.cs.template | 4 +- .../templates/Operations.cs.template | 18 +-- .../OperationsExtensions.cs.template | 20 ++-- tests/E2E.Tests/MinikubeTests.cs | 112 ++++++++++++++++++ 6 files changed, 143 insertions(+), 31 deletions(-) diff --git a/src/LibKubernetesGenerator/GeneralNameHelper.cs b/src/LibKubernetesGenerator/GeneralNameHelper.cs index 0a4649c1d..907fdbc2c 100644 --- a/src/LibKubernetesGenerator/GeneralNameHelper.cs +++ b/src/LibKubernetesGenerator/GeneralNameHelper.cs @@ -21,8 +21,8 @@ public GeneralNameHelper(ClassNameHelper classNameHelper) public void RegisterHelper(ScriptObject scriptObject) { scriptObject.Import(nameof(GetInterfaceName), new Func(GetInterfaceName)); - scriptObject.Import(nameof(GetMethodName), new Func(GetMethodName)); - scriptObject.Import(nameof(GetActionTypeName), new Func(GetActionTypeName)); + scriptObject.Import(nameof(GetOperationId), new Func(GetOperationId)); + scriptObject.Import(nameof(GetActionName), new Func(GetActionName)); scriptObject.Import(nameof(GetDotNetName), new Func(GetDotNetName)); scriptObject.Import(nameof(GetDotNetNameOpenApiParameter), new Func(GetDotNetNameOpenApiParameter)); } @@ -139,7 +139,7 @@ public string GetDotNetName(string jsonName, string style = "parameter") return jsonName.ToCamelCase(); } - public static string GetMethodName(OpenApiOperation watchOperation, string suffix) + public static string GetOperationId(OpenApiOperation watchOperation, string suffix) { var tag = watchOperation.Tags[0]; tag = tag.Replace("_", string.Empty); @@ -164,7 +164,7 @@ public static string GetMethodName(OpenApiOperation watchOperation, string suffi return methodName; } - public static string GetActionTypeName(OpenApiOperationDescription apiOperation, string resource, string suffix) + public static string GetActionName(OpenApiOperationDescription apiOperation, string resource, string suffix) { var actionType = apiOperation.Operation?.ExtensionData?["x-kubernetes-action"] as string; diff --git a/src/LibKubernetesGenerator/templates/Client.cs.template b/src/LibKubernetesGenerator/templates/Client.cs.template index 16ce17394..aa047a87e 100644 --- a/src/LibKubernetesGenerator/templates/Client.cs.template +++ b/src/LibKubernetesGenerator/templates/Client.cs.template @@ -28,14 +28,14 @@ public partial class {{name}}Client : ResourceClient /// /// A which can be used to cancel the asynchronous operation. /// - public async Task{{GetReturnType api.operation "<>"}} {{GetActionTypeName api name "Async"}}( + public async Task{{GetReturnType api.operation "<>"}} {{GetActionName api name "Async"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, {{ end }} CancellationToken cancellationToken = default(CancellationToken)) { {{if IfReturnType api.operation "stream"}} - var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + var _result = await Client.{{group}}.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} @@ -45,7 +45,7 @@ public partial class {{name}}Client : ResourceClient {{GetReturnType api.operation "_result.Body"}}; {{end}} {{if IfReturnType api.operation "obj"}} - using (var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + using (var _result = await Client.{{group}}.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} @@ -56,7 +56,7 @@ public partial class {{name}}Client : ResourceClient } {{end}} {{if IfReturnType api.operation "void"}} - using (var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + using (var _result = await Client.{{group}}.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} @@ -79,13 +79,13 @@ public partial class {{name}}Client : ResourceClient /// /// A which can be used to cancel the asynchronous operation. /// - public async Task {{GetActionTypeName api name "Async"}}( + public async Task {{GetActionName api name "Async"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{ end }} CancellationToken cancellationToken = default(CancellationToken)) { - using (var _result = await Client.{{group}}.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + using (var _result = await Client.{{group}}.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} diff --git a/src/LibKubernetesGenerator/templates/IOperations.cs.template b/src/LibKubernetesGenerator/templates/IOperations.cs.template index c310acf9f..6904b8b91 100644 --- a/src/LibKubernetesGenerator/templates/IOperations.cs.template +++ b/src/LibKubernetesGenerator/templates/IOperations.cs.template @@ -25,7 +25,7 @@ public partial interface I{{name}}Operations /// /// A which can be used to cancel the asynchronous operation. /// - Task"}}> {{GetMethodName api.operation "WithHttpMessagesAsync"}}( + Task"}}> {{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, {{ end }} @@ -47,7 +47,7 @@ public partial interface I{{name}}Operations /// /// A which can be used to cancel the asynchronous operation. /// - Task> {{GetMethodName api.operation "WithHttpMessagesAsync"}}( + Task> {{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, {{ end }} diff --git a/src/LibKubernetesGenerator/templates/Operations.cs.template b/src/LibKubernetesGenerator/templates/Operations.cs.template index 876ed8a16..d98337ada 100644 --- a/src/LibKubernetesGenerator/templates/Operations.cs.template +++ b/src/LibKubernetesGenerator/templates/Operations.cs.template @@ -10,13 +10,13 @@ public partial class AbstractKubernetes : I{{name}}Operations { {{for api in apis }} {{if IfReturnType api.operation "void"}} - private async Task I{{name}}Operations_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + private async Task I{{name}}Operations_{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{end}} {{if IfReturnType api.operation "obj"}} - private async Task> I{{name}}Operations_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + private async Task> I{{name}}Operations_{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{end}} {{if IfReturnType api.operation "stream"}} - private async Task> I{{name}}Operations_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + private async Task> I{{name}}Operations_{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{end}} {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, @@ -90,7 +90,7 @@ public partial class AbstractKubernetes : I{{name}}Operations } /// - async Task"}}> I{{name}}Operations.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + async Task"}}> I{{name}}Operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} @@ -98,7 +98,7 @@ public partial class AbstractKubernetes : I{{name}}Operations CancellationToken cancellationToken) { {{if IfReturnType api.operation "void"}} - return await I{{name}}Operations_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + return await I{{name}}Operations_{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} @@ -106,7 +106,7 @@ public partial class AbstractKubernetes : I{{name}}Operations cancellationToken).ConfigureAwait(false); {{end}} {{if IfReturnType api.operation "obj"}} - return await I{{name}}Operations_{{GetMethodName api.operation "WithHttpMessagesAsync"}}{{GetReturnType api.operation "<>"}}( + return await I{{name}}Operations_{{GetOperationId api.operation "WithHttpMessagesAsync"}}{{GetReturnType api.operation "<>"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} @@ -114,7 +114,7 @@ public partial class AbstractKubernetes : I{{name}}Operations cancellationToken).ConfigureAwait(false); {{end}} {{if IfReturnType api.operation "stream"}} - return await I{{name}}Operations_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + return await I{{name}}Operations_{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} @@ -125,14 +125,14 @@ public partial class AbstractKubernetes : I{{name}}Operations {{if IfReturnType api.operation "object"}} /// - async Task> I{{name}}Operations.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + async Task> I{{name}}Operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} IReadOnlyDictionary> customHeaders, CancellationToken cancellationToken) { - return await I{{name}}Operations_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + return await I{{name}}Operations_{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} diff --git a/src/LibKubernetesGenerator/templates/OperationsExtensions.cs.template b/src/LibKubernetesGenerator/templates/OperationsExtensions.cs.template index 98c304ede..b05f0e243 100644 --- a/src/LibKubernetesGenerator/templates/OperationsExtensions.cs.template +++ b/src/LibKubernetesGenerator/templates/OperationsExtensions.cs.template @@ -23,14 +23,14 @@ public static partial class {{name}}OperationsExtensions /// {{ToXmlDoc api.description}} /// {{ end }} - public static {{GetReturnType api.operation "void"}} {{GetMethodName api.operation ""}}( + public static {{GetReturnType api.operation "void"}} {{GetOperationId api.operation ""}}( this I{{name}}Operations operations {{ for parameter in api.operation.parameters}} ,{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}} {{end}} ) { - {{GetReturnType api.operation "return"}} operations.{{GetMethodName api.operation "Async"}}( + {{GetReturnType api.operation "return"}} operations.{{GetOperationId api.operation "Async"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} @@ -50,14 +50,14 @@ public static partial class {{name}}OperationsExtensions /// {{ToXmlDoc parameter.description}} /// {{end}} - public static T {{GetMethodName api.operation ""}}( + public static T {{GetOperationId api.operation ""}}( this I{{name}}Operations operations {{ for parameter in api.operation.parameters}} ,{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}} {{end}} ) { - return operations.{{GetMethodName api.operation "Async"}}( + return operations.{{GetOperationId api.operation "Async"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} @@ -80,7 +80,7 @@ public static partial class {{name}}OperationsExtensions /// /// A which can be used to cancel the asynchronous operation. /// - public static async Task{{GetReturnType api.operation "<>"}} {{GetMethodName api.operation "Async"}}( + public static async Task{{GetReturnType api.operation "<>"}} {{GetOperationId api.operation "Async"}}( this I{{name}}Operations operations, {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, @@ -88,7 +88,7 @@ public static partial class {{name}}OperationsExtensions CancellationToken cancellationToken = default(CancellationToken)) { {{if IfReturnType api.operation "stream"}} - var _result = await operations.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + var _result = await operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} @@ -98,7 +98,7 @@ public static partial class {{name}}OperationsExtensions {{GetReturnType api.operation "_result.Body"}}; {{end}} {{if IfReturnType api.operation "obj"}} - using (var _result = await operations.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + using (var _result = await operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} @@ -109,7 +109,7 @@ public static partial class {{name}}OperationsExtensions } {{end}} {{if IfReturnType api.operation "void"}} - using (var _result = await operations.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + using (var _result = await operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} @@ -135,14 +135,14 @@ public static partial class {{name}}OperationsExtensions /// /// A which can be used to cancel the asynchronous operation. /// - public static async Task {{GetMethodName api.operation "Async"}}( + public static async Task {{GetOperationId api.operation "Async"}}( this I{{name}}Operations operations, {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, {{ end }} CancellationToken cancellationToken = default(CancellationToken)) { - using (var _result = await operations.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( + using (var _result = await operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{end}} diff --git a/tests/E2E.Tests/MinikubeTests.cs b/tests/E2E.Tests/MinikubeTests.cs index fc5136d5a..d25b12a24 100644 --- a/tests/E2E.Tests/MinikubeTests.cs +++ b/tests/E2E.Tests/MinikubeTests.cs @@ -16,6 +16,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using k8s.ClientSets; using Xunit; namespace k8s.E2E @@ -584,6 +585,117 @@ await genericPods.CreateNamespacedAsync( } } + [MinikubeFact] + public async Task ClientSetTest() + { + var namespaceParameter = "default"; + var podName = "k8scsharp-e2e-clinetset-pod"; + + using var kubernetes = CreateClient(); + + var clientSet = new ClientSet((Kubernetes)kubernetes); + async Task Cleanup() + { + var pods = await clientSet.CoreV1.Pods.ListAsync(namespaceParameter).ConfigureAwait(false); + while (pods.Items.Any(p => p.Metadata.Name == podName)) + { + try + { + await clientSet.CoreV1.Pods.DeleteAsync(podName, namespaceParameter).ConfigureAwait(false); + } + catch (HttpOperationException e) + { + if (e.Response.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return; + } + } + } + } + + try + { + await Cleanup().ConfigureAwait(false); + + // create + list + { + await clientSet.CoreV1.Pods.PutAsync( + new V1Pod() + { + Metadata = new V1ObjectMeta { Name = podName, Labels = new Dictionary { { "place", "holder" }, }, }, + Spec = new V1PodSpec + { + Containers = new[] { new V1Container() { Name = "k8scsharp-e2e", Image = "nginx", }, }, + }, + }, + podName, + namespaceParameter).ConfigureAwait(false); + + var pods = await clientSet.CoreV1.Pods.ListAsync(namespaceParameter).ConfigureAwait(false); + Assert.Contains(pods.Items, p => p.Metadata.Name == podName); + } + + // replace + get + { + var pod = await clientSet.CoreV1.Pods.GetAsync(namespaceParameter, podName).ConfigureAwait(false); + var old = JsonSerializer.SerializeToDocument(pod); + + var newLabels = new Dictionary(pod.Metadata.Labels) { ["test"] = "clinetset-test-jsonpatch" }; + pod.Metadata.Labels = newLabels; + + var expected = JsonSerializer.SerializeToDocument(pod); + var patch = old.CreatePatch(expected); + + await clientSet.CoreV1.Pods.PatchAsync(new V1Patch(patch, V1Patch.PatchType.JsonPatch), namespaceParameter, podName).ConfigureAwait(false); + var pods = await clientSet.CoreV1.Pods.ListAsync(namespaceParameter).ConfigureAwait(false); + Assert.Contains(pods.Items, p => p.Labels().Contains(new KeyValuePair("test", "clinetset-test-jsonpatch"))); + } + + // replace + get + { + var pod = await clientSet.CoreV1.Pods.GetAsync(namespaceParameter, podName).ConfigureAwait(false); + pod.Spec.Containers[0].Image = "httpd"; + await clientSet.CoreV1.Pods.PutAsync(pod, namespaceParameter, podName).ConfigureAwait(false); + + pod = await clientSet.CoreV1.Pods.GetAsync(namespaceParameter, podName).ConfigureAwait(false); + Assert.Equal("httpd", pod.Spec.Containers[0].Image); + } + + // delete + list + { + var pods = new V1PodList(); + var retry = 5; + while (retry-- > 0) + { + try + { + await clientSet.CoreV1.Pods.DeleteAsync(namespaceParameter, podName).ConfigureAwait(false); + } + catch (HttpOperationException e) + { + if (e.Response.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return; + } + } + + pods = await clientSet.CoreV1.Pods.ListAsync(namespaceParameter).ConfigureAwait(false); + if (pods.Items.All(p => p.Metadata.Name != podName)) + { + break; + } + + await Task.Delay(TimeSpan.FromSeconds(2.5)).ConfigureAwait(false); + } + + Assert.DoesNotContain(pods.Items, p => p.Metadata.Name == podName); + } + } + finally + { + await Cleanup().ConfigureAwait(false); + } + } [MinikubeFact] public async Task CopyToPodTestAsync() From 56f63e50231068968bd58407eb8fe782a006bd20 Mon Sep 17 00:00:00 2001 From: endless001 Date: Fri, 16 May 2025 12:25:34 +0800 Subject: [PATCH 08/12] Fix order of parameters in Pod API calls for consistency --- tests/E2E.Tests/MinikubeTests.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/E2E.Tests/MinikubeTests.cs b/tests/E2E.Tests/MinikubeTests.cs index d25b12a24..37c8532c2 100644 --- a/tests/E2E.Tests/MinikubeTests.cs +++ b/tests/E2E.Tests/MinikubeTests.cs @@ -619,7 +619,7 @@ async Task Cleanup() // create + list { - await clientSet.CoreV1.Pods.PutAsync( + await clientSet.CoreV1.Pods.PostAsync( new V1Pod() { Metadata = new V1ObjectMeta { Name = podName, Labels = new Dictionary { { "place", "holder" }, }, }, @@ -628,7 +628,6 @@ await clientSet.CoreV1.Pods.PutAsync( Containers = new[] { new V1Container() { Name = "k8scsharp-e2e", Image = "nginx", }, }, }, }, - podName, namespaceParameter).ConfigureAwait(false); var pods = await clientSet.CoreV1.Pods.ListAsync(namespaceParameter).ConfigureAwait(false); @@ -637,7 +636,7 @@ await clientSet.CoreV1.Pods.PutAsync( // replace + get { - var pod = await clientSet.CoreV1.Pods.GetAsync(namespaceParameter, podName).ConfigureAwait(false); + var pod = await clientSet.CoreV1.Pods.GetAsync(podName, namespaceParameter).ConfigureAwait(false); var old = JsonSerializer.SerializeToDocument(pod); var newLabels = new Dictionary(pod.Metadata.Labels) { ["test"] = "clinetset-test-jsonpatch" }; @@ -646,18 +645,20 @@ await clientSet.CoreV1.Pods.PutAsync( var expected = JsonSerializer.SerializeToDocument(pod); var patch = old.CreatePatch(expected); - await clientSet.CoreV1.Pods.PatchAsync(new V1Patch(patch, V1Patch.PatchType.JsonPatch), namespaceParameter, podName).ConfigureAwait(false); + await clientSet.CoreV1.Pods + .PatchAsync(new V1Patch(patch, V1Patch.PatchType.JsonPatch), podName, namespaceParameter) + .ConfigureAwait(false); var pods = await clientSet.CoreV1.Pods.ListAsync(namespaceParameter).ConfigureAwait(false); Assert.Contains(pods.Items, p => p.Labels().Contains(new KeyValuePair("test", "clinetset-test-jsonpatch"))); } // replace + get { - var pod = await clientSet.CoreV1.Pods.GetAsync(namespaceParameter, podName).ConfigureAwait(false); + var pod = await clientSet.CoreV1.Pods.GetAsync(podName, namespaceParameter).ConfigureAwait(false); pod.Spec.Containers[0].Image = "httpd"; - await clientSet.CoreV1.Pods.PutAsync(pod, namespaceParameter, podName).ConfigureAwait(false); + await clientSet.CoreV1.Pods.PutAsync(pod, podName, namespaceParameter).ConfigureAwait(false); - pod = await clientSet.CoreV1.Pods.GetAsync(namespaceParameter, podName).ConfigureAwait(false); + pod = await clientSet.CoreV1.Pods.GetAsync(podName, namespaceParameter).ConfigureAwait(false); Assert.Equal("httpd", pod.Spec.Containers[0].Image); } @@ -669,7 +670,7 @@ await clientSet.CoreV1.Pods.PutAsync( { try { - await clientSet.CoreV1.Pods.DeleteAsync(namespaceParameter, podName).ConfigureAwait(false); + await clientSet.CoreV1.Pods.DeleteAsync(podName, namespaceParameter).ConfigureAwait(false); } catch (HttpOperationException e) { From 5c3f9a4c65da491e76c3f5ff1ab169ba515dc1c4 Mon Sep 17 00:00:00 2001 From: endless001 Date: Fri, 16 May 2025 12:32:52 +0800 Subject: [PATCH 09/12] Enhance documentation for ClientSet and ResourceClient classes --- src/KubernetesClient/ClientSets/ClientSet.cs | 11 ++++++++--- .../ClientSets/ResourceClient.cs | 18 ++++++++++++------ tests/E2E.Tests/MinikubeTests.cs | 6 +++--- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/KubernetesClient/ClientSets/ClientSet.cs b/src/KubernetesClient/ClientSets/ClientSet.cs index 58c87c24d..ed0d49624 100644 --- a/src/KubernetesClient/ClientSets/ClientSet.cs +++ b/src/KubernetesClient/ClientSets/ClientSet.cs @@ -1,5 +1,10 @@ -namespace k8s.ClientSets; - -public partial class ClientSet +namespace k8s.ClientSets { + /// + /// Represents a base class for clients that interact with Kubernetes resources. + /// Provides shared functionality for derived resource-specific clients. + /// + public partial class ClientSet + { + } } diff --git a/src/KubernetesClient/ClientSets/ResourceClient.cs b/src/KubernetesClient/ClientSets/ResourceClient.cs index 9477afe69..bbf1c43e8 100644 --- a/src/KubernetesClient/ClientSets/ResourceClient.cs +++ b/src/KubernetesClient/ClientSets/ResourceClient.cs @@ -1,10 +1,16 @@ -namespace k8s.ClientSets; - -public abstract class ResourceClient +namespace k8s.ClientSets { - protected Kubernetes Client { get; } - public ResourceClient(Kubernetes kubernetes) + /// + /// Represents a set of Kubernetes clients for interacting with the Kubernetes API. + /// This class provides access to various client implementations for managing Kubernetes resources. + /// + public abstract class ResourceClient { - Client = kubernetes; + protected Kubernetes Client { get; } + + public ResourceClient(Kubernetes kubernetes) + { + Client = kubernetes; + } } } diff --git a/tests/E2E.Tests/MinikubeTests.cs b/tests/E2E.Tests/MinikubeTests.cs index 37c8532c2..5b3c48946 100644 --- a/tests/E2E.Tests/MinikubeTests.cs +++ b/tests/E2E.Tests/MinikubeTests.cs @@ -589,7 +589,7 @@ await genericPods.CreateNamespacedAsync( public async Task ClientSetTest() { var namespaceParameter = "default"; - var podName = "k8scsharp-e2e-clinetset-pod"; + var podName = "k8scsharp-e2e-clientset-pod"; using var kubernetes = CreateClient(); @@ -639,7 +639,7 @@ await clientSet.CoreV1.Pods.PostAsync( var pod = await clientSet.CoreV1.Pods.GetAsync(podName, namespaceParameter).ConfigureAwait(false); var old = JsonSerializer.SerializeToDocument(pod); - var newLabels = new Dictionary(pod.Metadata.Labels) { ["test"] = "clinetset-test-jsonpatch" }; + var newLabels = new Dictionary(pod.Metadata.Labels) { ["test"] = "clientset-test-jsonpatch" }; pod.Metadata.Labels = newLabels; var expected = JsonSerializer.SerializeToDocument(pod); @@ -649,7 +649,7 @@ await clientSet.CoreV1.Pods .PatchAsync(new V1Patch(patch, V1Patch.PatchType.JsonPatch), podName, namespaceParameter) .ConfigureAwait(false); var pods = await clientSet.CoreV1.Pods.ListAsync(namespaceParameter).ConfigureAwait(false); - Assert.Contains(pods.Items, p => p.Labels().Contains(new KeyValuePair("test", "clinetset-test-jsonpatch"))); + Assert.Contains(pods.Items, p => p.Labels().Contains(new KeyValuePair("test", "clientset-test-jsonpatch"))); } // replace + get From 177809d5bc1c397a9417a4b2491b321912640a6a Mon Sep 17 00:00:00 2001 From: endless01 Date: Sun, 18 May 2025 13:56:45 +0800 Subject: [PATCH 10/12] Refactor ClientSet and GroupClient for Kubernetes usage --- src/KubernetesClient/ClientSets/ClientSet.cs | 6 ++++++ .../templates/ClientSet.cs.template | 9 +-------- .../templates/GroupClient.cs.template | 7 +++---- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/KubernetesClient/ClientSets/ClientSet.cs b/src/KubernetesClient/ClientSets/ClientSet.cs index ed0d49624..53aa37df9 100644 --- a/src/KubernetesClient/ClientSets/ClientSet.cs +++ b/src/KubernetesClient/ClientSets/ClientSet.cs @@ -6,5 +6,11 @@ namespace k8s.ClientSets /// public partial class ClientSet { + private readonly Kubernetes _kubernetes; + + public ClientSet(Kubernetes kubernetes) + { + _kubernetes = kubernetes; + } } } diff --git a/src/LibKubernetesGenerator/templates/ClientSet.cs.template b/src/LibKubernetesGenerator/templates/ClientSet.cs.template index 6254a9e5f..c358b2b3c 100644 --- a/src/LibKubernetesGenerator/templates/ClientSet.cs.template +++ b/src/LibKubernetesGenerator/templates/ClientSet.cs.template @@ -11,13 +11,6 @@ namespace k8s.ClientSets; public partial class ClientSet { {{for group in groups}} - public {{group}}GroupClient {{group}} { get; } + public {{group}}GroupClient {{group}} => new {{group}}GroupClient(_kubernetes); {{end}} - - public ClientSet(Kubernetes kubernetes) - { - {{for group in groups}} - {{group}} = new {{group}}GroupClient(kubernetes); - {{end}} - } } diff --git a/src/LibKubernetesGenerator/templates/GroupClient.cs.template b/src/LibKubernetesGenerator/templates/GroupClient.cs.template index 228b2895f..45f219e55 100644 --- a/src/LibKubernetesGenerator/templates/GroupClient.cs.template +++ b/src/LibKubernetesGenerator/templates/GroupClient.cs.template @@ -11,15 +11,14 @@ namespace k8s.ClientSets; /// public partial class {{name}}GroupClient { + private readonly Kubernetes _kubernetes; {{for client in clients}} - public {{client}}Client {{client}} { get; } + public {{client}}Client {{client}} => new {{client}}Client(_kubernetes); {{end}} public {{name}}GroupClient(Kubernetes kubernetes) { - {{for client in clients}} - {{client}} = new {{client}}Client(kubernetes); - {{end}} + _kubernetes = kubernetes; } } From 3085283114f14e3b9e023ae88f9729754d3db8d9 Mon Sep 17 00:00:00 2001 From: endless001 Date: Thu, 22 May 2025 12:31:13 +0800 Subject: [PATCH 11/12] Refactor Pod API calls in tests to use singular form for consistency --- Directory.Packages.props | 1 - examples/clientset/Program.cs | 4 +- .../ClientSetGenerator.cs | 3 +- .../GeneralNameHelper.cs | 61 +++++-------------- .../LibKubernetesGenerator.target | 4 -- .../templates/Client.cs.template | 4 +- tests/E2E.Tests/MinikubeTests.cs | 24 ++++---- 7 files changed, 32 insertions(+), 69 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 005a782cc..2e1b01456 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -33,7 +33,6 @@ - diff --git a/examples/clientset/Program.cs b/examples/clientset/Program.cs index 8fe2a0b2f..9c2cac461 100644 --- a/examples/clientset/Program.cs +++ b/examples/clientset/Program.cs @@ -13,13 +13,13 @@ private static async Task Main(string[] args) var client = new Kubernetes(config); ClientSet clientSet = new ClientSet(client); - var list = await clientSet.CoreV1.Pods.ListAsync("default").ConfigureAwait(false); + 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.Pods.GetAsync("test","default").ConfigureAwait(false); + var pod = await clientSet.CoreV1.Pod.GetAsync("test","default").ConfigureAwait(false); System.Console.WriteLine(pod?.Metadata?.Name); } } diff --git a/src/LibKubernetesGenerator/ClientSetGenerator.cs b/src/LibKubernetesGenerator/ClientSetGenerator.cs index 7701adc0e..a0b392d8b 100644 --- a/src/LibKubernetesGenerator/ClientSetGenerator.cs +++ b/src/LibKubernetesGenerator/ClientSetGenerator.cs @@ -3,7 +3,6 @@ using NSwag; using System.Collections.Generic; using System.Linq; -using Humanizer; namespace LibKubernetesGenerator { @@ -71,7 +70,7 @@ public void Generate(OpenApiDocument swagger, IncrementalGeneratorPostInitializa foreach (var item in apis.GroupBy(x => x.Kind)) { - var kind = item.Key.Pluralize(); + var kind = item.Key; apiGroups[kind] = item.Select(x => x.Api).ToArray(); clients.Add(kind); } diff --git a/src/LibKubernetesGenerator/GeneralNameHelper.cs b/src/LibKubernetesGenerator/GeneralNameHelper.cs index 907fdbc2c..3c3077122 100644 --- a/src/LibKubernetesGenerator/GeneralNameHelper.cs +++ b/src/LibKubernetesGenerator/GeneralNameHelper.cs @@ -22,7 +22,7 @@ public void RegisterHelper(ScriptObject scriptObject) { scriptObject.Import(nameof(GetInterfaceName), new Func(GetInterfaceName)); scriptObject.Import(nameof(GetOperationId), new Func(GetOperationId)); - scriptObject.Import(nameof(GetActionName), new Func(GetActionName)); + scriptObject.Import(nameof(GetActionName), new Func(GetActionName)); scriptObject.Import(nameof(GetDotNetName), new Func(GetDotNetName)); scriptObject.Import(nameof(GetDotNetNameOpenApiParameter), new Func(GetDotNetNameOpenApiParameter)); } @@ -164,59 +164,28 @@ public static string GetOperationId(OpenApiOperation watchOperation, string suff return methodName; } - public static string GetActionName(OpenApiOperationDescription apiOperation, string resource, string suffix) + public static string GetActionName(OpenApiOperation apiOperation, string resource, string suffix) { - var actionType = apiOperation.Operation?.ExtensionData?["x-kubernetes-action"] as string; - - if (string.IsNullOrEmpty(actionType)) - { - return $"{apiOperation.Method.ToPascalCase()}{suffix}"; - } - - var resourceNamespace = ParsePathSegmentAfterParameter(apiOperation.Path, "namespace").ToPascalCase(); - var resourceName = ParsePathSegmentAfterParameter(apiOperation.Path, "name").ToPascalCase(); - var actionMappings = new Dictionary(StringComparer.OrdinalIgnoreCase) + var operationId = apiOperation.OperationId.ToPascalCase(); + var replacements = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { "get", "Get" }, - { "list", "List" }, - { "put", "Put" }, - { "patch", "Patch" }, - { "post", "Post" }, - { "delete", "Delete" }, - { "deletecollection", "DeleteCollection" }, - { "watch", "Watch" }, - { "watchlist", "WatchList" }, - { "proxy", "Proxy" }, + { "Replace", "Put" }, + { "Read", "Get" }, + { "Create", "Post" }, }; - if (actionMappings.TryGetValue(actionType, out var actionPrefix)) - { - return Regex.Replace($"{actionPrefix}{resourceNamespace}{resourceName}{suffix}", resource, string.Empty, - RegexOptions.IgnoreCase); - } - - if (string.Equals("connect", actionType, StringComparison.OrdinalIgnoreCase)) + foreach (var replacement in replacements) { - return Regex.Replace($"Connect{apiOperation.Method}{resourceNamespace}{resourceName}{suffix}", resource, - string.Empty, - RegexOptions.IgnoreCase); + operationId = Regex.Replace(operationId, replacement.Key, replacement.Value, RegexOptions.IgnoreCase); } - return $"{actionType.ToPascalCase()}{suffix}"; - } - - private static string ParsePathSegmentAfterParameter(string path, string variableName = "namespace") - { - var pattern = $@"/\{{{variableName}\}}/([^/]+)/?"; - - var match = Regex.Match(path, pattern); - - if (match.Success && match.Groups.Count > 1) - { - return match.Groups[1].Value; - } + var resources = new[] { resource, "ForAllNamespaces", "Namespaced" }; + var pattern = string.Join("|", Array.ConvertAll(resources, Regex.Escape)); + var actionName = pattern.Length > 0 + ? Regex.Replace(operationId, pattern, string.Empty, RegexOptions.IgnoreCase) + : operationId; - return string.Empty; + return $"{actionName}{suffix}"; } } } diff --git a/src/LibKubernetesGenerator/LibKubernetesGenerator.target b/src/LibKubernetesGenerator/LibKubernetesGenerator.target index fd84f9fb0..916c1dd19 100644 --- a/src/LibKubernetesGenerator/LibKubernetesGenerator.target +++ b/src/LibKubernetesGenerator/LibKubernetesGenerator.target @@ -23,9 +23,6 @@ - - - @@ -47,7 +44,6 @@ - diff --git a/src/LibKubernetesGenerator/templates/Client.cs.template b/src/LibKubernetesGenerator/templates/Client.cs.template index aa047a87e..02c55d7b1 100644 --- a/src/LibKubernetesGenerator/templates/Client.cs.template +++ b/src/LibKubernetesGenerator/templates/Client.cs.template @@ -28,7 +28,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 name "Async"}}( + public async Task{{GetReturnType api.operation "<>"}} {{GetActionName api.operation name "Async"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, {{ end }} @@ -79,7 +79,7 @@ public partial class {{name}}Client : ResourceClient /// /// A which can be used to cancel the asynchronous operation. /// - public async Task {{GetActionName api name "Async"}}( + public async Task {{GetActionName api.operation name "Async"}}( {{ for parameter in api.operation.parameters}} {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{ end }} diff --git a/tests/E2E.Tests/MinikubeTests.cs b/tests/E2E.Tests/MinikubeTests.cs index 5b3c48946..7fb0d03ec 100644 --- a/tests/E2E.Tests/MinikubeTests.cs +++ b/tests/E2E.Tests/MinikubeTests.cs @@ -596,12 +596,12 @@ public async Task ClientSetTest() var clientSet = new ClientSet((Kubernetes)kubernetes); async Task Cleanup() { - var pods = await clientSet.CoreV1.Pods.ListAsync(namespaceParameter).ConfigureAwait(false); + var pods = await clientSet.CoreV1.Pod.ListAsync(namespaceParameter).ConfigureAwait(false); while (pods.Items.Any(p => p.Metadata.Name == podName)) { try { - await clientSet.CoreV1.Pods.DeleteAsync(podName, namespaceParameter).ConfigureAwait(false); + await clientSet.CoreV1.Pod.DeleteAsync(podName, namespaceParameter).ConfigureAwait(false); } catch (HttpOperationException e) { @@ -619,7 +619,7 @@ async Task Cleanup() // create + list { - await clientSet.CoreV1.Pods.PostAsync( + await clientSet.CoreV1.Pod.PostAsync( new V1Pod() { Metadata = new V1ObjectMeta { Name = podName, Labels = new Dictionary { { "place", "holder" }, }, }, @@ -630,13 +630,13 @@ await clientSet.CoreV1.Pods.PostAsync( }, namespaceParameter).ConfigureAwait(false); - var pods = await clientSet.CoreV1.Pods.ListAsync(namespaceParameter).ConfigureAwait(false); + var pods = await clientSet.CoreV1.Pod.ListAsync(namespaceParameter).ConfigureAwait(false); Assert.Contains(pods.Items, p => p.Metadata.Name == podName); } // replace + get { - var pod = await clientSet.CoreV1.Pods.GetAsync(podName, namespaceParameter).ConfigureAwait(false); + var pod = await clientSet.CoreV1.Pod.GetAsync(podName, namespaceParameter).ConfigureAwait(false); var old = JsonSerializer.SerializeToDocument(pod); var newLabels = new Dictionary(pod.Metadata.Labels) { ["test"] = "clientset-test-jsonpatch" }; @@ -645,20 +645,20 @@ await clientSet.CoreV1.Pods.PostAsync( var expected = JsonSerializer.SerializeToDocument(pod); var patch = old.CreatePatch(expected); - await clientSet.CoreV1.Pods + await clientSet.CoreV1.Pod .PatchAsync(new V1Patch(patch, V1Patch.PatchType.JsonPatch), podName, namespaceParameter) .ConfigureAwait(false); - var pods = await clientSet.CoreV1.Pods.ListAsync(namespaceParameter).ConfigureAwait(false); + var pods = await clientSet.CoreV1.Pod.ListAsync(namespaceParameter).ConfigureAwait(false); Assert.Contains(pods.Items, p => p.Labels().Contains(new KeyValuePair("test", "clientset-test-jsonpatch"))); } // replace + get { - var pod = await clientSet.CoreV1.Pods.GetAsync(podName, namespaceParameter).ConfigureAwait(false); + var pod = await clientSet.CoreV1.Pod.GetAsync(podName, namespaceParameter).ConfigureAwait(false); pod.Spec.Containers[0].Image = "httpd"; - await clientSet.CoreV1.Pods.PutAsync(pod, podName, namespaceParameter).ConfigureAwait(false); + await clientSet.CoreV1.Pod.PutAsync(pod, podName, namespaceParameter).ConfigureAwait(false); - pod = await clientSet.CoreV1.Pods.GetAsync(podName, namespaceParameter).ConfigureAwait(false); + pod = await clientSet.CoreV1.Pod.GetAsync(podName, namespaceParameter).ConfigureAwait(false); Assert.Equal("httpd", pod.Spec.Containers[0].Image); } @@ -670,7 +670,7 @@ await clientSet.CoreV1.Pods { try { - await clientSet.CoreV1.Pods.DeleteAsync(podName, namespaceParameter).ConfigureAwait(false); + await clientSet.CoreV1.Pod.DeleteAsync(podName, namespaceParameter).ConfigureAwait(false); } catch (HttpOperationException e) { @@ -680,7 +680,7 @@ await clientSet.CoreV1.Pods } } - pods = await clientSet.CoreV1.Pods.ListAsync(namespaceParameter).ConfigureAwait(false); + pods = await clientSet.CoreV1.Pod.ListAsync(namespaceParameter).ConfigureAwait(false); if (pods.Items.All(p => p.Metadata.Name != podName)) { break; From 218bb319cfe68c3f4b2ed9f5b16b4db47896e041 Mon Sep 17 00:00:00 2001 From: endless001 Date: Thu, 22 May 2025 14:05:09 +0800 Subject: [PATCH 12/12] Refactor Pod API calls to use 'Create' and 'Update' methods for consistency --- src/LibKubernetesGenerator/GeneralNameHelper.cs | 3 +-- tests/E2E.Tests/MinikubeTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/LibKubernetesGenerator/GeneralNameHelper.cs b/src/LibKubernetesGenerator/GeneralNameHelper.cs index 3c3077122..15194bb90 100644 --- a/src/LibKubernetesGenerator/GeneralNameHelper.cs +++ b/src/LibKubernetesGenerator/GeneralNameHelper.cs @@ -169,9 +169,8 @@ public static string GetActionName(OpenApiOperation apiOperation, string resourc var operationId = apiOperation.OperationId.ToPascalCase(); var replacements = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { "Replace", "Put" }, + { "Replace", "Update" }, { "Read", "Get" }, - { "Create", "Post" }, }; foreach (var replacement in replacements) diff --git a/tests/E2E.Tests/MinikubeTests.cs b/tests/E2E.Tests/MinikubeTests.cs index 7fb0d03ec..547ee90ea 100644 --- a/tests/E2E.Tests/MinikubeTests.cs +++ b/tests/E2E.Tests/MinikubeTests.cs @@ -619,7 +619,7 @@ async Task Cleanup() // create + list { - await clientSet.CoreV1.Pod.PostAsync( + await clientSet.CoreV1.Pod.CreateAsync( new V1Pod() { Metadata = new V1ObjectMeta { Name = podName, Labels = new Dictionary { { "place", "holder" }, }, }, @@ -656,7 +656,7 @@ await clientSet.CoreV1.Pod { var pod = await clientSet.CoreV1.Pod.GetAsync(podName, namespaceParameter).ConfigureAwait(false); pod.Spec.Containers[0].Image = "httpd"; - await clientSet.CoreV1.Pod.PutAsync(pod, podName, namespaceParameter).ConfigureAwait(false); + await clientSet.CoreV1.Pod.UpdateAsync(pod, podName, namespaceParameter).ConfigureAwait(false); pod = await clientSet.CoreV1.Pod.GetAsync(podName, namespaceParameter).ConfigureAwait(false); Assert.Equal("httpd", pod.Spec.Containers[0].Image);