Skip to content

Commit 0f3ac81

Browse files
authored
Scoped and Namespaced Clients (#25)
1 parent d5c313d commit 0f3ac81

File tree

11 files changed

+241
-56
lines changed

11 files changed

+241
-56
lines changed
Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Microsoft.Extensions.DependencyInjection;
1+
using K8sOperator.NET.Metadata;
2+
using Microsoft.Extensions.DependencyInjection;
23

34
namespace K8sOperator.NET.Builder;
45

@@ -7,6 +8,15 @@ internal class EventWatcherBuilder(IServiceProvider serviceProvider, IController
78
public IEventWatcher Build()
89
{
910
var watcherType = typeof(EventWatcher<>).MakeGenericType(controller.ResourceType);
10-
return (IEventWatcher)ActivatorUtilities.CreateInstance(serviceProvider, watcherType, controller, metadata);
11+
IKubernetesClient client = ActivatorUtilities.CreateInstance<ClusterKubernetesClient>(serviceProvider);
12+
13+
var clientScope = metadata.OfType<IEntityScopeMetadata>().FirstOrDefault();
14+
if (clientScope?.Scope == EntityScope.Namespaced)
15+
{
16+
client = ActivatorUtilities.CreateInstance<NamespacedKubernetesClient>(serviceProvider);
17+
}
18+
19+
20+
return (IEventWatcher)ActivatorUtilities.CreateInstance(serviceProvider, watcherType, client, controller, metadata);
1121
}
1222
}

src/K8sOperator.NET/Builder/OperatorHostBuilder.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.Extensions.Configuration;
44
using Microsoft.Extensions.DependencyInjection;
55
using Microsoft.Extensions.Logging;
6+
using System.Collections.Generic;
67
using System.Data.Common;
78
using System.Reflection;
89

@@ -91,7 +92,10 @@ private void ConfigureMetadata()
9192
var dockerImage = Assembly.GetEntryAssembly()?.GetCustomAttribute<DockerImageAttribute>()
9293
?? DockerImageAttribute.Default;
9394

94-
_metadata.AddRange([operatorName, dockerImage]);
95+
var entityScope = Assembly.GetEntryAssembly()?.GetCustomAttribute<EntityScopeMetadata>()
96+
?? new EntityScopeMetadata(EntityScopeMetadata.Default);
97+
98+
_metadata.AddRange([operatorName, dockerImage, entityScope]);
9599
}
96100
public IConfigurationManager Configuration => _configurationManager;
97101
public IServiceCollection Services => _serviceCollection;
@@ -131,7 +135,12 @@ private void ConfigureLogging()
131135
_serviceCollection.AddLogging(config =>
132136
{
133137
config.AddConfiguration(Configuration.GetSection("Logging"));
134-
config.AddConsole();
138+
config.AddSimpleConsole(options =>
139+
{
140+
options.IncludeScopes = false;
141+
options.SingleLine = true;
142+
options.TimestampFormat = "HH:mm:ss ";
143+
});
135144
});
136145
}
137146

src/K8sOperator.NET/EventWatcher.cs

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using k8s;
2+
using k8s.Autorest;
23
using k8s.Models;
34
using K8sOperator.NET.Extensions;
45
using K8sOperator.NET.Metadata;
@@ -30,11 +31,10 @@ public interface IEventWatcher
3031
Task Start(CancellationToken cancellationToken);
3132
}
3233

