Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="Humanizer.Core" Version="2.14.1" />
<PackageVersion Include="Autofac" Version="8.2.1" />
<PackageVersion Include="CaseExtensions" Version="1.1.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.13.0" />
Expand All @@ -50,4 +51,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();
IKubernetes client = new Kubernetes(config);

ClientSet clientSet = new ClientSet(client);
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);
}
}
}
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
5 changes: 5 additions & 0 deletions src/KubernetesClient/ClientSets/ClientSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace k8s.ClientSets;

public partial class ClientSet
{
}
10 changes: 10 additions & 0 deletions src/KubernetesClient/ClientSets/ResourceClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace k8s.ClientSets;

public abstract class ResourceClient
{
protected Kubernetes Client { get; }
public ResourceClient(IKubernetes kubernetes)
{
Client = (Kubernetes)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
104 changes: 104 additions & 0 deletions src/LibKubernetesGenerator/ClientSetGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using CaseExtensions;
using Microsoft.CodeAnalysis;
using NSwag;
using System.Collections.Generic;
using System.Linq;
using Humanizer;

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.Pluralize();
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;
}
}
56 changes: 56 additions & 0 deletions src/LibKubernetesGenerator/GeneralNameHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ 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(GetActionName), new Func<OpenApiOperationDescription, 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 @@ -162,5 +163,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<string, string>(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;
}
}
}
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
5 changes: 5 additions & 0 deletions src/LibKubernetesGenerator/LibKubernetesGenerator.target
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@
</ItemGroup>

<ItemGroup>

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

<!-- CaseExtensions No Dependency -->
<PackageReference Include="CaseExtensions" GeneratePathProperty="true" PrivateAssets="all" />

<!-- Humanizer -->
<PackageReference Include="Humanizer.Core" GeneratePathProperty="true" PrivateAssets="all"/>

<!-- Autofac -->
<PackageReference Include="Autofac" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" GeneratePathProperty="true" PrivateAssets="all" />
Expand All @@ -43,6 +47,7 @@
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGCaseExtensions)\lib\netstandard2.0\CaseExtensions.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGHumanizer_Core)\lib\netstandard2.0\Humanizer.dll" IncludeRuntimeDependency="false" />

<TargetPathWithTargetPlatformMoniker Include="$(PKGAutofac)\lib\netstandard2.0\Autofac.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGMicrosoft_Bcl_AsyncInterfaces)\lib\netstandard2.0\Microsoft.Bcl.AsyncInterfaces.dll" IncludeRuntimeDependency="false" />
Expand Down
Loading
Loading