Implementing a more modular API (#1627)

* fix typo

* Add modular API

* Use x-kubernetes-action instead of operationId to generate method names

* fix

* Clean code style warnings

* Refactor client constructors to use Kubernetes type instead of IKubernetes

* Add ClientSet tests for Kubernetes pod operations

* Fix order of parameters in Pod API calls for consistency

* Enhance documentation for ClientSet and ResourceClient classes

* Refactor ClientSet and GroupClient for Kubernetes usage

* Refactor Pod API calls in tests to use singular form for consistency

* Refactor Pod API calls to use 'Create' and 'Update' methods for consistency
This commit is contained in:
Ayr Loong
2025-05-22 15:56:39 +08:00
committed by GitHub
parent e6317b857d
commit f1125e9435
20 changed files with 488 additions and 34 deletions

View File

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

View File

@@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
</Project>

View File

@@ -46,6 +46,10 @@
<Compile Include="..\KubernetesClient\Models\V1Status.cs" /> <Compile Include="..\KubernetesClient\Models\V1Status.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="..\KubernetesClient\ClientSets\ClientSet.cs" />
<Compile Include="..\KubernetesClient\ClientSets\ResourceClient.cs"/>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\KubernetesClient\AbstractKubernetes.cs" /> <Compile Include="..\KubernetesClient\AbstractKubernetes.cs" />
<Compile Include="..\KubernetesClient\GeneratedApiVersion.cs" /> <Compile Include="..\KubernetesClient\GeneratedApiVersion.cs" />

View File

@@ -75,7 +75,10 @@
<Compile Include="..\KubernetesClient\Autorest\HttpRequestMessageWrapper.cs" /> <Compile Include="..\KubernetesClient\Autorest\HttpRequestMessageWrapper.cs" />
<Compile Include="..\KubernetesClient\Autorest\HttpResponseMessageWrapper.cs" /> <Compile Include="..\KubernetesClient\Autorest\HttpResponseMessageWrapper.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Compile Include="..\KubernetesClient\ClientSets\ClientSet.cs" />
<Compile Include="..\KubernetesClient\ClientSets\ResourceClient.cs"/>
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\KubernetesClient\FileSystem.cs" /> <Compile Include="..\KubernetesClient\FileSystem.cs" />
<Compile Include="..\KubernetesClient\IKubernetes.cs" /> <Compile Include="..\KubernetesClient\IKubernetes.cs" />

View File

@@ -0,0 +1,16 @@
namespace k8s.ClientSets
{
/// <summary>
/// Represents a base class for clients that interact with Kubernetes resources.
/// Provides shared functionality for derived resource-specific clients.
/// </summary>
public partial class ClientSet
{
private readonly Kubernetes _kubernetes;
public ClientSet(Kubernetes kubernetes)
{
_kubernetes = kubernetes;
}
}
}

View File

@@ -0,0 +1,16 @@
namespace k8s.ClientSets
{
/// <summary>
/// Represents a set of Kubernetes clients for interacting with the Kubernetes API.
/// This class provides access to various client implementations for managing Kubernetes resources.
/// </summary>
public abstract class ResourceClient
{
protected Kubernetes Client { get; }
public ResourceClient(Kubernetes kubernetes)
{
Client = kubernetes;
}
}
}

View File

@@ -18,11 +18,11 @@ namespace k8s
/// <inheritdoc/> /// <inheritdoc/>
public Task<WebSocket> WebSocketNamespacedPodExecAsync(string name, string @namespace = "default", public Task<WebSocket> WebSocketNamespacedPodExecAsync(string name, string @namespace = "default",
string command = null, string container = null, bool stderr = true, bool stdin = true, bool stdout = true, string command = null, string container = null, bool stderr = true, bool stdin = true, bool stdout = true,
bool tty = true, string webSocketSubProtol = null, Dictionary<string, List<string>> customHeaders = null, bool tty = true, string webSocketSubProtocol = null, Dictionary<string, List<string>> customHeaders = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return WebSocketNamespacedPodExecAsync(name, @namespace, new string[] { command }, container, stderr, stdin, return WebSocketNamespacedPodExecAsync(name, @namespace, new string[] { command }, container, stderr, stdin,
stdout, tty, webSocketSubProtol, customHeaders, cancellationToken); stdout, tty, webSocketSubProtocol, customHeaders, cancellationToken);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -30,7 +30,7 @@ namespace k8s
string name, string name,
string @namespace = "default", IEnumerable<string> command = null, string container = null, string @namespace = "default", IEnumerable<string> command = null, string container = null,
bool stderr = true, bool stdin = true, bool stdout = true, bool tty = true, bool stderr = true, bool stdin = true, bool stdout = true, bool tty = true,
string webSocketSubProtol = WebSocketProtocol.V4BinaryWebsocketProtocol, string webSocketSubProtocol = WebSocketProtocol.V4BinaryWebsocketProtocol,
Dictionary<string, List<string>> customHeaders = null, Dictionary<string, List<string>> customHeaders = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
@@ -45,7 +45,7 @@ namespace k8s
public virtual Task<WebSocket> WebSocketNamespacedPodExecAsync(string name, string @namespace = "default", public virtual Task<WebSocket> WebSocketNamespacedPodExecAsync(string name, string @namespace = "default",
IEnumerable<string> command = null, string container = null, bool stderr = true, bool stdin = true, IEnumerable<string> command = null, string container = null, bool stderr = true, bool stdin = true,
bool stdout = true, bool tty = true, bool stdout = true, bool tty = true,
string webSocketSubProtol = WebSocketProtocol.V4BinaryWebsocketProtocol, string webSocketSubProtocol = WebSocketProtocol.V4BinaryWebsocketProtocol,
Dictionary<string, List<string>> customHeaders = null, Dictionary<string, List<string>> customHeaders = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
@@ -114,7 +114,7 @@ namespace k8s
uriBuilder.Query = uriBuilder.Query =
query.ToString(1, query.Length - 1); // UriBuilder.Query doesn't like leading '?' chars, so trim it 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); cancellationToken);
} }
@@ -173,7 +173,7 @@ namespace k8s
/// <inheritdoc/> /// <inheritdoc/>
public Task<WebSocket> WebSocketNamespacedPodAttachAsync(string name, string @namespace, public Task<WebSocket> WebSocketNamespacedPodAttachAsync(string name, string @namespace,
string container = default, bool stderr = true, bool stdin = false, bool stdout = true, string container = default, bool stderr = true, bool stdin = false, bool stdout = true,
bool tty = false, string webSocketSubProtol = null, Dictionary<string, List<string>> customHeaders = null, bool tty = false, string webSocketSubProtocol = null, Dictionary<string, List<string>> customHeaders = null,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
if (name == null) if (name == null)
@@ -208,7 +208,7 @@ namespace k8s
uriBuilder.Query = uriBuilder.Query =
query.ToString(1, query.Length - 1); // UriBuilder.Query doesn't like leading '?' chars, so trim it 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); cancellationToken);
} }

View File

@@ -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<string>();
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<string>();
var apiGroups = new Dictionary<string, OpenApiOperationDescription[]>();
foreach (var grouped in data.Where(d => HasKubernetesAction(d.Operation?.ExtensionData))
.GroupBy(d => d.Operation.Tags.First()))
{
var clients = new List<string>();
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<string, object>)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<string, object> extensionData) =>
extensionData?.ContainsKey("x-kubernetes-action") ?? false;
}
}

