Skip to content

Implementing a more modular API #1627

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.7.112" />
<GlobalPackageReference Include="StyleCop.Analyzers" Version="1.1.118" />
</ItemGroup>
</Project>
</Project>
26 changes: 26 additions & 0 deletions examples/clientset/Program.cs
Original file line number Diff line number Diff line change
@@ -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);

Check warning on line 22 in examples/clientset/Program.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

System.Console.WriteLine(pod?.Metadata?.Name);
}
}
}
6 changes: 6 additions & 0 deletions examples/clientset/clientset.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>

</Project>
6 changes: 5 additions & 1 deletion src/KubernetesClient.Aot/KubernetesClient.Aot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
<Compile Include="..\KubernetesClient\Models\V1Status.cs" />

</ItemGroup>
<ItemGroup>
<Compile Include="..\KubernetesClient\ClientSets\ClientSet.cs" />
<Compile Include="..\KubernetesClient\ClientSets\ResourceClient.cs"/>
</ItemGroup>
<ItemGroup>
<Compile Include="..\KubernetesClient\AbstractKubernetes.cs" />
<Compile Include="..\KubernetesClient\GeneratedApiVersion.cs" />
Expand Down Expand Up @@ -107,4 +111,4 @@
<ItemGroup>
<ProjectReference Include="..\LibKubernetesGenerator\generators\LibKubernetesGenerator\LibKubernetesGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
</Project>
</Project>
5 changes: 4 additions & 1 deletion src/KubernetesClient.Classic/KubernetesClient.Classic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@
<Compile Include="..\KubernetesClient\Autorest\HttpRequestMessageWrapper.cs" />
<Compile Include="..\KubernetesClient\Autorest\HttpResponseMessageWrapper.cs" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\KubernetesClient\ClientSets\ClientSet.cs" />
<Compile Include="..\KubernetesClient\ClientSets\ResourceClient.cs"/>
</ItemGroup>
<ItemGroup>
<Compile Include="..\KubernetesClient\FileSystem.cs" />
<Compile Include="..\KubernetesClient\IKubernetes.cs" />
Expand Down
16 changes: 16 additions & 0 deletions src/KubernetesClient/ClientSets/ClientSet.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
16 changes: 16 additions & 0 deletions src/KubernetesClient/ClientSets/ResourceClient.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
14 changes: 7 additions & 7 deletions src/KubernetesClient/Kubernetes.WebSocket.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ public partial class Kubernetes
/// <inheritdoc/>
public Task<WebSocket> 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<string, List<string>> customHeaders = null,
bool tty = true, string webSocketSubProtocol = null, Dictionary<string, List<string>> 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);
}

/// <inheritdoc/>
public virtual async Task<IStreamDemuxer> MuxedStreamNamespacedPodExecAsync(
string name,
string @namespace = "default", IEnumerable<string> 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<string, List<string>> customHeaders = null,
CancellationToken cancellationToken = default)
{
Expand All @@ -45,7 +45,7 @@ public virtual async Task<IStreamDemuxer> MuxedStreamNamespacedPodExecAsync(
public virtual Task<WebSocket> WebSocketNamespacedPodExecAsync(string name, string @namespace = "default",
IEnumerable<string> 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<string, List<string>> customHeaders = null,
CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -114,7 +114,7 @@ public virtual Task<WebSocket> 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);
}

Expand Down Expand Up @@ -173,7 +173,7 @@ public Task<WebSocket> WebSocketNamespacedPodPortForwardAsync(string name, strin
/// <inheritdoc/>
public Task<WebSocket> WebSocketNamespacedPodAttachAsync(string name, string @namespace,
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)
{
if (name == null)
Expand Down Expand Up @@ -208,7 +208,7 @@ public Task<WebSocket> 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);
}

Expand Down
103 changes: 103 additions & 0 deletions src/LibKubernetesGenerator/ClientSetGenerator.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
28 changes: 26 additions & 2 deletions src/LibKubernetesGenerator/GeneralNameHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public GeneralNameHelper(ClassNameHelper classNameHelper)
public void RegisterHelper(ScriptObject scriptObject)
{
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(GetDotNetNameOpenApiParameter), new Func<OpenApiParameter, string, string>(GetDotNetNameOpenApiParameter));
}
Expand Down Expand Up @@ -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);
Expand All @@ -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<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}";
}
}
}
2 changes: 2 additions & 0 deletions src/LibKubernetesGenerator/KubernetesClientSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ private static IContainer BuildContainer(OpenApiDocument swagger)
builder.RegisterType<ModelExtGenerator>();
builder.RegisterType<ModelGenerator>();
builder.RegisterType<ApiGenerator>();
builder.RegisterType<ClientSetGenerator>();
builder.RegisterType<VersionConverterStubGenerator>();
builder.RegisterType<VersionGenerator>();

Expand All @@ -80,6 +81,7 @@ public void Initialize(IncrementalGeneratorInitializationContext generatorContex
container.Resolve<ModelExtGenerator>().Generate(swagger, ctx);
container.Resolve<VersionConverterStubGenerator>().Generate(swagger, ctx);
container.Resolve<ApiGenerator>().Generate(swagger, ctx);
container.Resolve<ClientSetGenerator>().Generate(swagger, ctx);
});
#endif

Expand Down
1 change: 1 addition & 0 deletions src/LibKubernetesGenerator/LibKubernetesGenerator.target
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
</ItemGroup>

<ItemGroup>

<!-- Scriban No Dependency -->
<PackageReference Include="Scriban" IncludeAssets="Build" />

Expand Down
Loading
Loading