Skip to content

Commit 5c28a14

Browse files
author
Christoph Bühler
committed
feat: add runtime generated entity clients
1 parent 2e7e671 commit 5c28a14

17 files changed

+86
-48
lines changed

src/KubeOps.Abstractions/Builder/IOperatorBuilder.cs

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,41 +21,40 @@ public interface IOperatorBuilder
2121

2222
/// <summary>
2323
/// <para>
24-
/// Register an entity within the operator.
25-
/// Entities must be registered to be used in controllers and other
26-
/// elements like Kubernetes clients.
27-
/// </para>
28-
/// <para>
29-
/// This method will also register a transient IKubernetesClient{TEntity} for
30-
/// the entity.
24+
/// Register an entity Kubernetes client within the operator.
25+
/// This is used to register IKubernetesClient{TEntity} for the entity.
26+
/// An alternative way to create any Kubernetes client is to use the
27+
/// KubernetesClientFactory or instantiate the client by yourself.
3128
/// </para>
3229
/// </summary>
3330
/// <param name="metadata">The metadata of the entity.</param>
3431
/// <typeparam name="TEntity">The type of the entity.</typeparam>
3532
/// <returns>The builder for chaining.</returns>
36-
IOperatorBuilder AddEntity<TEntity>(EntityMetadata metadata)
33+
IOperatorBuilder AddEntityClient<TEntity>(EntityMetadata metadata)
3734
where TEntity : IKubernetesObject<V1ObjectMeta>;
3835

3936
/// <summary>
40-
/// Add a controller implementation for a specific entity to the operator.
41-
/// The metadata for the entity must be added as well.
37+
/// <para>
38+
/// Register an entity Kubernetes client within the operator.
39+
/// This is used to register IKubernetesClient{TEntity} for the entity.
40+
/// An alternative way to create any Kubernetes client is to use the
41+
/// KubernetesClientFactory or instantiate the client by yourself.
42+
/// This method uses reflection to get the metadata from the entity.
43+
/// </para>
4244
/// </summary>
43-
/// <typeparam name="TImplementation">Implementation type of the controller.</typeparam>
44-
/// <typeparam name="TEntity">Entity type.</typeparam>
45+
/// <typeparam name="TEntity">The type of the entity.</typeparam>
4546
/// <returns>The builder for chaining.</returns>
46-
IOperatorBuilder AddController<TImplementation, TEntity>()
47-
where TImplementation : class, IEntityController<TEntity>
47+
IOperatorBuilder AddEntityClient<TEntity>()
4848
where TEntity : IKubernetesObject<V1ObjectMeta>;
4949

5050
/// <summary>
51-
/// Add a controller implementation for a specific entity with the
52-
/// entity metadata.
51+
/// Add a controller implementation for a specific entity to the operator.
52+
/// The metadata for the entity must be added as well.
5353
/// </summary>
54-
/// <param name="metadata">The metadata of the entity.</param>
5554
/// <typeparam name="TImplementation">Implementation type of the controller.</typeparam>
5655
/// <typeparam name="TEntity">Entity type.</typeparam>
5756
/// <returns>The builder for chaining.</returns>
58-
IOperatorBuilder AddControllerWithEntity<TImplementation, TEntity>(EntityMetadata metadata)
57+
IOperatorBuilder AddController<TImplementation, TEntity>()
5958
where TImplementation : class, IEntityController<TEntity>
6059
where TEntity : IKubernetesObject<V1ObjectMeta>;
6160