View File

@@ -21,7 +21,8 @@ namespace LibKubernetesGenerator
public void RegisterHelper(ScriptObject scriptObject) public void RegisterHelper(ScriptObject scriptObject)
{ {
scriptObject.Import(nameof(GetInterfaceName), new Func<JsonSchema, string>(GetInterfaceName)); scriptObject.Import(nameof(GetInterfaceName), new Func<JsonSchema, string>(GetInterfaceName));
scriptObject.Import(nameof(GetMethodName), new Func<OpenApiOperation, string, string>(GetMethodName)); scriptObject.Import(nameof(GetOperationId), new Func<OpenApiOperation, string, string>(GetOperationId));
scriptObject.Import(nameof(GetActionName), new Func<OpenApiOperation, string, string, string>(GetActionName));
scriptObject.Import(nameof(GetDotNetName), new Func<string, string, string>(GetDotNetName)); scriptObject.Import(nameof(GetDotNetName), new Func<string, string, string>(GetDotNetName));
scriptObject.Import(nameof(GetDotNetNameOpenApiParameter), new Func<OpenApiParameter, string, string>(GetDotNetNameOpenApiParameter)); scriptObject.Import(nameof(GetDotNetNameOpenApiParameter), new Func<OpenApiParameter, string, string>(GetDotNetNameOpenApiParameter));
} }
@@ -138,7 +139,7 @@ namespace LibKubernetesGenerator
return jsonName.ToCamelCase(); return jsonName.ToCamelCase();
} }
public static string GetMethodName(OpenApiOperation watchOperation, string suffix) public static string GetOperationId(OpenApiOperation watchOperation, string suffix)
{ {
var tag = watchOperation.Tags[0]; var tag = watchOperation.Tags[0];
tag = tag.Replace("_", string.Empty); tag = tag.Replace("_", string.Empty);
@@ -162,5 +163,28 @@ namespace LibKubernetesGenerator
return methodName; return methodName;
} }
public static string GetActionName(OpenApiOperation apiOperation, string resource, string suffix)
{
var operationId = apiOperation.OperationId.ToPascalCase();
var replacements = new Dictionary<string, string>(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}";
}
} }
} }

