diff --git a/Directory.Packages.props b/Directory.Packages.props index 45f27d6cf..2e1b01456 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -50,4 +50,4 @@ - \ No newline at end of file + diff --git a/examples/clientset/Program.cs b/examples/clientset/Program.cs new file mode 100644 index 000000000..9c2cac461 --- /dev/null +++ b/examples/clientset/Program.cs @@ -0,0 +1,26 @@ +// 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(); + var client = new Kubernetes(config); + + ClientSet clientSet = new ClientSet(client); + var list = await clientSet.CoreV1.Pod.ListAsync("default").ConfigureAwait(false); + foreach (var item in list) + { + System.Console.WriteLine(item.Metadata.Name); + } + + var pod = await clientSet.CoreV1.Pod.GetAsync("test","default").ConfigureAwait(false); + System.Console.WriteLine(pod?.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/ClientSets/ClientSet.cs b/src/KubernetesClient/ClientSets/ClientSet.cs new file mode 100644 index 000000000..53aa37df9 --- /dev/null +++ b/src/KubernetesClient/ClientSets/ClientSet.cs @@ -0,0 +1,16 @@ +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 + { + private readonly Kubernetes _kubernetes; + + public ClientSet(Kubernetes kubernetes) + { + _kubernetes = kubernetes; + } + } +} diff --git a/src/KubernetesClient/ClientSets/ResourceClient.cs b/src/KubernetesClient/ClientSets/ResourceClient.cs new file mode 100644 index 000000000..bbf1c43e8 --- /dev/null +++ b/src/KubernetesClient/ClientSets/ResourceClient.cs @@ -0,0 +1,16 @@ +namespace k8s.ClientSets +{ + /// + /// 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 + { + protected Kubernetes Client { get; } + + public ResourceClient(Kubernetes kubernetes) + { + Client = kubernetes; + } + } +} 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/LibKubernetesGenerator/ClientSetGenerator.cs b/src/LibKubernetesGenerator/ClientSetGenerator.cs new file mode 100644 index 000000000..a0b392d8b --- /dev/null +++ b/src/LibKubernetesGenerator/ClientSetGenerator.cs @@ -0,0 +1,103 @@ +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"] as string, Api = x }; + }); + + foreach (var item in apis.GroupBy(x => x.Kind)) + { + var kind = item.Key; + 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(); + 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"); + } + + 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/GeneralNameHelper.cs b/src/LibKubernetesGenerator/GeneralNameHelper.cs index c9b5cf5af..15194bb90 100644 --- a/src/LibKubernetesGenerator/GeneralNameHelper.cs +++ b/src/LibKubernetesGenerator/GeneralNameHelper.cs @@ -21,7 +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(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)); } @@ -138,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); @@ -162,5 +163,28 @@ public static string GetMethodName(OpenApiOperation watchOperation, string suffi return methodName; } + + public static string GetActionName(OpenApiOperation apiOperation, string resource, string suffix) + { + var operationId = apiOperation.OperationId.ToPascalCase(); + var replacements = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "Replace", "Update" }, + { "Read", "Get" }, + }; + + foreach (var replacement in replacements) + { + operationId = Regex.Replace(operationId, replacement.Key, replacement.Value, RegexOptions.IgnoreCase); + } + + 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 $"{actionName}{suffix}"; + } } } diff --git a/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs b/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs index 0e6278fb4..6ea216f28 100644 --- a/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs +++ b/src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs @@ -61,6 +61,7 @@ private static IContainer BuildContainer(OpenApiDocument swagger) builder.RegisterType(); builder.RegisterType(); builder.RegisterType(); + builder.RegisterType(); builder.RegisterType(); builder.RegisterType(); @@ -80,6 +81,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/LibKubernetesGenerator.target b/src/LibKubernetesGenerator/LibKubernetesGenerator.target index d9240bcdd..916c1dd19 100644 --- a/src/LibKubernetesGenerator/LibKubernetesGenerator.target +++ b/src/LibKubernetesGenerator/LibKubernetesGenerator.target @@ -16,6 +16,7 @@ + diff --git a/src/LibKubernetesGenerator/templates/Client.cs.template b/src/LibKubernetesGenerator/templates/Client.cs.template new file mode 100644 index 000000000..02c55d7b1 --- /dev/null +++ b/src/LibKubernetesGenerator/templates/Client.cs.template @@ -0,0 +1,100 @@ +// +// 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(Kubernetes kubernetes) : base(kubernetes) + { + } + + {{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.operation 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}}.{{GetOperationId 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}}.{{GetOperationId 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}}.{{GetOperationId 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.operation name "Async"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{ end }} + CancellationToken cancellationToken = default(CancellationToken)) + { + using (var _result = await Client.{{group}}.{{GetOperationId api.operation "WithHttpMessagesAsync"}}( + {{ for parameter in api.operation.parameters}} + {{GetDotNetNameOpenApiParameter parameter "false"}}, + {{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..c358b2b3c --- /dev/null +++ b/src/LibKubernetesGenerator/templates/ClientSet.cs.template @@ -0,0 +1,16 @@ +// +// 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}} => 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..45f219e55 --- /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 +{ + private readonly Kubernetes _kubernetes; + + {{for client in clients}} + public {{client}}Client {{client}} => new {{client}}Client(_kubernetes); + {{end}} + + public {{name}}GroupClient(Kubernetes kubernetes) + { + _kubernetes = kubernetes; + } +} 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..547ee90ea 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,118 @@ await genericPods.CreateNamespacedAsync( } } + [MinikubeFact] + public async Task ClientSetTest() + { + var namespaceParameter = "default"; + var podName = "k8scsharp-e2e-clientset-pod"; + + using var kubernetes = CreateClient(); + + var clientSet = new ClientSet((Kubernetes)kubernetes); + async Task Cleanup() + { + var pods = await clientSet.CoreV1.Pod.ListAsync(namespaceParameter).ConfigureAwait(false); + while (pods.Items.Any(p => p.Metadata.Name == podName)) + { + try + { + await clientSet.CoreV1.Pod.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.Pod.CreateAsync( + new V1Pod() + { + Metadata = new V1ObjectMeta { Name = podName, Labels = new Dictionary { { "place", "holder" }, }, }, + Spec = new V1PodSpec + { + Containers = new[] { new V1Container() { Name = "k8scsharp-e2e", Image = "nginx", }, }, + }, + }, + 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.Pod.GetAsync(podName, namespaceParameter).ConfigureAwait(false); + var old = JsonSerializer.SerializeToDocument(pod); + + var newLabels = new Dictionary(pod.Metadata.Labels) { ["test"] = "clientset-test-jsonpatch" }; + pod.Metadata.Labels = newLabels; + + var expected = JsonSerializer.SerializeToDocument(pod); + var patch = old.CreatePatch(expected); + + await clientSet.CoreV1.Pod + .PatchAsync(new V1Patch(patch, V1Patch.PatchType.JsonPatch), podName, 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.Pod.GetAsync(podName, namespaceParameter).ConfigureAwait(false); + pod.Spec.Containers[0].Image = "httpd"; + 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); + } + + // delete + list + { + var pods = new V1PodList(); + var retry = 5; + while (retry-- > 0) + { + try + { + await clientSet.CoreV1.Pod.DeleteAsync(podName, namespaceParameter).ConfigureAwait(false); + } + catch (HttpOperationException e) + { + if (e.Response.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return; + } + } + + pods = await clientSet.CoreV1.Pod.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() 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,