src/KubeOps.Generator/Generators/EntityDefinitionGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public void Execute(GeneratorExecutionContext context)
9898
MemberAccessExpression(
9999
SyntaxKind.SimpleMemberAccessExpression,
100100
IdentifierName("builder"),
101-
GenericName(Identifier("AddEntity"))
101+
GenericName(Identifier("AddEntityClient"))
102102
.WithTypeArgumentList(
103103
TypeArgumentList(
104104
SingletonSeparatedList<TypeSyntax>(

src/KubeOps.Operator/Builder/OperatorBuilder.cs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using KubeOps.Abstractions.Finalizer;
1414
using KubeOps.Abstractions.Queue;
1515
using KubeOps.KubernetesClient;
16+
using KubeOps.Operator.Client;
1617
using KubeOps.Operator.Finalizer;
1718
using KubeOps.Operator.Queue;
1819
using KubeOps.Operator.Watcher;
@@ -35,10 +36,17 @@ public OperatorBuilder(IServiceCollection services, OperatorSettings settings)
3536

3637
public IServiceCollection Services { get; }
3738

38-
public IOperatorBuilder AddEntity<TEntity>(EntityMetadata metadata)
39+
public IOperatorBuilder AddEntityClient<TEntity>(EntityMetadata metadata)
3940
where TEntity : IKubernetesObject<V1ObjectMeta>
4041
{
41-
Services.AddTransient<IKubernetesClient<TEntity>>(_ => new KubernetesClient<TEntity>(metadata));
42+
Services.AddTransient<IKubernetesClient<TEntity>>(_ => KubernetesClientFactory.Create<TEntity>(metadata));
43+
return this;
44+
}
45+
46+
public IOperatorBuilder AddEntityClient<TEntity>()
47+
where TEntity : IKubernetesObject<V1ObjectMeta>
48+
{
49+
Services.AddTransient<IKubernetesClient<TEntity>>(_ => KubernetesClientFactory.Create<TEntity>());
4250
return this;
4351
}
4452

@@ -62,11 +70,6 @@ public IOperatorBuilder AddController<TImplementation, TEntity>()
6270
return this;
6371
}
6472

65-
public IOperatorBuilder AddControllerWithEntity<TImplementation, TEntity>(EntityMetadata metadata)
66-
where TImplementation : class, IEntityController<TEntity>
67-
where TEntity : IKubernetesObject<V1ObjectMeta> =>
68-
AddController<TImplementation, TEntity>().AddEntity<TEntity>(metadata);
69-
7073
public IOperatorBuilder AddFinalizer<TImplementation, TEntity>(string identifier)
7174
where TImplementation : class, IEntityFinalizer<TEntity>
7275
where TEntity : IKubernetesObject<V1ObjectMeta>
@@ -86,7 +89,8 @@ private static Func<IServiceProvider, EntityFinalizerAttacher<TImplementation, T
8689
=> services => async entity =>
8790
{
8891
var logger = services.GetService<ILogger<EntityFinalizerAttacher<TImplementation, TEntity>>>();
89-
var client = services.GetRequiredService<IKubernetesClient<TEntity>>();
92+
using var client = services.GetService<IKubernetesClient<TEntity>>() ??
93+
KubernetesClientFactory.Create<TEntity>();
9094

9195
logger?.LogTrace(
9296
"""Try to add finalizer "{finalizer}" on entity "{kind}/{name}".""",
@@ -206,14 +210,13 @@ private static Func<IServiceProvider, EventPublisher> CreateEventPublisher()
206210
private void AddOperatorBase()
207211
{
208212
Services.AddSingleton(_settings);
209-
Services.AddTransient<IKubernetesClient<Corev1Event>>(_ => new KubernetesClient<Corev1Event>(new(
210-
Corev1Event.KubeKind, Corev1Event.KubeApiVersion, Plural: Corev1Event.KubePluralName)));
213+
214+
Services.AddTransient(_ => KubernetesClientFactory.Create<Corev1Event>());
211215
Services.AddTransient(CreateEventPublisher());
212216

213217
if (_settings.EnableLeaderElection)
214218
{
215-
using var client = new KubernetesClient<Corev1Event>(new(
216-
Corev1Event.KubeKind, Corev1Event.KubeApiVersion, Plural: Corev1Event.KubePluralName));
219+
using var client = KubernetesClientFactory.Create<Corev1Event>();
217220

218221
var elector = new LeaderElector(
219222
new LeaderElectionConfig(
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using k8s;
2+
using k8s.Models;
3+
4+
using KubeOps.Abstractions.Entities;
5+
using KubeOps.KubernetesClient;
6+
using KubeOps.Transpiler;
7+
8+
namespace KubeOps.Operator.Client;
9+
10+
/// <summary>
11+
/// Factory to create a <see cref="IKubernetesClient{TEntity}"/> for a given <see cref="EntityMetadata"/>
12+
/// or type.
13+
/// </summary>
14+
public static class KubernetesClientFactory
15+
{
16+
/// <summary>
17+
/// Create a <see cref="IKubernetesClient{TEntity}"/> for a given <see cref="EntityMetadata"/>.
18+
/// This method does not use reflection at runtime since the metadata is already provided.
19+
/// </summary>
20+
/// <param name="metadata">Metadata for the entity.</param>
21+
/// <typeparam name="TEntity">Type of the entity.</typeparam>
22+
/// <returns>The created <see cref="IKubernetesClient{TEntity}"/>.</returns>
23+
public static IKubernetesClient<TEntity> Create<TEntity>(EntityMetadata metadata)
24+
where TEntity : IKubernetesObject<V1ObjectMeta> =>
25+
new KubernetesClient<TEntity>(metadata);
26+
27+
/// <summary>
28+
/// Create a <see cref="IKubernetesClient{TEntity}"/> for a given type.
29+
/// This method uses reflection at runtime to fetch the metadata for the entity.
30+
/// </summary>
31+
/// <typeparam name="TEntity">Type of the entity.</typeparam>
32+
/// <returns>The created <see cref="IKubernetesClient{TEntity}"/>.</returns>
33+
public static IKubernetesClient<TEntity> Create<TEntity>()
34+
where TEntity : IKubernetesObject<V1ObjectMeta> =>
35+
Create<TEntity>(Entities.ToEntityMetadata(typeof(TEntity)).Metadata);
36+
}

src/KubeOps.Operator/KubeOps.Operator.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<ItemGroup>
2323
<ProjectReference Include="..\KubeOps.Abstractions\KubeOps.Abstractions.csproj"/>
2424
<ProjectReference Include="..\KubeOps.KubernetesClient\KubeOps.KubernetesClient.csproj" />
25+
<ProjectReference Include="..\KubeOps.Transpiler\KubeOps.Transpiler.csproj" />
2526
</ItemGroup>
2627

2728
<ItemGroup>

src/KubeOps.Operator/Watcher/LeaderAwareResourceWatcher{TEntity}.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,10 @@ internal class LeaderAwareResourceWatcher<TEntity> : ResourceWatcher<TEntity>
1919
public LeaderAwareResourceWatcher(
2020
ILogger<LeaderAwareResourceWatcher<TEntity>> logger,
2121
IServiceProvider provider,
22-
IKubernetesClient<TEntity> client,
2322
TimedEntityQueue<TEntity> queue,
2423
OperatorSettings settings,
2524
LeaderElector elector)
26-
: base(logger, provider, client, queue, settings)
25+
: base(logger, provider, queue, settings)
2726
{
2827
_logger = logger;
2928
_elector = elector;

src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using KubeOps.Abstractions.Controller;
1010
using KubeOps.Abstractions.Finalizer;
1111
using KubeOps.KubernetesClient;
12+
using KubeOps.Operator.Client;
1213
using KubeOps.Operator.Finalizer;
1314
using KubeOps.Operator.Queue;
1415

@@ -36,13 +37,12 @@ internal class ResourceWatcher<TEntity> : IHostedService
3637
public ResourceWatcher(
3738
ILogger<ResourceWatcher<TEntity>> logger,
3839
IServiceProvider provider,
39-
IKubernetesClient<TEntity> client,
4040
TimedEntityQueue<TEntity> queue,
4141
OperatorSettings settings)
4242
{
4343
_logger = logger;
4444
_provider = provider;
45-
_client = client;
45+
_client = provider.GetService<IKubernetesClient<TEntity>>() ?? KubernetesClientFactory.Create<TEntity>();
4646
_queue = queue;
4747
_settings = settings;
4848
_finalizers = new(() => _provider.GetServices<FinalizerRegistration>().ToList());
@@ -237,7 +237,7 @@ private async Task ReconcileFinalizer(TEntity entity)
237237
var pendingFinalizer = entity.Finalizers();
238238
if (_finalizers.Value.Find(reg =>
239239
reg.EntityType == entity.GetType() && pendingFinalizer.Contains(reg.Identifier)) is not
240-
{ Identifier: var identifier, FinalizerType: var type })
240+
{ Identifier: var identifier, FinalizerType: var type })
241241
{
242242
_logger.LogDebug(
243243
"""Entity "{kind}/{name}" is finalizing but this operator has no registered finalizers for it.""",

test/KubeOps.Generator.Test/EntityDefinitionGenerator.Test.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public static class EntityDefinitions
3535
public static readonly EntityMetadata V1TestEntity = new("TestEntity", "v1", "testing.dev", null);
3636
public static IOperatorBuilder RegisterEntities(this IOperatorBuilder builder)
3737
{
38-
builder.AddEntity<global::V1TestEntity>(V1TestEntity);
38+
builder.AddEntityClient<global::V1TestEntity>(V1TestEntity);
3939
return builder;
4040
}
4141
}
@@ -60,8 +60,8 @@ public static class EntityDefinitions
6060
public static readonly EntityMetadata V1AnotherEntity = new("AnotherEntity", "v1", "testing.dev", null);
6161
public static IOperatorBuilder RegisterEntities(this IOperatorBuilder builder)
6262
{
63-
builder.AddEntity<global::V1TestEntity>(V1TestEntity);
64-
builder.AddEntity<global::V1AnotherEntity>(V1AnotherEntity);
63+
builder.AddEntityClient<global::V1TestEntity>(V1TestEntity);
64+
builder.AddEntityClient<global::V1AnotherEntity>(V1AnotherEntity);
6565
return builder;
6666
}
6767
}

test/KubeOps.Operator.Test/Builder/OperatorBuilder.Test.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public void Should_Add_Default_Resources()
4242
[Fact]
4343
public void Should_Add_Entity_Resources()
4444
{
45-
_builder.AddEntity<V1IntegrationTestEntity>(new EntityMetadata("test", "v1", "testentities"));
45+
_builder.AddEntityClient<V1IntegrationTestEntity>(new EntityMetadata("test", "v1", "testentities"));
4646

4747
_builder.Services.Should().Contain(s =>
4848
s.ServiceType == typeof(IKubernetesClient<V1IntegrationTestEntity>) &&

test/KubeOps.Operator.Test/Controller/CancelEntityRequeue.Integration.Test.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public async Task InitializeAsync()
4545
await _hostBuilder.ConfigureAndStart(builder => builder.Services
4646
.AddSingleton(Mock)
4747
.AddKubernetesOperator()
48-
.AddControllerWithEntity<TestController, V1IntegrationTestEntity>(meta));
48+
.AddController<TestController, V1IntegrationTestEntity>());
4949
}
5050

5151
public async Task DisposeAsync()

0 commit comments

Comments
 (0)