View File

@@ -61,6 +61,7 @@ namespace LibKubernetesGenerator
builder.RegisterType<ModelExtGenerator>(); builder.RegisterType<ModelExtGenerator>();
builder.RegisterType<ModelGenerator>(); builder.RegisterType<ModelGenerator>();
builder.RegisterType<ApiGenerator>(); builder.RegisterType<ApiGenerator>();
builder.RegisterType<ClientSetGenerator>();
builder.RegisterType<VersionConverterStubGenerator>(); builder.RegisterType<VersionConverterStubGenerator>();
builder.RegisterType<VersionGenerator>(); builder.RegisterType<VersionGenerator>();
@@ -80,6 +81,7 @@ namespace LibKubernetesGenerator
container.Resolve<ModelExtGenerator>().Generate(swagger, ctx); container.Resolve<ModelExtGenerator>().Generate(swagger, ctx);
container.Resolve<VersionConverterStubGenerator>().Generate(swagger, ctx); container.Resolve<VersionConverterStubGenerator>().Generate(swagger, ctx);
container.Resolve<ApiGenerator>().Generate(swagger, ctx); container.Resolve<ApiGenerator>().Generate(swagger, ctx);
container.Resolve<ClientSetGenerator>().Generate(swagger, ctx);
}); });
#endif #endif

View File

@@ -16,6 +16,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<!-- Scriban No Dependency --> <!-- Scriban No Dependency -->
<PackageReference Include="Scriban" IncludeAssets="Build" /> <PackageReference Include="Scriban" IncludeAssets="Build" />

View File

@@ -0,0 +1,100 @@
// <auto-generated>
// 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.
// </auto-generated>
using System.Net.Http;
using System.Net.Http.Headers;
namespace k8s.ClientSets;
/// <summary>
/// </summary>
public partial class {{name}}Client : ResourceClient
{
public {{name}}Client(Kubernetes kubernetes) : base(kubernetes)
{
}
{{for api in apis }}
/// <summary>
/// {{ToXmlDoc api.operation.description}}
/// </summary>
{{ for parameter in api.operation.parameters}}
/// <param name="{{GetDotNetNameOpenApiParameter parameter "false"}}">
/// {{ToXmlDoc parameter.description}}
/// </param>
{{end}}
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation.
/// </param>
public async Task{{GetReturnType api.operation "<>"}} {{GetActionName api.operation name "Async"}}(
{{ for parameter in api.operation.parameters}}
{{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"}}
/// <summary>
/// {{ToXmlDoc api.operation.description}}
/// </summary>
{{ for parameter in api.operation.parameters}}
/// <param name="{{GetDotNetNameOpenApiParameter parameter "false"}}">
/// {{ToXmlDoc parameter.description}}
/// </param>
{{end}}
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation.
/// </param>
public async Task<T> {{GetActionName api.operation name "Async"}}<T>(
{{ for parameter in api.operation.parameters}}
{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}},
{{ end }}
CancellationToken cancellationToken = default(CancellationToken))
{
using (var _result = await Client.{{group}}.{{GetOperationId api.operation "WithHttpMessagesAsync"}}<T>(
{{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}}
null,
cancellationToken).ConfigureAwait(false))
{
return _result.Body;
}
}
{{end}}
{{end}}
}

View File

@@ -0,0 +1,16 @@
// <auto-generated>
// 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.
// </auto-generated>
namespace k8s.ClientSets;
/// <summary>
/// </summary>
public partial class ClientSet
{
{{for group in groups}}
public {{group}}GroupClient {{group}} => new {{group}}GroupClient(_kubernetes);
{{end}}
}

View File

@@ -0,0 +1,24 @@
// <auto-generated>
// 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.
// </auto-generated>
namespace k8s.ClientSets;
/// <summary>
/// </summary>
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;
}
}

View File

