diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml
index 8d43eee..09a252a 100644
--- a/.github/workflows/pipeline.yaml
+++ b/.github/workflows/pipeline.yaml
@@ -11,7 +11,7 @@ on:
- ".github/workflows/**"
env:
- version: 7.1.${{github.run_number}}
+ version: 9.0.${{github.run_number}}
imageRepository: "emberstack/kubernetes-reflector"
DOCKER_CLI_EXPERIMENTAL: "enabled"
diff --git a/README.md b/README.md
index 84761bb..a09cbe3 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ If you need help or found a bug, please feel free to open an Issue on GitHub (ht
Reflector can be deployed either manually or using Helm (recommended).
### Prerequisites
-- Kubernetes 1.14+
+- Kubernetes 1.22+
- Helm 3 (if deployed using Helm)
#### Deployment using Helm
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644
index 0000000..2bbe6ca
--- /dev/null
+++ b/src/Directory.Build.props
@@ -0,0 +1,34 @@
+
+
+
+ false
+ true
+ enable
+ enable
+
+ embedded
+ true
+
+ $(MSBuildThisFileDirectory)..\
+
+ true
+
+
+ CS1591;CS1571;CS1573;CS1574;CS1723;NU1901;NU1902;NU1903;
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+ trx%3bLogFileName=$(MSBuildProjectName).trx
+ $(MSBuildThisFileDirectory).artifacts/TestResults
+
+
\ No newline at end of file
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
new file mode 100644
index 0000000..fe326b7
--- /dev/null
+++ b/src/Directory.Packages.props
@@ -0,0 +1,16 @@
+
+
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ES.Kubernetes.Reflector.sln b/src/ES.Kubernetes.Reflector.sln
index f726f1a..49b0238 100644
--- a/src/ES.Kubernetes.Reflector.sln
+++ b/src/ES.Kubernetes.Reflector.sln
@@ -5,6 +5,11 @@ VisualStudioVersion = 17.0.31710.8
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ES.Kubernetes.Reflector", "ES.Kubernetes.Reflector\ES.Kubernetes.Reflector.csproj", "{96CDE0CF-7782-490B-8AF6-4219DB0236B3}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "....Solution Items", "....Solution Items", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
+ ProjectSection(SolutionItems) = preProject
+ Directory.Build.props = Directory.Build.props
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/src/ES.Kubernetes.Reflector/Core/ConfigMapMirror.cs b/src/ES.Kubernetes.Reflector/Core/ConfigMapMirror.cs
index 430631e..8a37efd 100644
--- a/src/ES.Kubernetes.Reflector/Core/ConfigMapMirror.cs
+++ b/src/ES.Kubernetes.Reflector/Core/ConfigMapMirror.cs
@@ -6,21 +6,23 @@
namespace ES.Kubernetes.Reflector.Core;
-public class ConfigMapMirror : ResourceMirror
+public class ConfigMapMirror(ILogger logger, IServiceProvider serviceProvider)
+ : ResourceMirror(logger, serviceProvider)
{
- public ConfigMapMirror(ILogger logger, IKubernetes client) : base(logger, client)
- {
- }
+ private readonly IServiceProvider _serviceProvider = serviceProvider;
protected override async Task OnResourceWithNameList(string itemRefName)
{
- return (await Client.CoreV1.ListConfigMapForAllNamespacesAsync(fieldSelector: $"metadata.name={itemRefName}")).Items
+ using var client = _serviceProvider.GetRequiredService();
+ return (await client.CoreV1.ListConfigMapForAllNamespacesAsync(fieldSelector: $"metadata.name={itemRefName}"))
+ .Items
.ToArray();
}
- protected override Task OnResourceApplyPatch(V1Patch patch, KubeRef refId)
+ protected override async Task OnResourceApplyPatch(V1Patch patch, KubeRef refId)
{
- return Client.CoreV1.PatchNamespacedConfigMapAsync(patch, refId.Name, refId.Namespace);
+ using var client = _serviceProvider.GetRequiredService();
+ await client.CoreV1.PatchNamespacedConfigMapAsync(patch, refId.Name, refId.Namespace);
}
protected override Task OnResourceConfigurePatch(V1ConfigMap source, JsonPatchDocument patchDoc)
@@ -30,9 +32,10 @@ protected override Task OnResourceConfigurePatch(V1ConfigMap source, JsonPatchDo
return Task.CompletedTask;
}
- protected override Task OnResourceCreate(V1ConfigMap item, string ns)
+ protected override async Task OnResourceCreate(V1ConfigMap item, string ns)
{
- return Client.CoreV1.CreateNamespacedConfigMapAsync(item, ns);
+ using var client = _serviceProvider.GetRequiredService();
+ await client.CoreV1.CreateNamespacedConfigMapAsync(item, ns);
}
protected override Task OnResourceClone(V1ConfigMap sourceResource)
@@ -46,13 +49,15 @@ protected override Task OnResourceClone(V1ConfigMap sourceResource)
});
}
- protected override Task OnResourceDelete(KubeRef resourceId)
+ protected override async Task OnResourceDelete(KubeRef resourceId)
{
- return Client.CoreV1.DeleteNamespacedConfigMapAsync(resourceId.Name, resourceId.Namespace);
+ using var client = _serviceProvider.GetRequiredService();
+ await client.CoreV1.DeleteNamespacedConfigMapAsync(resourceId.Name, resourceId.Namespace);
}
- protected override Task OnResourceGet(KubeRef refId)
+ protected override async Task OnResourceGet(KubeRef refId)
{
- return Client.CoreV1.ReadNamespacedConfigMapAsync(refId.Name, refId.Namespace);
+ using var client = _serviceProvider.GetRequiredService();
+ return await client.CoreV1.ReadNamespacedConfigMapAsync(refId.Name, refId.Namespace);
}
}
\ No newline at end of file
diff --git a/src/ES.Kubernetes.Reflector/Core/ConfigMapWatcher.cs b/src/ES.Kubernetes.Reflector/Core/ConfigMapWatcher.cs
index 8bda2d1..a53881b 100644
--- a/src/ES.Kubernetes.Reflector/Core/ConfigMapWatcher.cs
+++ b/src/ES.Kubernetes.Reflector/Core/ConfigMapWatcher.cs
@@ -8,18 +8,18 @@
namespace ES.Kubernetes.Reflector.Core;
-public class ConfigMapWatcher : WatcherBackgroundService
+public class ConfigMapWatcher(
+ ILogger logger,
+ IMediator mediator,
+ IServiceProvider serviceProvider,
+ IOptionsMonitor options)
+ : WatcherBackgroundService(logger, mediator, serviceProvider, options)
{
- public ConfigMapWatcher(ILogger logger, IMediator mediator, IKubernetes client,
- IOptionsMonitor options) :
- base(logger, mediator, client, options)
+ protected override Task> OnGetWatcher(IKubernetes client,
+ CancellationToken cancellationToken)
{
- }
-
-
- protected override Task> OnGetWatcher(CancellationToken cancellationToken)
- {
- return Client.CoreV1.ListConfigMapForAllNamespacesWithHttpMessagesAsync(watch: true, timeoutSeconds: WatcherTimeout,
+ return client.CoreV1.ListConfigMapForAllNamespacesWithHttpMessagesAsync(watch: true,
+ timeoutSeconds: WatcherTimeout,
cancellationToken: cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/ES.Kubernetes.Reflector/Core/Extensions/MetadataExtensions.cs b/src/ES.Kubernetes.Reflector/Core/Extensions/MetadataExtensions.cs
index a10891a..ae35a58 100644
--- a/src/ES.Kubernetes.Reflector/Core/Extensions/MetadataExtensions.cs
+++ b/src/ES.Kubernetes.Reflector/Core/Extensions/MetadataExtensions.cs
@@ -11,7 +11,7 @@ public static class MetadataExtensions
public static IReadOnlyDictionary SafeAnnotations(this V1ObjectMeta metadata)
{
- return (IReadOnlyDictionary) (metadata.Annotations ?? new Dictionary());
+ return (IReadOnlyDictionary)(metadata.Annotations ?? new Dictionary());
}
public static KubeRef GetRef(this IKubernetesObject resource)
@@ -39,11 +39,11 @@ public static bool TryGet(this IReadOnlyDictionary annotation
Converters.TryAdd(typeof(T), conv);
}
- value = (T?) conv.ConvertFromString(raw.Trim());
+ value = (T?)conv.ConvertFromString(raw.Trim());
}
else
{
- value = (T) Convert.ChangeType(raw.Trim(), typeof(T));
+ value = (T)Convert.ChangeType(raw.Trim(), typeof(T));
}
return true;
diff --git a/src/ES.Kubernetes.Reflector/Core/Json/JsonPropertyNameContractResolver.cs b/src/ES.Kubernetes.Reflector/Core/Json/JsonPropertyNameContractResolver.cs
index ba0772e..e4abae5 100644
--- a/src/ES.Kubernetes.Reflector/Core/Json/JsonPropertyNameContractResolver.cs
+++ b/src/ES.Kubernetes.Reflector/Core/Json/JsonPropertyNameContractResolver.cs
@@ -18,6 +18,5 @@ protected override JsonProperty CreateProperty(MemberInfo member, MemberSerializ
if (member.GetCustomAttribute() is not { } propertyNameAttribute) return property;
property.PropertyName = propertyNameAttribute.Name;
return property;
-
}
}
\ No newline at end of file
diff --git a/src/ES.Kubernetes.Reflector/Core/Mirroring/Extensions/ReflectionStatusExtensions.cs b/src/ES.Kubernetes.Reflector/Core/Mirroring/Extensions/ReflectionStatusExtensions.cs
index 868c0a3..c0b51ec 100644
--- a/src/ES.Kubernetes.Reflector/Core/Mirroring/Extensions/ReflectionStatusExtensions.cs
+++ b/src/ES.Kubernetes.Reflector/Core/Mirroring/Extensions/ReflectionStatusExtensions.cs
@@ -20,7 +20,7 @@ public static bool CanBeAutoReflectedToNamespace(this ReflectorProperties proper
private static bool PatternListMatch(string patternList, string value)
{
if (string.IsNullOrEmpty(patternList)) return true;
- var regexPatterns = patternList.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries);
+ var regexPatterns = patternList.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
return regexPatterns.Where(s => !string.IsNullOrWhiteSpace(s)).Select(s => s.Trim())
.Select(pattern => Regex.Match(value, pattern))
.Any(match => match.Success && match.Value.Length == value.Length);
diff --git a/src/ES.Kubernetes.Reflector/Core/Mirroring/ResourceMirror.cs b/src/ES.Kubernetes.Reflector/Core/Mirroring/ResourceMirror.cs
index 05e7c8f..4a11b47 100644
--- a/src/ES.Kubernetes.Reflector/Core/Mirroring/ResourceMirror.cs
+++ b/src/ES.Kubernetes.Reflector/Core/Mirroring/ResourceMirror.cs
@@ -16,7 +16,7 @@
namespace ES.Kubernetes.Reflector.Core.Mirroring;
-public abstract class ResourceMirror :
+public abstract class ResourceMirror(ILogger logger, IServiceProvider serviceProvider) :
INotificationHandler,
INotificationHandler
where TResource : class, IKubernetesObject
@@ -27,23 +27,14 @@ public abstract class ResourceMirror :
private readonly ConcurrentDictionary _notFoundCache = new();
private readonly ConcurrentDictionary _propertiesCache = new();
- protected readonly IKubernetes Client;
- protected readonly ILogger Logger;
-
-
- protected ResourceMirror(ILogger logger, IKubernetes client)
- {
- Logger = logger;
- Client = client;
- }
+ protected readonly ILogger Logger = logger;
///
- /// Handles notifications
+ /// Handles notifications
///
public Task Handle(WatcherClosed notification, CancellationToken cancellationToken)
{
-
//If not TResource or Namespace, not something this instance should handle
if (notification.ResourceType != typeof(TResource) &&
notification.ResourceType != typeof(V1Namespace)) return Task.CompletedTask;
@@ -60,9 +51,8 @@ public Task Handle(WatcherClosed notification, CancellationToken cancellationTok
}
///
- /// Handles notifications
+ /// Handles notifications
///
-
public async Task Handle(WatcherEvent notification, CancellationToken cancellationToken)
{
switch (notification.Item)
@@ -82,37 +72,37 @@ public async Task Handle(WatcherEvent notification, CancellationToken cancellati
{
case WatchEventType.Added:
case WatchEventType.Modified:
- {
- await HandleUpsert(resource, notification.Type, cancellationToken);
- }
+ {
+ await HandleUpsert(resource, cancellationToken);
+ }
break;
case WatchEventType.Deleted:
+ {
+ _propertiesCache.Remove(itemRef, out _);
+ var properties = resource.GetReflectionProperties();
+
+
+ if (!properties.IsReflection)
+ {
+ if (properties is { Allowed: true, AutoEnabled: true } &&
+ _autoReflectionCache.TryGetValue(itemRef, out var reflectionList))
+ foreach (var reflectionId in reflectionList.ToArray())
+ {
+ Logger.LogDebug("Deleting {id} - Source {sourceId} has been deleted", reflectionId,
+ itemRef);
+ await OnResourceDelete(reflectionId);
+ }
+
+ _autoSources.Remove(itemRef, out _);
+ _directReflectionCache.Remove(itemRef, out _);
+ _autoReflectionCache.Remove(itemRef, out _);
+ }
+ else
{
- _propertiesCache.Remove(itemRef, out _);
- var properties = resource.GetReflectionProperties();
-
-
- if (!properties.IsReflection)
- {
- if (properties is { Allowed: true, AutoEnabled: true } &&
- _autoReflectionCache.TryGetValue(itemRef, out var reflectionList))
- foreach (var reflectionId in reflectionList.ToArray())
- {
- Logger.LogDebug("Deleting {id} - Source {sourceId} has been deleted", reflectionId,
- itemRef);
- await OnResourceDelete(reflectionId);
- }
-
- _autoSources.Remove(itemRef, out _);
- _directReflectionCache.Remove(itemRef, out _);
- _autoReflectionCache.Remove(itemRef, out _);
- }
- else
- {
- foreach (var item in _directReflectionCache) item.Value.Remove(itemRef);
- foreach (var item in _autoReflectionCache) item.Value.Remove(itemRef);
- }
+ foreach (var item in _directReflectionCache) item.Value.Remove(itemRef);
+ foreach (var item in _autoReflectionCache) item.Value.Remove(itemRef);
}
+ }
break;
case WatchEventType.Error:
case WatchEventType.Bookmark:
@@ -122,35 +112,35 @@ public async Task Handle(WatcherEvent notification, CancellationToken cancellati
break;
case V1Namespace ns:
- {
- if (notification.Type != WatchEventType.Added) return;
- Logger.LogTrace("Handling {eventType} {resourceType} {resourceRef}", notification.Type, ns.Kind,
- ns.GetRef());
+ {
+ if (notification.Type != WatchEventType.Added) return;
+ Logger.LogTrace("Handling {eventType} {resourceType} {resourceRef}", notification.Type, ns.Kind,
+ ns.GetRef());
- foreach (var autoSourceRef in _autoSources.Keys)
- {
- var properties = _propertiesCache[autoSourceRef];
- if (!properties.CanBeAutoReflectedToNamespace(ns.Name())) continue;
+ foreach (var autoSourceRef in _autoSources.Keys)
+ {
+ var properties = _propertiesCache[autoSourceRef];
+ if (!properties.CanBeAutoReflectedToNamespace(ns.Name())) continue;
- var reflectionRef = new KubeRef(ns.Name(), autoSourceRef.Name);
- var autoReflectionList = _autoReflectionCache.GetOrAdd(autoSourceRef, new List());
+ var reflectionRef = new KubeRef(ns.Name(), autoSourceRef.Name);
+ var autoReflectionList = _autoReflectionCache.GetOrAdd(autoSourceRef, new List());
- if (autoReflectionList.Contains(reflectionRef)) return;
+ if (autoReflectionList.Contains(reflectionRef)) return;
- await ResourceReflect(autoSourceRef, reflectionRef, null, null, true);
+ await ResourceReflect(autoSourceRef, reflectionRef, null, null, true);
- if (!autoReflectionList.Contains(reflectionRef))
- autoReflectionList.Add(reflectionRef);
- }
+ if (!autoReflectionList.Contains(reflectionRef))
+ autoReflectionList.Add(reflectionRef);
}
+ }
break;
}
}
- private async Task HandleUpsert(TResource resource, WatchEventType eventType, CancellationToken cancellationToken)
+ private async Task HandleUpsert(TResource resource, CancellationToken cancellationToken)
{
var resourceRef = resource.GetRef();
var properties = resource.GetReflectionProperties();
@@ -187,13 +177,13 @@ private async Task HandleUpsert(TResource resource, WatchEventType eventType, Ca
await OnResourceDelete(reflectionId);
}
- var isAutoSource = properties is { Allowed: true, AutoEnabled: true};
+ var isAutoSource = properties is { Allowed: true, AutoEnabled: true };
//Update the status of an auto-source
_autoSources.AddOrUpdate(resourceRef, isAutoSource, (_, _) => isAutoSource);
//If not allowed or auto is disabled, remove the cache for auto-reflections
- if (!isAutoSource) { _autoReflectionCache.Remove(resourceRef, out _); }
+ if (!isAutoSource) _autoReflectionCache.Remove(resourceRef, out _);
//If reflection is disabled, remove the reflections cache and stop reflecting
if (!properties.Allowed)
@@ -207,7 +197,7 @@ private async Task HandleUpsert(TResource resource, WatchEventType eventType, Ca
if (_directReflectionCache.TryGetValue(resourceRef, out reflectionList))
foreach (var reflectionRef in reflectionList.ToArray())
{
- //Try to get the properties for the reflection. Otherwise remove it
+ //Try to get the properties for the reflection. Otherwise, remove it
if (!_propertiesCache.TryGetValue(reflectionRef, out var reflectionProperties))
{
reflectionList.Remove(reflectionRef);
@@ -334,7 +324,8 @@ private async Task AutoReflectionForSource(KubeRef resourceRef, TResource? resou
var autoReflectionList = _autoReflectionCache.GetOrAdd(resourceRef, _ => new List());
var matches = await OnResourceWithNameList(resourceRef.Name);
- var namespaces = (await Client.CoreV1.ListNamespaceAsync(cancellationToken: cancellationToken)).Items;
+ using var client = serviceProvider.GetRequiredService();
+ var namespaces = (await client.CoreV1.ListNamespaceAsync(cancellationToken: cancellationToken)).Items;
foreach (var match in matches)
{
@@ -374,7 +365,6 @@ private async Task AutoReflectionForSource(KubeRef resourceRef, TResource? resou
m.GetReflectionProperties().Reflects.Equals(resourceRef))
.Select(m => m.GetRef()).ToList();
-
autoReflectionList.Clear();
autoReflectionList.AddRange(toCreate);
diff --git a/src/ES.Kubernetes.Reflector/Core/NamespaceWatcher.cs b/src/ES.Kubernetes.Reflector/Core/NamespaceWatcher.cs
index bb8c8be..47642df 100644
--- a/src/ES.Kubernetes.Reflector/Core/NamespaceWatcher.cs
+++ b/src/ES.Kubernetes.Reflector/Core/NamespaceWatcher.cs
@@ -8,18 +8,17 @@
namespace ES.Kubernetes.Reflector.Core;
-public class NamespaceWatcher : WatcherBackgroundService
+public class NamespaceWatcher(
+ ILogger logger,
+ IMediator mediator,
+ IServiceProvider serviceProvider,
+ IOptionsMonitor options)
+ : WatcherBackgroundService(logger, mediator, serviceProvider, options)
{
- public NamespaceWatcher(ILogger logger, IMediator mediator, IKubernetes client,
- IOptionsMonitor options) :
- base(logger, mediator, client, options)
+ protected override Task> OnGetWatcher(IKubernetes client,
+ CancellationToken cancellationToken)
{
- }
-
-
- protected override Task> OnGetWatcher(CancellationToken cancellationToken)
- {
- return Client.CoreV1.ListNamespaceWithHttpMessagesAsync(watch: true, timeoutSeconds: WatcherTimeout,
+ return client.CoreV1.ListNamespaceWithHttpMessagesAsync(watch: true, timeoutSeconds: WatcherTimeout,
cancellationToken: cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/ES.Kubernetes.Reflector/Core/Resources/KubeRef.cs b/src/ES.Kubernetes.Reflector/Core/Resources/KubeRef.cs
index 2ee5836..fb4648e 100644
--- a/src/ES.Kubernetes.Reflector/Core/Resources/KubeRef.cs
+++ b/src/ES.Kubernetes.Reflector/Core/Resources/KubeRef.cs
@@ -15,7 +15,7 @@ public KubeRef(string ns, string name)
public KubeRef(string value)
{
if (string.IsNullOrWhiteSpace(value)) return;
- var split = value.Split(new[] {"/"}, StringSplitOptions.RemoveEmptyEntries).ToList();
+ var split = value.Split(new[] { "/" }, StringSplitOptions.RemoveEmptyEntries).ToList();
if (!split.Any()) return;
switch (split.Count)
{
@@ -39,12 +39,19 @@ public KubeRef(V1ObjectMeta metadata) : this(metadata.Namespace() ?? string.Empt
public string Name { get; } = string.Empty;
+ public bool Equals(KubeRef? other)
+ {
+ if (ReferenceEquals(this, other)) return true;
+ return string.Equals(Namespace, other?.Namespace) && string.Equals(Name, other?.Name);
+ }
+
+
public static bool TryParse(string value, out KubeRef id)
{
id = Empty;
if (string.IsNullOrWhiteSpace(value)) return false;
- var split = value.Trim().Split(new[] {"/"}, StringSplitOptions.RemoveEmptyEntries).ToList();
+ var split = value.Trim().Split(new[] { "/" }, StringSplitOptions.RemoveEmptyEntries).ToList();
if (!split.Any()) return false;
if (split.Count > 2) return false;
id = split.Count == 1
@@ -55,15 +62,6 @@ public static bool TryParse(string value, out KubeRef id)
}
- public bool Equals(KubeRef? other)
- {
- if (ReferenceEquals(this, other)) return true;
- return string.Equals(Namespace, other?.Namespace) && string.Equals(Name, other?.Name);
- }
-
-
-
-
public override int GetHashCode()
{
unchecked
diff --git a/src/ES.Kubernetes.Reflector/Core/SecretMirror.cs b/src/ES.Kubernetes.Reflector/Core/SecretMirror.cs
index f31aa95..f875915 100644
--- a/src/ES.Kubernetes.Reflector/Core/SecretMirror.cs
+++ b/src/ES.Kubernetes.Reflector/Core/SecretMirror.cs
@@ -6,21 +6,23 @@
namespace ES.Kubernetes.Reflector.Core;
-public class SecretMirror : ResourceMirror
+public class SecretMirror(ILogger logger, IServiceProvider serviceProvider)
+ : ResourceMirror(logger, serviceProvider)
{
- public SecretMirror(ILogger logger, IKubernetes client) : base(logger, client)
- {
- }
+ private readonly IServiceProvider _serviceProvider = serviceProvider;
protected override async Task OnResourceWithNameList(string itemRefName)
{
- return (await Client.CoreV1.ListSecretForAllNamespacesAsync(fieldSelector: $"metadata.name={itemRefName}")).Items
+ using var client = _serviceProvider.GetRequiredService();
+ return (await client.CoreV1.ListSecretForAllNamespacesAsync(fieldSelector: $"metadata.name={itemRefName}"))
+ .Items
.ToArray();
}
- protected override Task OnResourceApplyPatch(V1Patch patch, KubeRef refId)
+ protected override async Task OnResourceApplyPatch(V1Patch patch, KubeRef refId)
{
- return Client.CoreV1.PatchNamespacedSecretWithHttpMessagesAsync(patch, refId.Name, refId.Namespace);
+ using var client = _serviceProvider.GetRequiredService();
+ await client.CoreV1.PatchNamespacedSecretWithHttpMessagesAsync(patch, refId.Name, refId.Namespace);
}
protected override Task OnResourceConfigurePatch(V1Secret source, JsonPatchDocument patchDoc)
@@ -29,9 +31,10 @@ protected override Task OnResourceConfigurePatch(V1Secret source, JsonPatchDocum
return Task.CompletedTask;
}
- protected override Task OnResourceCreate(V1Secret item, string ns)
+ protected override async Task OnResourceCreate(V1Secret item, string ns)
{
- return Client.CoreV1.CreateNamespacedSecretAsync(item, ns);
+ using var client = _serviceProvider.GetRequiredService();
+ await client.CoreV1.CreateNamespacedSecretAsync(item, ns);
}
protected override Task OnResourceClone(V1Secret sourceResource)
@@ -45,14 +48,16 @@ protected override Task OnResourceClone(V1Secret sourceResource)
});
}
- protected override Task OnResourceDelete(KubeRef resourceId)
+ protected override async Task OnResourceDelete(KubeRef resourceId)
{
- return Client.CoreV1.DeleteNamespacedSecretAsync(resourceId.Name, resourceId.Namespace);
+ using var client = _serviceProvider.GetRequiredService();
+ await client.CoreV1.DeleteNamespacedSecretAsync(resourceId.Name, resourceId.Namespace);
}
- protected override Task OnResourceGet(KubeRef refId)
+ protected override async Task OnResourceGet(KubeRef refId)
{
- return Client.CoreV1.ReadNamespacedSecretAsync(refId.Name, refId.Namespace);
+ using var client = _serviceProvider.GetRequiredService();
+ return await client.CoreV1.ReadNamespacedSecretAsync(refId.Name, refId.Namespace);
}
protected override Task OnResourceIgnoreCheck(V1Secret item)
diff --git a/src/ES.Kubernetes.Reflector/Core/SecretWatcher.cs b/src/ES.Kubernetes.Reflector/Core/SecretWatcher.cs
index 9c5a13c..cc8995d 100644
--- a/src/ES.Kubernetes.Reflector/Core/SecretWatcher.cs
+++ b/src/ES.Kubernetes.Reflector/Core/SecretWatcher.cs
@@ -8,18 +8,18 @@
namespace ES.Kubernetes.Reflector.Core;
-public class SecretWatcher : WatcherBackgroundService
+public class SecretWatcher(
+ ILogger logger,
+ IMediator mediator,
+ IServiceProvider serviceProvider,
+ IOptionsMonitor options)
+ : WatcherBackgroundService(logger, mediator, serviceProvider, options)
{
- public SecretWatcher(ILogger logger, IMediator mediator, IKubernetes client,
- IOptionsMonitor options) :
- base(logger, mediator, client, options)
+ protected override Task> OnGetWatcher(IKubernetes client,
+ CancellationToken cancellationToken)
{
- }
-
-
- protected override Task> OnGetWatcher(CancellationToken cancellationToken)
- {
- return Client.CoreV1.ListSecretForAllNamespacesWithHttpMessagesAsync(watch: true, timeoutSeconds: WatcherTimeout,
+ return client.CoreV1.ListSecretForAllNamespacesWithHttpMessagesAsync(watch: true,
+ timeoutSeconds: WatcherTimeout,
cancellationToken: cancellationToken);
}
}
\ No newline at end of file
diff --git a/src/ES.Kubernetes.Reflector/Core/Watchers/WatcherBackgroundService.cs b/src/ES.Kubernetes.Reflector/Core/Watchers/WatcherBackgroundService.cs
index 095e453..bbe1b0d 100644
--- a/src/ES.Kubernetes.Reflector/Core/Watchers/WatcherBackgroundService.cs
+++ b/src/ES.Kubernetes.Reflector/Core/Watchers/WatcherBackgroundService.cs
@@ -9,41 +9,43 @@
namespace ES.Kubernetes.Reflector.Core.Watchers;
-public abstract class WatcherBackgroundService : BackgroundService
+public abstract class WatcherBackgroundService(
+ ILogger logger,
+ IMediator mediator,
+ IServiceProvider serviceProvider,
+ IOptionsMonitor options)
+ : BackgroundService
where TResource : IKubernetesObject
{
- private readonly IOptionsMonitor _options;
- protected readonly IKubernetes Client;
- protected readonly ILogger Logger;
- protected readonly IMediator Mediator;
+ protected readonly ILogger Logger = logger;
+ protected readonly IMediator Mediator = mediator;
- protected WatcherBackgroundService(ILogger logger, IMediator mediator, IKubernetes client,
- IOptionsMonitor options)
- {
- Logger = logger;
- Mediator = mediator;
- Client = client;
- _options = options;
- }
-
- protected int? WatcherTimeout => _options.CurrentValue.Watcher?.Timeout;
+ protected int WatcherTimeout => options.CurrentValue.Watcher?.Timeout ?? 3600;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var sessionStopwatch = new Stopwatch();
while (!stoppingToken.IsCancellationRequested)
{
+ await using var scope = serviceProvider.CreateAsyncScope();
+
var sessionFaulted = false;
sessionStopwatch.Restart();
+
try
{
Logger.LogInformation("Requesting {type} resources", typeof(TResource).Name);
- using var watcher = OnGetWatcher(stoppingToken);
- var watchList = watcher.WatchAsync(cancellationToken: stoppingToken);
- await foreach (var (type, item) in watchList
- .WithCancellation(stoppingToken))
+
+ using var absoluteTimeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(WatcherTimeout + 3));
+ using var cancellationCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken, absoluteTimeoutCts.Token);
+ using var client = scope.ServiceProvider.GetRequiredService();
+
+ using var watcher = OnGetWatcher(client, stoppingToken);
+ var watchList = watcher.WatchAsync(cancellationToken: cancellationCts.Token);
+
+ await foreach (var (type, item) in watchList)
await Mediator.Publish(new WatcherEvent
{
Item = item,
@@ -77,5 +79,6 @@ await Mediator.Publish(new WatcherClosed
}
}
- protected abstract Task> OnGetWatcher(CancellationToken cancellationToken);
+ protected abstract Task> OnGetWatcher(IKubernetes client,
+ CancellationToken cancellationToken);
}
\ No newline at end of file
diff --git a/src/ES.Kubernetes.Reflector/Dockerfile b/src/ES.Kubernetes.Reflector/Dockerfile
index ca12d46..b657cfe 100644
--- a/src/ES.Kubernetes.Reflector/Dockerfile
+++ b/src/ES.Kubernetes.Reflector/Dockerfile
@@ -1,8 +1,9 @@
-FROM mcr.microsoft.com/dotnet/aspnet:7.0-bookworm-slim AS base
+FROM mcr.microsoft.com/dotnet/aspnet:9.0-bookworm-slim AS base
+USER app
WORKDIR /app
-EXPOSE 25080
+EXPOSE 8080
-FROM mcr.microsoft.com/dotnet/sdk:7.0-bookworm-slim-amd64 AS build
+FROM mcr.microsoft.com/dotnet/sdk:9.0-bookworm-slim-amd64 AS build
WORKDIR /src
COPY ["ES.Kubernetes.Reflector/ES.Kubernetes.Reflector.csproj", "ES.Kubernetes.Reflector/"]
RUN dotnet restore "ES.Kubernetes.Reflector/ES.Kubernetes.Reflector.csproj"
diff --git a/src/ES.Kubernetes.Reflector/ES.Kubernetes.Reflector.csproj b/src/ES.Kubernetes.Reflector/ES.Kubernetes.Reflector.csproj
index e4146be..52051b0 100644
--- a/src/ES.Kubernetes.Reflector/ES.Kubernetes.Reflector.csproj
+++ b/src/ES.Kubernetes.Reflector/ES.Kubernetes.Reflector.csproj
@@ -1,7 +1,7 @@
-
+
- net7.0
+ net9.0
enable
enable
Linux
@@ -9,15 +9,13 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ES.Kubernetes.Reflector/Program.cs b/src/ES.Kubernetes.Reflector/Program.cs
index 5cae923..08f140f 100644
--- a/src/ES.Kubernetes.Reflector/Program.cs
+++ b/src/ES.Kubernetes.Reflector/Program.cs
@@ -1,103 +1,53 @@
-using Autofac;
-using Autofac.Extensions.DependencyInjection;
+using ES.FX.Hosting.Lifetime;
+using ES.FX.Ignite.Hosting;
+using ES.FX.Ignite.OpenTelemetry.Exporter.Seq.Hosting;
+using ES.FX.Ignite.Serilog.Hosting;
+using ES.FX.Serilog.Lifetime;
using ES.Kubernetes.Reflector.Core;
using ES.Kubernetes.Reflector.Core.Configuration;
using k8s;
using Microsoft.Extensions.Options;
-using Serilog;
-Log.Logger = new LoggerConfiguration()
- .ReadFrom.Configuration(new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("reflector.logging.json")
- .AddEnvironmentVariables("ES_")
- .AddCommandLine(args)
- .Build())
- .CreateLogger();
-
-
-try
+return await ProgramEntry.CreateBuilder(args).UseSerilog().Build().RunAsync(async _ =>
{
- Log.Information("Starting host");
-
var builder = WebApplication.CreateBuilder(args);
- builder.Environment.EnvironmentName =
- Environment.GetEnvironmentVariable($"{nameof(ES)}_{nameof(Environment)}") ??
- Environments.Production;
+ builder.Logging.ClearProviders();
- builder.Configuration.AddJsonFile("appsettings.json", false, true);
- builder.Configuration.AddJsonFile("reflector.logging.json");
builder.Configuration.AddEnvironmentVariables("ES_");
- builder.Configuration.AddCommandLine(args);
-
- builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
- builder.Host.UseSerilog();
- builder.Host.UseConsoleLifetime();
-
- builder.Services.AddHttpClient();
- builder.Services.AddOptions();
- builder.Services.AddHealthChecks();
- builder.Services.AddMediatR(config => config.RegisterServicesFromAssembly(typeof(void).Assembly));
- builder.Services.AddControllers();
+ builder.Ignite();
+ builder.IgniteSerilog();
+ builder.IgniteSeqOpenTelemetryExporter();
+ builder.Services.AddMediatR(config =>
+ config.RegisterServicesFromAssembly(typeof(Program).Assembly));
- builder.Services.Configure(builder.Configuration.GetSection("Reflector"));
+ builder.Services.Configure(builder.Configuration.GetSection(nameof(ES.Kubernetes.Reflector)));
-
- builder.Services.AddSingleton(s =>
+ builder.Services.AddTransient(s =>
{
var reflectorOptions = s.GetRequiredService>();
var config = KubernetesClientConfiguration.BuildDefaultConfig();
- config.HttpClientTimeout = TimeSpan.FromMinutes(30);
if (reflectorOptions.Value.Kubernetes is not null)
- {
- config.SkipTlsVerify =
+ config.SkipTlsVerify =
reflectorOptions.Value.Kubernetes.SkipTlsVerify.GetValueOrDefault(false);
- }
return config;
});
-
-
- builder.Services.AddSingleton(s =>
- new Kubernetes(s.GetRequiredService()));
+ builder.Services.AddTransient(s =>
+ new Kubernetes(s.GetRequiredService()));
- builder.Host.ConfigureContainer((ContainerBuilder container) =>
- {
- container.Register(c => c.Resolve().CreateClient()).AsSelf();
-
- container.RegisterType().AsImplementedInterfaces().SingleInstance();
-
- container.RegisterType().AsImplementedInterfaces().SingleInstance();
- container.RegisterType().AsImplementedInterfaces().SingleInstance();
+ builder.Services.AddHostedService();
+ builder.Services.AddHostedService();
+ builder.Services.AddHostedService();
- container.RegisterType().AsImplementedInterfaces().SingleInstance();
- container.RegisterType().AsImplementedInterfaces().SingleInstance();
- });
-
- builder.WebHost.ConfigureKestrel(options => { options.ListenAnyIP(25080); });
+ builder.Services.AddSingleton();
+ builder.Services.AddSingleton();
var app = builder.Build();
-
- if (!app.Environment.IsDevelopment()) app.UseExceptionHandler("/Error");
-
- app.UseStaticFiles();
- app.UseRouting();
- app.UseHealthChecks("/healthz");
- app.UseAuthorization();
-
+ app.Ignite();
await app.RunAsync();
return 0;
-}
-catch (Exception ex)
-{
- Log.Fatal(ex, "Host terminated unexpectedly");
- return 1;
-}
-finally
-{
- Log.CloseAndFlush();
-}
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/src/ES.Kubernetes.Reflector/Properties/launchSettings.json b/src/ES.Kubernetes.Reflector/Properties/launchSettings.json
index 721ffa5..8df3f75 100644
--- a/src/ES.Kubernetes.Reflector/Properties/launchSettings.json
+++ b/src/ES.Kubernetes.Reflector/Properties/launchSettings.json
@@ -4,9 +4,9 @@
"commandName": "Project",
"launchBrowser": false,
"environmentVariables": {
- "ES_ENVIRONMENT": "Development"
+ "ASPNETCORE_ENVIRONMENT": "Development"
},
- "applicationUrl": "http://0.0.0.0:25080",
+ "applicationUrl": "http://0.0.0.0:8080",
"dotnetRunMessages": false
},
"Docker": {
diff --git a/src/ES.Kubernetes.Reflector/appsettings.Development.json b/src/ES.Kubernetes.Reflector/appsettings.Development.json
index e47ef4b..aaae1f7 100644
--- a/src/ES.Kubernetes.Reflector/appsettings.Development.json
+++ b/src/ES.Kubernetes.Reflector/appsettings.Development.json
@@ -1,3 +1,23 @@
{
- "DetailedErrors": true
+ "Serilog": {
+ "MinimumLevel": {
+ "Override": {
+ "ES.Kubernetes": "Verbose"
+ }
+ }
+ },
+
+ "Ignite": {
+ "OpenTelemetry": {
+ "Exporter": {
+ "Seq": {
+ "IngestionEndpoint": "http://seq.localenv.io:5341",
+ "HealthUrl": "http://seq.localenv.io/health",
+ "Settings": {
+ "Enabled": true
+ }
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/ES.Kubernetes.Reflector/appsettings.json b/src/ES.Kubernetes.Reflector/appsettings.json
index 383f7fa..2864055 100644
--- a/src/ES.Kubernetes.Reflector/appsettings.json
+++ b/src/ES.Kubernetes.Reflector/appsettings.json
@@ -1,5 +1,50 @@
{
- "AllowedHosts": "*",
+ "Serilog": {
+ "Using": [ "Serilog.Sinks.Seq" ],
+ "LevelSwitches": { "$consoleLevelSwitch": "Verbose" },
+ "MinimumLevel": {
+ "Default": "Verbose",
+ "Override": {
+ "Microsoft": "Information",
+ "System.Net.Http": "Warning",
+ "Polly": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information",
+ "Microsoft.AspNetCore": "Warning",
+ "Microsoft.AspNetCore.DataProtection": "Error",
+ "ES.FX": "Information",
+ "ES.Kubernetes.Reflector": "Information"
+ }
+ },
+ "WriteTo": [
+ {
+ "Name": "Console",
+ "Args": {
+ "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}",
+ "levelSwitch": "$consoleLevelSwitch"
+ }
+ }
+ ]
+ },
+ "Ignite": {
+ "Settings": {
+ "Configuration": {
+ "AdditionalJsonSettingsFiles": [],
+ "AdditionalJsonAppSettingsOverrides": [ "overrides" ]
+ },
+ "OpenTelemetry": {
+ "AspNetCoreTracingHealthChecksRequestsFiltered": true
+ }
+ },
+ "OpenTelemetry": {
+ "Exporter": {
+ "Seq": {
+ "Settings": {
+ "Enabled": false
+ }
+ }
+ }
+ }
+ },
"Reflector": {
"Watcher": {
"Timeout": ""
diff --git a/src/ES.Kubernetes.Reflector/reflector.logging.json b/src/ES.Kubernetes.Reflector/reflector.logging.json
deleted file mode 100644
index 0e768f6..0000000
--- a/src/ES.Kubernetes.Reflector/reflector.logging.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "Serilog": {
- "Using": ["Serilog.Sinks.Console"],
- "MinimumLevel": {
- "Default": "Information",
- "Override": {
- "Microsoft.AspNetCore.Server.Kestrel": "Error",
- "Microsoft": "Warning",
- "System": "Warning",
- "Microsoft.Extensions.Http": "Warning"
- }
- },
- "WriteTo": [
- {
- "Name": "Console",
- "Args": {
- "outputTemplate":
- "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}"
- }
- }
- ]
- }
-}
\ No newline at end of file
diff --git a/src/NuGet.config b/src/NuGet.config
new file mode 100644
index 0000000..492986b
--- /dev/null
+++ b/src/NuGet.config
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/helm/reflector/templates/cron.yaml b/src/helm/reflector/templates/cron.yaml
index 34ef812..b96dcad 100644
--- a/src/helm/reflector/templates/cron.yaml
+++ b/src/helm/reflector/templates/cron.yaml
@@ -68,6 +68,11 @@ spec:
value: {{ .Values.configuration.logging.minimumLevel | quote }}
- name: ES_Reflector__Watcher__Timeout
value: {{ .Values.configuration.watcher.timeout | quote }}
+ - name: ES_Reflector__Kubernetes__SkipTlsVerify
+ value: {{ .Values.configuration.kubernetes.skipTlsVerify | quote }}
+ {{- with .Values.extraEnv }}
+ {{- toYaml . | nindent 12 }}
+ {{- end }}
resources:
{{- toYaml .Values.resources | nindent 16 }}
{{- end }}
diff --git a/src/helm/reflector/templates/deployment.yaml b/src/helm/reflector/templates/deployment.yaml
index d6c1dc6..7085512 100644
--- a/src/helm/reflector/templates/deployment.yaml
+++ b/src/helm/reflector/templates/deployment.yaml
@@ -57,21 +57,15 @@ spec:
ports:
- name: http
- containerPort: 25080
+ containerPort: 8080
protocol: TCP
livenessProbe:
- {{- toYaml .Values.healthcheck | nindent 12 }}
- initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
- periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
+ {{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
- {{- toYaml .Values.healthcheck | nindent 12 }}
- initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
- periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
+ {{- toYaml .Values.readinessProbe | nindent 12 }}
{{- if semverCompare ">= 1.18-0" .Capabilities.KubeVersion.Version }}
startupProbe:
- {{- toYaml .Values.healthcheck | nindent 12 }}
- failureThreshold: {{ .Values.startupProbe.failureThreshold }}
- periodSeconds: {{ .Values.startupProbe.periodSeconds }}
+ {{- toYaml .Values.startupProbe | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
@@ -89,7 +83,7 @@ spec:
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
- {{ - with .Values.volumes }}
+ {{- with .Values.volumes }}
volumes:
{{- toYaml . | nindent 8}}
{{- end }}
diff --git a/src/helm/reflector/values.yaml b/src/helm/reflector/values.yaml
index b4da1b4..14f9b78 100644
--- a/src/helm/reflector/values.yaml
+++ b/src/helm/reflector/values.yaml
@@ -61,21 +61,31 @@ securityContext:
runAsNonRoot: true
runAsUser: 1000
-healthcheck:
+livenessProbe:
httpGet:
- path: /healthz
+ path: /health/live
port: http
-
-livenessProbe:
+ timeoutSeconds: 10
initialDelaySeconds: 5
periodSeconds: 10
+ failureThreshold: 5
readinessProbe:
+ httpGet:
+ path: /health/ready
+ port: http
+ timeoutSeconds: 10
initialDelaySeconds: 5
periodSeconds: 10
+ failureThreshold: 5
startupProbe:
- # The application will have a maximum of 50s (10 * 5 = 50s) to finish its startup.
- failureThreshold: 10
- periodSeconds: 5
+ httpGet:
+ path: /health/ready
+ port: http
+ timeoutSeconds: 10
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ failureThreshold: 5
+
resources:
{}
@@ -107,11 +117,6 @@ topologySpreadConstraints: []
priorityClassName: ""
-#mount external persistent/ephemeral storage to required locations if readOnlyRootFileSystem=true
-volumes:
- - name: tmp
- emptyDir: {}
+volumes: []
-volumeMounts:
- - name: tmp
- mountPath: /tmp
\ No newline at end of file
+volumeMounts: []
\ No newline at end of file