33-
internal class EventWatcher<T>(IKubernetes client, Controller<T> controller, List<object> metadata, ILoggerFactory loggerfactory) : IEventWatcher
34+
internal class EventWatcher<T>(IKubernetesClient client, Controller<T> controller, List<object> metadata, ILoggerFactory loggerfactory) : IEventWatcher
3435
where T: CustomResource
3536
{
3637
private KubernetesEntityAttribute Crd => Metadata.OfType<KubernetesEntityAttribute>().First();
37-
private string Namespace => Metadata.OfType<IWatchNamespaceMetadata>().FirstOrDefault()?.Namespace ?? "default";
3838
private string LabelSelector => Metadata.OfType<ILabelSelectorMetadata>().FirstOrDefault()?.LabelSelector ?? string.Empty;
3939
private string Finalizer => Metadata.OfType<IFinalizerMetadata>().FirstOrDefault()?.Name ?? FinalizerMetadata.Default;
4040

@@ -43,7 +43,7 @@ internal class EventWatcher<T>(IKubernetes client, Controller<T> controller, Lis
4343
private CancellationToken _cancellationToken = CancellationToken.None;
4444
private readonly Controller<T> _controller = controller;
4545

46-
public IKubernetes Client { get; } = client;
46+
public IKubernetesClient Client { get; } = client;
4747
public ILogger Logger { get; } = loggerfactory.CreateLogger("watcher");
4848
public IReadOnlyList<object> Metadata { get; } = metadata;
4949
public IController Controller => _controller;
@@ -53,27 +53,16 @@ public async Task Start(CancellationToken cancellationToken)
5353
_cancellationToken = cancellationToken;
5454
_isRunning = true;
5555

56-
var response = Client.CustomObjects.ListNamespacedCustomObjectWithHttpMessagesAsync(
57-
Crd.Group,
58-
Crd.ApiVersion,
59-
Namespace,
60-
Crd.PluralName,
61-
watch: true,
62-
allowWatchBookmarks: true,
63-
labelSelector: LabelSelector,
64-
timeoutSeconds: (int)TimeSpan.FromMinutes(60).TotalSeconds,
65-
cancellationToken: cancellationToken
66-
);
67-
68-
Logger.BeginWatch(Namespace, Crd.PluralName, LabelSelector);
56+
var response = Client.ListAsync<T>(LabelSelector, cancellationToken);
6957

7058
await foreach (var (type, item) in response.WatchAsync<T, object>(OnError, cancellationToken))
7159
{
7260
OnEvent(type, item);
7361
}
7462

75-
Logger.EndWatch(Namespace, Crd.PluralName, LabelSelector);
63+
Logger.EndWatch(Crd.PluralName, LabelSelector);
7664
}
65+
7766

7867
private void OnEvent(WatchEventType eventType, T customResource)
7968
{
@@ -206,15 +195,7 @@ private async Task<T> ReplaceAsync(T resource, CancellationToken cancellationTok
206195
Logger.ReplaceResource(resource);
207196

208197
// Replace the resource
209-
var result = await Client.CustomObjects.ReplaceNamespacedCustomObjectAsync<T>(
210-
resource,
211-
Crd.Group,
212-
Crd.ApiVersion,
213-
resource.Metadata.NamespaceProperty,
214-
Crd.PluralName,
215-
resource.Metadata.Name,
216-
cancellationToken: cancellationToken
217-
).ConfigureAwait(false);
198+
var result = await Client.ReplaceAsync(resource, cancellationToken);
218199

219200
return result;
220201
}
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using k8s;
1+
using k8s;
22
using Microsoft.Extensions.DependencyInjection;
33

44
namespace K8sOperator.NET.Extensions;
@@ -15,20 +15,20 @@ public static class KubernetesBuilderExtensions
1515
/// <returns></returns>
1616
public static IServiceCollection AddKubernetes(this IServiceCollection services)
1717
{
18-
KubernetesClientConfiguration config;
18+
services.AddSingleton<IKubernetes>(x => {
1919

20-
if (KubernetesClientConfiguration.IsInCluster())
21-
{
22-
config = KubernetesClientConfiguration.InClusterConfig();
23-
config.SkipTlsVerify = true;
24-
}
25-
else
26-
{
27-
config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
28-
}
29-
30-
services.AddSingleton<IKubernetes>(new Kubernetes(config));
20+
KubernetesClientConfiguration config;
3121

22+
if (KubernetesClientConfiguration.IsInCluster())
23+
{
24+
config = KubernetesClientConfiguration.InClusterConfig();
25+
}
26+
else
27+
{
28+
config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
29+
}
30+
return new Kubernetes(config);
31+
});
3232
return services;
3333
}
3434
}

src/K8sOperator.NET/Extensions/LoggingExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ internal static partial class LoggingExtensions
4343
[LoggerMessage(
4444
EventId = 5,
4545
Level = LogLevel.Information,
46-
Message = "Begin watch {ns}/{plural} {labelselector}"
46+
Message = "End watch {plural} {labelselector}"
4747
)]
48-
public static partial void EndWatch(this ILogger logger, string ns, string plural, string labelselector);
48+
public static partial void EndWatch(this ILogger logger, string plural, string labelselector);
4949

5050
[LoggerMessage(
5151
EventId = 6,

src/K8sOperator.NET/Extensions/MetaDataExtensions.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,12 @@ public static TBuilder WithGroup<TBuilder>(this TBuilder builder,
6161
public static TBuilder WatchNamespace<TBuilder>(this TBuilder builder, string watchNamespace)
6262
where TBuilder : IControllerConventionBuilder
6363
{
64-
builder.WithSingle(new WatchNamespaceMetadata(watchNamespace));
64+
builder.Finally(x => {
65+
x.Metadata.RemoveAll(x => x.GetType() == typeof(WatchNamespaceMetadata));
66+
x.Metadata.RemoveAll(x => x.GetType() == typeof(EntityScopeMetadata));
67+
x.Metadata.Add(new WatchNamespaceMetadata(watchNamespace));
68+
x.Metadata.Add(new EntityScopeMetadata(EntityScope.Namespaced));
69+
});
6570
return builder;
6671
}
6772

src/K8sOperator.NET/K8sOperator.NET.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3+
<PropertyGroup>
4+
<UserSecretsId>75af4ad7-a38b-4759-92a0-fa12fc4f56dd</UserSecretsId>
5+
</PropertyGroup>
6+
37
<ItemGroup>
48
<PackageReference Include="KubernetesClient" />
59
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" />
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
using k8s;
2+
using k8s.Autorest;
3+
using k8s.Models;
4+
using K8sOperator.NET.Extensions;
5+
using K8sOperator.NET.Models;
6+
using Microsoft.Extensions.Logging;
7+
using System.Reflection;
8+
9+
namespace K8sOperator.NET;
10+
11+
internal interface IKubernetesClient
12+
{
13+
Task<HttpOperationResponse<object>> ListAsync<T>(string labelSelector, CancellationToken cancellationToken)
14+
where T : CustomResource;
15+
Task<T> ReplaceAsync<T>(T resource, CancellationToken cancellationToken)
16+
where T : CustomResource;
17+
18+
19+
}
20+
21+
internal class NamespacedKubernetesClient(IKubernetes client, ILogger<NamespacedKubernetesClient> logger, string ns = "default") : IKubernetesClient
22+
{
23+
public IKubernetes Client { get; } = client;
24+
public ILogger Logger { get; } = logger;
25+
public string Namespace { get; } = ns;
26+
27+
public Task<HttpOperationResponse<object>> ListAsync<T>(string labelSelector, CancellationToken cancellationToken) where T : CustomResource
28+
{
29+
var info = typeof(T).GetCustomAttribute<KubernetesEntityAttribute>()!;
30+
31+
Logger.BeginWatch(Namespace, info.PluralName, labelSelector);
32+
33+
var response = Client.CustomObjects.ListNamespacedCustomObjectWithHttpMessagesAsync(
34+
info.Group,
35+
info.ApiVersion,
36+
Namespace,
37+
info.PluralName,
38+
watch: true,
39+
allowWatchBookmarks: true,
40+
labelSelector: labelSelector,
41+
timeoutSeconds: (int)TimeSpan.FromMinutes(60).TotalSeconds,
42+
cancellationToken: cancellationToken
43+
);
44+
45+
return response;
46+
}
47+
48+
public async Task<T> ReplaceAsync<T>(T resource, CancellationToken cancellationToken)
49+
where T : CustomResource
50+
{
51+
Logger.ReplaceResource(resource);
52+
53+
var info = resource.GetType().GetCustomAttribute<KubernetesEntityAttribute>()!;
54+
55+
// Replace the resource
56+
var result = await Client.CustomObjects.ReplaceNamespacedCustomObjectAsync<T>(
57+
resource,
58+
info.Group,
59+
info.ApiVersion,
60+
resource.Metadata.NamespaceProperty,
61+
info.PluralName,
62+
resource.Metadata.Name,
63+
cancellationToken: cancellationToken
64+
).ConfigureAwait(false);
65+
66+
return result;
67+
}
68+
}
69+
70+
71+
internal class ClusterKubernetesClient(IKubernetes client, ILogger<ClusterKubernetesClient> logger) : IKubernetesClient
72+
{
73+
public IKubernetes Client { get; } = client;
74+
public ILogger Logger { get; } = logger;
75+
76+
public async Task<T> ReplaceAsync<T>(T resource, CancellationToken cancellationToken)
77+
where T : CustomResource
78+
{
79+
Logger.ReplaceResource(resource);
80+
81+
var info = resource.GetType().GetCustomAttribute<KubernetesEntityAttribute>()!;
82+
83+
// Replace the resource
84+
var result = await Client.CustomObjects.ReplaceClusterCustomObjectAsync<T>(
85+
resource,
86+
info.Group,
87+
info.ApiVersion,
88+
info.PluralName,
89+
resource.Metadata.Name,
90+
cancellationToken: cancellationToken
91+
).ConfigureAwait(false);
92+
93+
return result;
94+
}
95+
96+
public Task<HttpOperationResponse<object>> ListAsync<T>(string labelSelector, CancellationToken cancellationToken)
97+
where T : CustomResource
98+
{
99+
var info = typeof(T).GetCustomAttribute<KubernetesEntityAttribute>()!;
100+
101+
Logger.BeginWatch("cluster-wide", info.PluralName, labelSelector);
102+
103+
var response = Client.CustomObjects.ListClusterCustomObjectWithHttpMessagesAsync(
104+
info.Group,
105+
info.ApiVersion,
106+
info.PluralName,
107+
watch: true,
108+
allowWatchBookmarks: true,
109+
labelSelector: labelSelector,
110+
timeoutSeconds: (int)TimeSpan.FromMinutes(60).TotalSeconds,
111+
cancellationToken: cancellationToken
112+
);
113+
114+
return response;
115+
}
116+
}

src/K8sOperator.NET/Metadata/EntityScopeMetadata.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ public interface IEntityScopeMetadata
3333
/// Sets the scope of the entity.
3434
/// </summary>
3535
/// <param name="scope"></param>
36-
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
36+
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
3737
public class EntityScopeMetadata(EntityScope scope) : Attribute, IEntityScopeMetadata
3838
{
3939
/// <summary>
4040
/// Default value.
4141
/// </summary>
42-
public const EntityScope Default = EntityScope.Namespaced;
42+
public const EntityScope Default = EntityScope.Cluster;
4343

4444
/// <inheritdoc/>
4545
public EntityScope Scope => scope;

0 commit comments

Comments
 (0)