@@ -25,7 +25,7 @@ public partial interface I{{name}}Operations
/// <param name="cancellationToken"> /// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation. /// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation.
/// </param> /// </param>
Task<HttpOperationResponse{{GetReturnType api.operation "<>"}}> {{GetMethodName api.operation "WithHttpMessagesAsync"}}( Task<HttpOperationResponse{{GetReturnType api.operation "<>"}}> {{GetOperationId api.operation "WithHttpMessagesAsync"}}(
{{ for parameter in api.operation.parameters}} {{ for parameter in api.operation.parameters}}
{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}},
{{ end }} {{ end }}
@@ -47,7 +47,7 @@ public partial interface I{{name}}Operations
/// <param name="cancellationToken"> /// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation. /// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation.
/// </param> /// </param>
Task<HttpOperationResponse<T>> {{GetMethodName api.operation "WithHttpMessagesAsync"}}<T>( Task<HttpOperationResponse<T>> {{GetOperationId api.operation "WithHttpMessagesAsync"}}<T>(
{{ for parameter in api.operation.parameters}} {{ for parameter in api.operation.parameters}}
{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}},
{{ end }} {{ end }}

View File

@@ -10,13 +10,13 @@ public partial class AbstractKubernetes : I{{name}}Operations
{ {
{{for api in apis }} {{for api in apis }}
{{if IfReturnType api.operation "void"}} {{if IfReturnType api.operation "void"}}
private async Task<HttpOperationResponse> I{{name}}Operations_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( private async Task<HttpOperationResponse> I{{name}}Operations_{{GetOperationId api.operation "WithHttpMessagesAsync"}}(
{{end}} {{end}}
{{if IfReturnType api.operation "obj"}} {{if IfReturnType api.operation "obj"}}
private async Task<HttpOperationResponse<T>> I{{name}}Operations_{{GetMethodName api.operation "WithHttpMessagesAsync"}}<T>( private async Task<HttpOperationResponse<T>> I{{name}}Operations_{{GetOperationId api.operation "WithHttpMessagesAsync"}}<T>(
{{end}} {{end}}
{{if IfReturnType api.operation "stream"}} {{if IfReturnType api.operation "stream"}}
private async Task<HttpOperationResponse<Stream>> I{{name}}Operations_{{GetMethodName api.operation "WithHttpMessagesAsync"}}( private async Task<HttpOperationResponse<Stream>> I{{name}}Operations_{{GetOperationId api.operation "WithHttpMessagesAsync"}}(
{{end}} {{end}}
{{ for parameter in api.operation.parameters}} {{ for parameter in api.operation.parameters}}
{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}},
@@ -90,7 +90,7 @@ public partial class AbstractKubernetes : I{{name}}Operations
} }
/// <inheritdoc/> /// <inheritdoc/>
async Task<HttpOperationResponse{{GetReturnType api.operation "<>"}}> I{{name}}Operations.{{GetMethodName api.operation "WithHttpMessagesAsync"}}( async Task<HttpOperationResponse{{GetReturnType api.operation "<>"}}> I{{name}}Operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}(
{{ for parameter in api.operation.parameters}} {{ for parameter in api.operation.parameters}}
{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}} {{end}}
@@ -98,7 +98,7 @@ public partial class AbstractKubernetes : I{{name}}Operations
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
{{if IfReturnType api.operation "void"}} {{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}} {{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}} {{end}}
@@ -106,7 +106,7 @@ public partial class AbstractKubernetes : I{{name}}Operations
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
{{end}} {{end}}
{{if IfReturnType api.operation "obj"}} {{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}} {{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}} {{end}}
@@ -114,7 +114,7 @@ public partial class AbstractKubernetes : I{{name}}Operations
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
{{end}} {{end}}
{{if IfReturnType api.operation "stream"}} {{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}} {{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}} {{end}}
@@ -125,14 +125,14 @@ public partial class AbstractKubernetes : I{{name}}Operations
{{if IfReturnType api.operation "object"}} {{if IfReturnType api.operation "object"}}
/// <inheritdoc/> /// <inheritdoc/>
async Task<HttpOperationResponse<T>> I{{name}}Operations.{{GetMethodName api.operation "WithHttpMessagesAsync"}}<T>( async Task<HttpOperationResponse<T>> I{{name}}Operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}<T>(
{{ for parameter in api.operation.parameters}} {{ for parameter in api.operation.parameters}}
{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}} {{end}}
IReadOnlyDictionary<string, IReadOnlyList<string>> customHeaders, IReadOnlyDictionary<string, IReadOnlyList<string>> customHeaders,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
return await I{{name}}Operations_{{GetMethodName api.operation "WithHttpMessagesAsync"}}<T>( return await I{{name}}Operations_{{GetOperationId api.operation "WithHttpMessagesAsync"}}<T>(
{{ for parameter in api.operation.parameters}} {{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}} {{end}}

View File

@@ -23,14 +23,14 @@ public static partial class {{name}}OperationsExtensions
/// {{ToXmlDoc api.description}} /// {{ToXmlDoc api.description}}
/// </param> /// </param>
{{ end }} {{ end }}
public static {{GetReturnType api.operation "void"}} {{GetMethodName api.operation ""}}( public static {{GetReturnType api.operation "void"}} {{GetOperationId api.operation ""}}(
this I{{name}}Operations operations this I{{name}}Operations operations
{{ for parameter in api.operation.parameters}} {{ for parameter in api.operation.parameters}}
,{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}} ,{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}
{{end}} {{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}} {{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}} {{end}}
@@ -50,14 +50,14 @@ public static partial class {{name}}OperationsExtensions
/// {{ToXmlDoc parameter.description}} /// {{ToXmlDoc parameter.description}}
/// </param> /// </param>
{{end}} {{end}}
public static T {{GetMethodName api.operation ""}}<T>( public static T {{GetOperationId api.operation ""}}<T>(
this I{{name}}Operations operations this I{{name}}Operations operations
{{ for parameter in api.operation.parameters}} {{ for parameter in api.operation.parameters}}
,{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}} ,{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}
{{end}} {{end}}
) )
{ {
return operations.{{GetMethodName api.operation "Async"}}<T>( return operations.{{GetOperationId api.operation "Async"}}<T>(
{{ for parameter in api.operation.parameters}} {{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}} {{end}}
@@ -80,7 +80,7 @@ public static partial class {{name}}OperationsExtensions
/// <param name="cancellationToken"> /// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation. /// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation.
/// </param> /// </param>
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, this I{{name}}Operations operations,
{{ for parameter in api.operation.parameters}} {{ for parameter in api.operation.parameters}}
{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}},
@@ -88,7 +88,7 @@ public static partial class {{name}}OperationsExtensions
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
{{if IfReturnType api.operation "stream"}} {{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}} {{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}} {{end}}
@@ -98,7 +98,7 @@ public static partial class {{name}}OperationsExtensions
{{GetReturnType api.operation "_result.Body"}}; {{GetReturnType api.operation "_result.Body"}};
{{end}} {{end}}
{{if IfReturnType api.operation "obj"}} {{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}} {{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}} {{end}}
@@ -109,7 +109,7 @@ public static partial class {{name}}OperationsExtensions
} }
{{end}} {{end}}
{{if IfReturnType api.operation "void"}} {{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}} {{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}} {{end}}
@@ -135,14 +135,14 @@ public static partial class {{name}}OperationsExtensions
/// <param name="cancellationToken"> /// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation. /// A <see cref="CancellationToken"/> which can be used to cancel the asynchronous operation.
/// </param> /// </param>
public static async Task<T> {{GetMethodName api.operation "Async"}}<T>( public static async Task<T> {{GetOperationId api.operation "Async"}}<T>(
this I{{name}}Operations operations, this I{{name}}Operations operations,
{{ for parameter in api.operation.parameters}} {{ for parameter in api.operation.parameters}}
{{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}}, {{GetDotNetTypeOpenApiParameter parameter}} {{GetDotNetNameOpenApiParameter parameter "true"}},
{{ end }} {{ end }}
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
using (var _result = await operations.{{GetMethodName api.operation "WithHttpMessagesAsync"}}<T>( using (var _result = await operations.{{GetOperationId api.operation "WithHttpMessagesAsync"}}<T>(
{{ for parameter in api.operation.parameters}} {{ for parameter in api.operation.parameters}}
{{GetDotNetNameOpenApiParameter parameter "false"}}, {{GetDotNetNameOpenApiParameter parameter "false"}},
{{end}} {{end}}

View File

@@ -16,6 +16,7 @@ using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using k8s.ClientSets;
using Xunit; using Xunit;
namespace k8s.E2E namespace k8s.E2E
@@ -584,6 +585,118 @@ namespace k8s.E2E
} }
} }
[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<string, string> { { "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<string, string>(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<string, string>("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] [MinikubeFact]
public async Task CopyToPodTestAsync() public async Task CopyToPodTestAsync()

View File

@@ -66,7 +66,7 @@ namespace k8s.Tests
false, false,
false, false,
true, true,
webSocketSubProtol: WebSocketProtocol.ChannelWebSocketProtocol, webSocketSubProtocol: WebSocketProtocol.ChannelWebSocketProtocol,
cancellationToken: TestCancellation).ConfigureAwait(true); cancellationToken: TestCancellation).ConfigureAwait(true);
Assert.Equal( Assert.Equal(
WebSocketProtocol.ChannelWebSocketProtocol, WebSocketProtocol.ChannelWebSocketProtocol,