diff --git a/KubeOps.sln b/KubeOps.sln
index c121685e..fa42e10c 100644
--- a/KubeOps.sln
+++ b/KubeOps.sln
@@ -47,6 +47,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KubeOps.Transpiler.Test", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KubeOps.Transpiler", "src\KubeOps.Transpiler\KubeOps.Transpiler.csproj", "{A793FC08-E76C-448B-BE93-88C137D2C7AB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KubeOps.KubernetesClient", "src\KubeOps.KubernetesClient\KubeOps.KubernetesClient.csproj", "{C2C6FF06-2B9D-4FAC-A039-3DC1E007DE3B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KubeOps.KubernetesClient.Test", "test\KubeOps.KubernetesClient.Test\KubeOps.KubernetesClient.Test.csproj", "{25F767E5-7A74-459B-83CC-39519461F38B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -68,6 +72,8 @@ Global
{BC6E6D3C-6404-4B01-9D72-AA16FEEBFF5C} = {C587731F-8191-4A19-8662-B89A60FE79A1}
{47914451-147D-427E-B150-9C47DBF28F2C} = {C587731F-8191-4A19-8662-B89A60FE79A1}
{A793FC08-E76C-448B-BE93-88C137D2C7AB} = {4DB01062-6DC5-4028-BB72-C0619C2F5F2E}
+ {C2C6FF06-2B9D-4FAC-A039-3DC1E007DE3B} = {4DB01062-6DC5-4028-BB72-C0619C2F5F2E}
+ {25F767E5-7A74-459B-83CC-39519461F38B} = {C587731F-8191-4A19-8662-B89A60FE79A1}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E9A0B04E-D90E-4B94-90E0-DD3666B098FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@@ -118,5 +124,13 @@ Global
{A793FC08-E76C-448B-BE93-88C137D2C7AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A793FC08-E76C-448B-BE93-88C137D2C7AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A793FC08-E76C-448B-BE93-88C137D2C7AB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C2C6FF06-2B9D-4FAC-A039-3DC1E007DE3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C2C6FF06-2B9D-4FAC-A039-3DC1E007DE3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C2C6FF06-2B9D-4FAC-A039-3DC1E007DE3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C2C6FF06-2B9D-4FAC-A039-3DC1E007DE3B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {25F767E5-7A74-459B-83CC-39519461F38B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {25F767E5-7A74-459B-83CC-39519461F38B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {25F767E5-7A74-459B-83CC-39519461F38B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {25F767E5-7A74-459B-83CC-39519461F38B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/src/KubeOps.Abstractions/Builder/IOperatorBuilder.cs b/src/KubeOps.Abstractions/Builder/IOperatorBuilder.cs
index 649a98a8..bfba5682 100644
--- a/src/KubeOps.Abstractions/Builder/IOperatorBuilder.cs
+++ b/src/KubeOps.Abstractions/Builder/IOperatorBuilder.cs
@@ -19,14 +19,14 @@ public interface IOperatorBuilder
IServiceCollection Services { get; }
///
- /// Add metadata for an entity to the operator.
+ /// Add an entity with its metadata to the operator.
/// Metadata must be added for each entity to be used in
/// controllers and other elements.
///
/// The metadata of the entity.
/// The type of the entity.
/// The builder for chaining.
- IOperatorBuilder AddEntityMetadata(EntityMetadata metadata)
+ IOperatorBuilder AddEntity(EntityMetadata metadata)
where TEntity : IKubernetesObject;
///
@@ -48,7 +48,7 @@ IOperatorBuilder AddController()
/// Implementation type of the controller.
/// Entity type.
/// The builder for chaining.
- IOperatorBuilder AddController(EntityMetadata metadata)
+ IOperatorBuilder AddControllerWithEntity(EntityMetadata metadata)
where TImplementation : class, IEntityController
where TEntity : IKubernetesObject;
}
diff --git a/src/KubeOps.Abstractions/Entities/EntityList.cs b/src/KubeOps.Abstractions/Entities/EntityList.cs
new file mode 100644
index 00000000..5071e7eb
--- /dev/null
+++ b/src/KubeOps.Abstractions/Entities/EntityList.cs
@@ -0,0 +1,22 @@
+using k8s;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Entities;
+
+///
+/// Type for a list of entities.
+///
+/// Type for the list entries.
+public class EntityList : KubernetesObject
+ where T : IKubernetesObject
+{
+ ///
+ /// Official list metadata object of kubernetes.
+ ///
+ public V1ListMeta Metadata { get; set; } = new();
+
+ ///
+ /// The list of items.
+ ///
+ public IList Items { get; set; } = new List();
+}
diff --git a/src/KubeOps.Abstractions/Entities/Extensions.cs b/src/KubeOps.Abstractions/Entities/Extensions.cs
new file mode 100644
index 00000000..06045853
--- /dev/null
+++ b/src/KubeOps.Abstractions/Entities/Extensions.cs
@@ -0,0 +1,42 @@
+using k8s;
+using k8s.Models;
+
+namespace KubeOps.Abstractions.Entities;
+
+///
+/// Method extensions for .
+///
+public static class Extensions
+{
+ ///
+ /// Sets the resource version of the specified Kubernetes object to the specified value.
+ ///
+ /// The type of the Kubernetes object.
+ /// The Kubernetes object.
+ /// The resource version to set.
+ /// The Kubernetes object with the updated resource version.
+ public static TEntity WithResourceVersion(
+ this TEntity entity,
+ string resourceVersion)
+ where TEntity : IKubernetesObject
+ {
+ entity.EnsureMetadata().ResourceVersion = resourceVersion;
+ return entity;
+ }
+
+ ///
+ /// Sets the resource version of the specified Kubernetes object to the resource version of another object.
+ ///
+ /// The type of the Kubernetes object.
+ /// The Kubernetes object.
+ /// The other Kubernetes object.
+ /// The Kubernetes object with the updated resource version.
+ public static TEntity WithResourceVersion(
+ this TEntity entity,
+ TEntity other)
+ where TEntity : IKubernetesObject
+ {
+ entity.EnsureMetadata().ResourceVersion = other.ResourceVersion();
+ return entity;
+ }
+}
diff --git a/src/KubeOps.Generator/EntityDefinitions/EntityDefinitionGenerator.cs b/src/KubeOps.Generator/EntityDefinitions/EntityDefinitionGenerator.cs
index 5eceb383..240b7a7e 100644
--- a/src/KubeOps.Generator/EntityDefinitions/EntityDefinitionGenerator.cs
+++ b/src/KubeOps.Generator/EntityDefinitions/EntityDefinitionGenerator.cs
@@ -1,3 +1,5 @@
+using KubeOps.Generator.SyntaxReceiver;
+
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -93,7 +95,7 @@ public void Execute(GeneratorExecutionContext context)
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("builder"),
- GenericName(Identifier("AddEntityMetadata"))
+ GenericName(Identifier("AddEntity"))
.WithTypeArgumentList(
TypeArgumentList(
SingletonSeparatedList(
diff --git a/src/KubeOps.Generator/EntityDefinitions/AttributedEntity.cs b/src/KubeOps.Generator/SyntaxReceiver/AttributedEntity.cs
similarity index 77%
rename from src/KubeOps.Generator/EntityDefinitions/AttributedEntity.cs
rename to src/KubeOps.Generator/SyntaxReceiver/AttributedEntity.cs
index b8758231..58674eff 100644
--- a/src/KubeOps.Generator/EntityDefinitions/AttributedEntity.cs
+++ b/src/KubeOps.Generator/SyntaxReceiver/AttributedEntity.cs
@@ -1,10 +1,10 @@
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-
-namespace KubeOps.Generator.EntityDefinitions;
-
-public record struct AttributedEntity(
- ClassDeclarationSyntax Class,
- string Kind,
- string Version,
- string? Group,
- string? Plural);
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace KubeOps.Generator.SyntaxReceiver;
+
+public record struct AttributedEntity(
+ ClassDeclarationSyntax Class,
+ string Kind,
+ string Version,
+ string? Group,
+ string? Plural);
diff --git a/src/KubeOps.Generator/EntityDefinitions/KubernetesEntitySyntaxReceiver.cs b/src/KubeOps.Generator/SyntaxReceiver/KubernetesEntitySyntaxReceiver.cs
similarity index 94%
rename from src/KubeOps.Generator/EntityDefinitions/KubernetesEntitySyntaxReceiver.cs
rename to src/KubeOps.Generator/SyntaxReceiver/KubernetesEntitySyntaxReceiver.cs
index 2eaa0218..37cfe3ac 100644
--- a/src/KubeOps.Generator/EntityDefinitions/KubernetesEntitySyntaxReceiver.cs
+++ b/src/KubeOps.Generator/SyntaxReceiver/KubernetesEntitySyntaxReceiver.cs
@@ -1,38 +1,40 @@
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-
-namespace KubeOps.Generator.EntityDefinitions;
-
-public class KubernetesEntitySyntaxReceiver : ISyntaxContextReceiver
-{
- private const string KindName = "Kind";
- private const string GroupName = "Group";
- private const string PluralName = "Plural";
- private const string VersionName = "ApiVersion";
- private const string DefaultVersion = "v1";
-
- public List Entities { get; } = new();
-
- public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
- {
- if (context.Node is not ClassDeclarationSyntax { AttributeLists.Count: > 0 } cls ||
- cls.AttributeLists.SelectMany(a => a.Attributes)
- .FirstOrDefault(a => a.Name.ToString() == "KubernetesEntity") is not { } attr)
- {
- return;
- }
-
- Entities.Add(new(
- cls,
- GetArgumentValue(attr, KindName) ?? cls.Identifier.ToString(),
- GetArgumentValue(attr, VersionName) ?? DefaultVersion,
- GetArgumentValue(attr, GroupName),
- GetArgumentValue(attr, PluralName)));
- }
-
- private static string? GetArgumentValue(AttributeSyntax attr, string argName) =>
- attr.ArgumentList?.Arguments.FirstOrDefault(a => a.NameEquals?.Name.ToString() == argName) is
- { Expression: LiteralExpressionSyntax { Token.ValueText: { } value } }
- ? value
- : null;
-}
+using KubeOps.Generator.EntityDefinitions;
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace KubeOps.Generator.SyntaxReceiver;
+
+public class KubernetesEntitySyntaxReceiver : ISyntaxContextReceiver
+{
+ private const string KindName = "Kind";
+ private const string GroupName = "Group";
+ private const string PluralName = "Plural";
+ private const string VersionName = "ApiVersion";
+ private const string DefaultVersion = "v1";
+
+ public List Entities { get; } = new();
+
+ public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
+ {
+ if (context.Node is not ClassDeclarationSyntax { AttributeLists.Count: > 0 } cls ||
+ cls.AttributeLists.SelectMany(a => a.Attributes)
+ .FirstOrDefault(a => a.Name.ToString() == "KubernetesEntity") is not { } attr)
+ {
+ return;
+ }
+
+ Entities.Add(new(
+ cls,
+ GetArgumentValue(attr, KindName) ?? cls.Identifier.ToString(),
+ GetArgumentValue(attr, VersionName) ?? DefaultVersion,
+ GetArgumentValue(attr, GroupName),
+ GetArgumentValue(attr, PluralName)));
+ }
+
+ private static string? GetArgumentValue(AttributeSyntax attr, string argName) =>
+ attr.ArgumentList?.Arguments.FirstOrDefault(a => a.NameEquals?.Name.ToString() == argName) is
+ { Expression: LiteralExpressionSyntax { Token.ValueText: { } value } }
+ ? value
+ : null;
+}
diff --git a/src/KubeOps.KubernetesClient/IKubernetesClient.cs b/src/KubeOps.KubernetesClient/IKubernetesClient.cs
index 3476667d..68a966c5 100644
--- a/src/KubeOps.KubernetesClient/IKubernetesClient.cs
+++ b/src/KubeOps.KubernetesClient/IKubernetesClient.cs
@@ -1,21 +1,19 @@
using k8s;
using k8s.Models;
+using KubeOps.Abstractions.Entities;
using KubeOps.KubernetesClient.LabelSelectors;
namespace KubeOps.KubernetesClient;
///
/// Client for the Kubernetes API. Contains various methods to manage Kubernetes entities.
+/// This client is specific to an entity of type .
///
-public interface IKubernetesClient
+/// The type of the Kubernetes entity.
+public interface IKubernetesClient
+ where TEntity : IKubernetesObject
{
- ///
- /// Represents the "original" kubernetes client from the
- /// "KubernetesClient" package.
- ///
- IKubernetes ApiClient { get; }
-
///
/// Return the base URI of the currently used KubernetesClient.
///
@@ -50,35 +48,25 @@ public interface IKubernetesClient
Task GetCurrentNamespace(string downwardApiEnvName = "POD_NAMESPACE");
///
- /// Fetch and return the actual Kubernetes (aka. Server Version).
- ///
- /// The of the current server.
- Task GetServerVersion();
-
- ///
- /// Fetch and return a entity from the Kubernetes API.
+ /// Fetch and return an entity from the Kubernetes API.
///
/// The name of the entity (metadata.name).
///
/// Optional namespace. If this is set, the entity must be a namespaced entity.
/// If it is omitted, the entity must be a cluster wide entity.
///
- /// The concrete type of the entity.
/// The found entity of the given type, or null otherwise.
- Task Get(string name, string? @namespace = null)
- where TEntity : class, IKubernetesObject;
+ Task Get(string name, string? @namespace = null);
///
/// Fetch and return a list of entities from the Kubernetes API.
///
/// If the entities are namespaced, provide the name of the namespace.
/// A string, representing an optional label selector for filtering fetched objects.
- /// The concrete type of the entity.
/// A list of Kubernetes entities.
- Task> List(
+ Task> List(
string? @namespace = null,
- string? labelSelector = null)
- where TEntity : IKubernetesObject;
+ string? labelSelector = null);
///
/// Fetch and return a list of entities from the Kubernetes API.
@@ -87,93 +75,78 @@ Task> List(
/// If only entities in a given namespace should be listed, provide the namespace here.
///
/// A list of label-selectors to apply to the search.
- /// The concrete type of the entity.
/// A list of Kubernetes entities.
- Task> List(
+ Task> List(
string? @namespace = null,
- params ILabelSelector[] labelSelectors)
- where TEntity : IKubernetesObject;
+ params LabelSelector[] labelSelectors);
///
/// Create or Update a entity. This first fetches the entity from the Kubernetes API
/// and if it does exist, updates the entity. Otherwise, the entity is created.
///
/// The entity in question.
- /// The concrete type of the entity.
/// The saved instance of the entity.
- Task Save(TEntity entity)
- where TEntity : class, IKubernetesObject;
+ async Task Save(TEntity entity) => await Get(entity.Name(), entity.Namespace()) switch
+ {
+ { } e => await Update(entity.WithResourceVersion(e)),
+ _ => await Create(entity),
+ };
///
/// Create the given entity on the Kubernetes API.
///
/// The entity instance.
- /// The concrete type of the entity.
/// The created instance of the entity.
- Task Create(TEntity entity)
- where TEntity : IKubernetesObject;
+ Task Create(TEntity entity);
///
/// Update the given entity on the Kubernetes API.
///
/// The entity instance.
- /// The concrete type of the entity.
/// The updated instance of the entity.
- Task Update(TEntity entity)
- where TEntity : IKubernetesObject;
+ Task Update(TEntity entity);
///
/// Update the status object of a given entity on the Kubernetes API.
///
/// The entity that contains a status object.
- /// The concrete type of the entity.
/// A task that completes when the call was made.
- public Task UpdateStatus(TEntity entity)
- where TEntity : IKubernetesObject;
+ public Task UpdateStatus(TEntity entity);
///
/// Delete a given entity from the Kubernetes API.
///
/// The entity in question.
- /// The concrete type of the entity.
/// A task that completes when the call was made.
- Task Delete(TEntity entity)
- where TEntity : IKubernetesObject;
+ Task Delete(TEntity entity);
///
/// Delete a given list of entities from the Kubernetes API.
///
/// The entities in question.
- /// The concrete type of the entity.
/// A task that completes when the calls were made.
- Task Delete(IEnumerable entities)
- where TEntity : IKubernetesObject;
+ Task Delete(IEnumerable entities);
///
/// Delete a given list of entities from the Kubernetes API.
///
/// The entities in question.
- /// The concrete type of the entity.
/// A task that completes when the calls were made.
- Task Delete(params TEntity[] entities)
- where TEntity : IKubernetesObject;
+ Task Delete(params TEntity[] entities);
///
/// Delete a given entity by name from the Kubernetes API.
///
/// The name of the entity.
/// The optional namespace of the entity.
- /// The concrete type of the entity.
/// A task that completes when the call was made.
- Task Delete(string name, string? @namespace = null)
- where TEntity : IKubernetesObject;
+ Task Delete(string name, string? @namespace = null);
///
/// Create a entity watcher on the Kubernetes API.
/// The entity watcher fires events for entity-events on
/// Kubernetes (events: .
///
- /// The timeout which the watcher has (after this timeout, the server will close the connection).
/// Action that is called when an event occurs.
/// Action that handles exceptions.
/// Action that handles closed connections.
@@ -181,26 +154,24 @@ Task Delete(string name, string? @namespace = null)
/// The namespace to watch for entities (if needed).
/// If the namespace is omitted, all entities on the cluster are watched.
///
+ /// The timeout which the watcher has (after this timeout, the server will close the connection).
/// Cancellation-Token.
/// A list of label-selectors to apply to the search.
- /// The concrete type of the entity.
/// A entity watcher for the given entity.
- Task> Watch(
- TimeSpan timeout,
+ Watcher Watch(
Action onEvent,
Action? onError = null,
Action? onClose = null,
string? @namespace = null,
+ TimeSpan? timeout = null,
CancellationToken cancellationToken = default,
- params ILabelSelector[] labelSelectors)
- where TEntity : IKubernetesObject;
+ params LabelSelector[] labelSelectors);
///
/// Create a entity watcher on the Kubernetes API.
/// The entity watcher fires events for entity-events on
/// Kubernetes (events: .
///
- /// The timeout which the watcher has (after this timeout, the server will close the connection).
/// Action that is called when an event occurs.
/// Action that handles exceptions.
/// Action that handles closed connections.
@@ -208,17 +179,16 @@ Task> Watch(
/// The namespace to watch for entities (if needed).
/// If the namespace is omitted, all entities on the cluster are watched.
///
- /// Cancellation-Token.
+ /// The timeout which the watcher has (after this timeout, the server will close the connection).
/// A string, representing an optional label selector for filtering watched objects.
- /// The concrete type of the entity.
+ /// Cancellation-Token.
/// A entity watcher for the given entity.
- Task> Watch(
- TimeSpan timeout,
+ Watcher Watch(
Action onEvent,
Action? onError = null,
Action? onClose = null,
string? @namespace = null,
- CancellationToken cancellationToken = default,
- string? labelSelector = null)
- where TEntity : IKubernetesObject;
+ TimeSpan? timeout = null,
+ string? labelSelector = null,
+ CancellationToken cancellationToken = default);
}
diff --git a/src/KubeOps.KubernetesClient/KubernetesClient.cs b/src/KubeOps.KubernetesClient/KubernetesClient.cs
index 5afc0ba6..1840e546 100644
--- a/src/KubeOps.KubernetesClient/KubernetesClient.cs
+++ b/src/KubeOps.KubernetesClient/KubernetesClient.cs
@@ -4,263 +4,259 @@
using k8s.Autorest;
using k8s.Models;
+using KubeOps.Abstractions.Entities;
using KubeOps.KubernetesClient.LabelSelectors;
namespace KubeOps.KubernetesClient;
-public class KubernetesClient : IKubernetesClient
+///
+public class KubernetesClient : IKubernetesClient
+ where TEntity : IKubernetesObject
{
private const string DownwardApiNamespaceFile = "/var/run/secrets/kubernetes.io/serviceaccount/namespace";
private const string DefaultNamespace = "default";
+ private readonly EntityMetadata _metadata;
private readonly KubernetesClientConfiguration _clientConfig;
private readonly IKubernetes _client;
-
- public KubernetesClient()
- : this(KubernetesClientConfiguration.BuildDefaultConfig())
+ private readonly GenericClient _genericClient;
+
+ ///
+ /// Create a new Kubernetes client for the given entity.
+ /// The client will use the default configuration.
+ ///
+ /// The metadata of the entity.
+ public KubernetesClient(EntityMetadata metadata)
+ : this(metadata, KubernetesClientConfiguration.BuildDefaultConfig())
{
}
- public KubernetesClient(KubernetesClientConfiguration clientConfig)
- : this(clientConfig, new Kubernetes(clientConfig))
+ ///
+ /// Create a new Kubernetes client for the given entity with a custom client configuration.
+ ///
+ /// The metadata of the entity.
+ /// The config for the underlying Kubernetes client.
+ public KubernetesClient(EntityMetadata metadata, KubernetesClientConfiguration clientConfig)
+ : this(metadata, clientConfig, new Kubernetes(clientConfig))
{
}
- public KubernetesClient(KubernetesClientConfiguration clientConfig, IKubernetes client)
+ ///
+ /// Create a new Kubernetes client for the given entity with a custom client configuration and client.
+ ///
+ /// The metadata of the entity.
+ /// The config for the underlying Kubernetes client.
+ /// The underlying client.
+ public KubernetesClient(EntityMetadata metadata, KubernetesClientConfiguration clientConfig, IKubernetes client)
{
+ _metadata = metadata;
_clientConfig = clientConfig;
_client = client;
+ _genericClient = metadata.Group switch
+ {
+ null => new GenericClient(
+ client,
+ metadata.Version,
+ metadata.PluralName),
+ _ => new GenericClient(
+ client,
+ metadata.Group,
+ metadata.Version,
+ metadata.PluralName),
+ };
}
- ///
- public IKubernetes ApiClient => _client;
-
///
public Uri BaseUri => _client.BaseUri;
///
- public Task GetCurrentNamespace(string downwardApiEnvName = "POD_NAMESPACE")
+ public async Task GetCurrentNamespace(string downwardApiEnvName = "POD_NAMESPACE")
{
- var result = DefaultNamespace;
-
- if (_clientConfig.Namespace != null)
+ if (_clientConfig.Namespace is { } configValue)
{
- result = _clientConfig.Namespace;
+ return configValue;
}
- if (Environment.GetEnvironmentVariable(downwardApiEnvName) != null)
+ if (Environment.GetEnvironmentVariable(downwardApiEnvName) is { } envValue)
{
- result = Environment.GetEnvironmentVariable(downwardApiEnvName) ?? string.Empty;
+ return envValue;
}
if (File.Exists(DownwardApiNamespaceFile))
{
- var ns = File.ReadAllText(DownwardApiNamespaceFile);
- result = ns.Trim();
+ var ns = await File.ReadAllTextAsync(DownwardApiNamespaceFile);
+ return ns.Trim();
}
- return Task.FromResult(result);
+ return DefaultNamespace;
}
///
- public Task GetServerVersion() => _client.Version.GetCodeAsync();
-
- ///
- public async Task Get(
- string name,
- string? @namespace = null)
- where TResource : class, IKubernetesObject
+ public async Task Get(string name, string? @namespace = null)
{
- try
+ var list = @namespace switch
{
- var client = CreateClient();
+ null => await _client.CustomObjects.ListClusterCustomObjectAsync>(
+ _metadata.Group ?? string.Empty,
+ _metadata.Version,
+ _metadata.PluralName),
+ _ => await _client.CustomObjects.ListNamespacedCustomObjectAsync>(
+ _metadata.Group ?? string.Empty,
+ _metadata.Version,
+ @namespace,
+ _metadata.PluralName),
+ };
- return await (string.IsNullOrWhiteSpace(@namespace)
- ? client.ReadAsync(name)
- : client.ReadNamespacedAsync(@namespace, name));
- }
- catch (HttpOperationException e) when (e.Response.StatusCode == HttpStatusCode.NotFound)
+ return list switch
{
- return null;
- }
+ { Items: [var existing] } => existing,
+ _ => default,
+ };
}
///
- public async Task> List(string? @namespace = null, string? labelSelector = null)
- where TResource : IKubernetesObject
- {
- var definition = EntityDefinition.FromType();
- var result = await (string.IsNullOrWhiteSpace(@namespace)
- ? _client.CustomObjects.ListClusterCustomObjectWithHttpMessagesAsync(
- definition.Group,
- definition.Version,
- definition.Plural,
- labelSelector: labelSelector)
- : _client.CustomObjects.ListNamespacedCustomObjectWithHttpMessagesAsync(
- definition.Group,
- definition.Version,
+ public async Task> List(string? @namespace = null, string? labelSelector = null)
+ => (@namespace switch
+ {
+ null => await _client.CustomObjects.ListClusterCustomObjectAsync>(
+ _metadata.Group ?? string.Empty,
+ _metadata.Version,
+ _metadata.PluralName,
+ labelSelector: labelSelector),
+ _ => await _client.CustomObjects.ListNamespacedCustomObjectAsync>(
+ _metadata.Group ?? string.Empty,
+ _metadata.Version,
@namespace,
- definition.Plural,
- labelSelector: labelSelector));
- var list = KubernetesJson.Deserialize>(result.Body.ToString());
- return list.Items;
- }
-
- ///
- public Task> List(
- string? @namespace = null,
- params ILabelSelector[] labelSelectors)
- where TResource : IKubernetesObject =>
- List(@namespace, labelSelectors.ToExpression());
+ _metadata.PluralName,
+ labelSelector: labelSelector),
+ }).Items;
///
- public Task Save(TResource resource)
- where TResource : class, IKubernetesObject =>
- resource.Uid() is null
- ? Create(resource)
- : Update(resource);
+ public Task> List(string? @namespace = null, params LabelSelector[] labelSelectors)
+ => List(@namespace, labelSelectors.ToExpression());
///
- public async Task Create(TResource resource)
- where TResource : IKubernetesObject
- {
- var client = CreateClient();
-
- return await (string.IsNullOrWhiteSpace(resource.Namespace())
- ? client.CreateAsync(resource)
- : client.CreateNamespacedAsync(resource, resource.Namespace()));
- }
+ public Task Create(TEntity entity)
+ => entity.Namespace() switch
+ {
+ { } ns => _genericClient.CreateNamespacedAsync(entity, ns),
+ null => _genericClient.CreateAsync(entity),
+ };
///
- public async Task Update(TResource resource)
- where TResource : IKubernetesObject
- {
- var client = CreateClient();
-
- return await (string.IsNullOrWhiteSpace(resource.Namespace())
- ? client.ReplaceAsync(resource, resource.Name())
- : client.ReplaceNamespacedAsync(resource, resource.Namespace(), resource.Name()));
- }
+ public Task Update(TEntity entity)
+ => entity.Namespace() switch
+ {
+ { } ns => _genericClient.ReplaceNamespacedAsync(entity, ns, entity.Name()),
+ null => _genericClient.ReplaceAsync(entity, entity.Name()),
+ };
///
- public async Task UpdateStatus(TResource resource)
- where TResource : IKubernetesObject
- {
- var definition = EntityDefinition.FromType();
- await (string.IsNullOrWhiteSpace(resource.Namespace())
- ? _client.CustomObjects.ReplaceClusterCustomObjectStatusAsync(
- resource,
- definition.Group,
- definition.Version,
- definition.Plural,
- resource.Name())
- : _client.CustomObjects.ReplaceNamespacedCustomObjectStatusAsync(
- resource,
- definition.Group,
- definition.Version,
- resource.Namespace(),
- definition.Plural,
- resource.Name()));
- }
+ public Task UpdateStatus(TEntity entity)
+ => entity.Namespace() switch
+ {
+ { } ns => _client.CustomObjects.ReplaceNamespacedCustomObjectStatusAsync(
+ entity,
+ _metadata.Group ?? string.Empty,
+ _metadata.Version,
+ ns,
+ _metadata.PluralName,
+ entity.Name()),
+ _ => _client.CustomObjects.ReplaceClusterCustomObjectStatusAsync(
+ entity,
+ _metadata.Group ?? string.Empty,
+ _metadata.Version,
+ _metadata.PluralName,
+ entity.Name()),
+ };
///
- public Task Delete(TResource resource)
- where TResource : IKubernetesObject => Delete(
- resource.Name(),
- resource.Namespace());
+ public Task Delete(TEntity entity) => Delete(
+ entity.Name(),
+ entity.Namespace());
///
- public Task Delete(IEnumerable resources)
- where TResource : IKubernetesObject =>
- Task.WhenAll(resources.Select(Delete));
+ public Task Delete(IEnumerable entities) =>
+ Task.WhenAll(entities.Select(Delete));
///
- public Task Delete(params TResource[] resources)
- where TResource : IKubernetesObject =>
- Task.WhenAll(resources.Select(Delete));
+ public Task Delete(params TEntity[] entities) =>
+ Task.WhenAll(entities.Select(Delete));
///
- public async Task Delete(string name, string? @namespace = null)
- where TResource : IKubernetesObject
+ public async Task Delete(string name, string? @namespace = null)
{
- var client = CreateClient();
-
try
{
- await (string.IsNullOrWhiteSpace(@namespace)
- ? client.DeleteAsync(name)
- : client.DeleteNamespacedAsync(@namespace, name));
+ switch (@namespace)
+ {
+ case not null:
+ await _genericClient.DeleteNamespacedAsync(@namespace, name);
+ break;
+ default:
+ await _genericClient.DeleteAsync(name);
+ break;
+ }
}
catch (HttpOperationException e) when (e.Response.StatusCode == HttpStatusCode.NotFound)
{
+ // The resource was not found. We can ignore this.
}
}
///
- public Task> Watch(
- TimeSpan timeout,
- Action onEvent,
+ public Watcher Watch(
+ Action onEvent,
Action? onError = null,
Action? onClose = null,
string? @namespace = null,
+ TimeSpan? timeout = null,
CancellationToken cancellationToken = default,
- params ILabelSelector[] labelSelectors)
- where TResource : IKubernetesObject
+ params LabelSelector[] labelSelectors)
=> Watch(
- timeout,
onEvent,
onError,
onClose,
@namespace,
- cancellationToken,
- string.Join(",", labelSelectors.Select(l => l.ToExpression())));
+ timeout,
+ labelSelectors.ToExpression(),
+ cancellationToken);
///
- public Task> Watch(
- TimeSpan timeout,
- Action onEvent,
+ public Watcher Watch(
+ Action onEvent,
Action? onError = null,
Action? onClose = null,
string? @namespace = null,
- CancellationToken cancellationToken = default,
- string? labelSelector = null)
- where TResource : IKubernetesObject
- {
- var crd = EntityDefinition.FromType();
- var result = string.IsNullOrWhiteSpace(@namespace)
- ? _client.CustomObjects.ListClusterCustomObjectWithHttpMessagesAsync(
- crd.Group,
- crd.Version,
- crd.Plural,
+ TimeSpan? timeout = null,
+ string? labelSelector = null,
+ CancellationToken cancellationToken = default)
+ => (@namespace switch
+ {
+ not null => _client.CustomObjects.ListNamespacedCustomObjectWithHttpMessagesAsync(
+ _metadata.Group ?? string.Empty,
+ _metadata.Version,
+ @namespace,
+ _metadata.PluralName,
labelSelector: labelSelector,
- timeoutSeconds: (int)timeout.TotalSeconds,
+ timeoutSeconds: timeout switch
+ {
+ null => null,
+ _ => (int?)timeout.Value.TotalSeconds,
+ },
watch: true,
- cancellationToken: cancellationToken)
- : _client.CustomObjects.ListNamespacedCustomObjectWithHttpMessagesAsync(
- crd.Group,
- crd.Version,
- @namespace,
- crd.Plural,
+ cancellationToken: cancellationToken),
+ _ => _client.CustomObjects.ListClusterCustomObjectWithHttpMessagesAsync(
+ _metadata.Group ?? string.Empty,
+ _metadata.Version,
+ _metadata.PluralName,
labelSelector: labelSelector,
- timeoutSeconds: (int)timeout.TotalSeconds,
+ timeoutSeconds: timeout switch
+ {
+ null => null,
+ _ => (int?)timeout.Value.TotalSeconds,
+ },
watch: true,
- cancellationToken: cancellationToken);
-
- return Task.FromResult(
- result.Watch(
- onEvent,
- onError,
- onClose));
- }
-
- private GenericClient CreateClient()
- where TResource : IKubernetesObject
- {
- var definition = EntityDefinition.FromType();
- return definition.Group switch
- {
- "" => new GenericClient(_client, definition.Version, definition.Plural),
- _ => new GenericClient(_client, definition.Group, definition.Version, definition.Plural),
- };
- }
+ cancellationToken: cancellationToken),
+ }).Watch(onEvent, onError, onClose);
}
diff --git a/src/KubeOps.KubernetesClient/LabelSelectors/EqualsSelector.cs b/src/KubeOps.KubernetesClient/LabelSelectors/EqualsSelector.cs
index ca6aacae..0e313b2f 100644
--- a/src/KubeOps.KubernetesClient/LabelSelectors/EqualsSelector.cs
+++ b/src/KubeOps.KubernetesClient/LabelSelectors/EqualsSelector.cs
@@ -5,13 +5,9 @@
/// a specific value (out of a list of values).
/// Note that "label in (value)" is the same as "label == value".
///
-public record EqualsSelector : ILabelSelector
+/// The label that needs to equal to one of the values.
+/// The possible values.
+public record EqualsSelector(string Label, params string[] Values) : LabelSelector
{
- public EqualsSelector(string label, params string[] values) => (Label, Values) = (label, values);
-
- public string Label { get; }
-
- public IEnumerable Values { get; }
-
- public string ToExpression() => $"{Label} in ({string.Join(",", Values)})";
+ protected override string ToExpression() => $"{Label} in ({string.Join(",", Values)})";
}
diff --git a/src/KubeOps.KubernetesClient/LabelSelectors/ExistsSelector.cs b/src/KubeOps.KubernetesClient/LabelSelectors/ExistsSelector.cs
index 4f7c8305..ed40e73c 100644
--- a/src/KubeOps.KubernetesClient/LabelSelectors/ExistsSelector.cs
+++ b/src/KubeOps.KubernetesClient/LabelSelectors/ExistsSelector.cs
@@ -3,7 +3,8 @@
///
/// Selector that checks if a certain label exists.
///
-public record ExistsSelector(string Label) : ILabelSelector
+/// The label that needs to exist on the entity/resource.
+public record ExistsSelector(string Label) : LabelSelector
{
- public string ToExpression() => $"{Label}";
+ protected override string ToExpression() => $"{Label}";
}
diff --git a/src/KubeOps.KubernetesClient/LabelSelectors/Extensions.cs b/src/KubeOps.KubernetesClient/LabelSelectors/Extensions.cs
index a9689914..f22e7b63 100644
--- a/src/KubeOps.KubernetesClient/LabelSelectors/Extensions.cs
+++ b/src/KubeOps.KubernetesClient/LabelSelectors/Extensions.cs
@@ -3,10 +3,10 @@
public static class Extensions
{
///
- /// Convert an enumerable list of s to a string.
+ /// Convert an enumerable list of s to a string.
///
/// The list of selectors.
/// A comma-joined string with all selectors converted to their expressions.
- public static string ToExpression(this IEnumerable selectors) =>
- string.Join(",", selectors.Select(s => s.ToExpression()));
+ public static string ToExpression(this IEnumerable selectors) =>
+ string.Join(",", selectors);
}
diff --git a/src/KubeOps.KubernetesClient/LabelSelectors/ILabelSelector.cs b/src/KubeOps.KubernetesClient/LabelSelectors/ILabelSelector.cs
deleted file mode 100644
index e5524824..00000000
--- a/src/KubeOps.KubernetesClient/LabelSelectors/ILabelSelector.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-namespace KubeOps.KubernetesClient.LabelSelectors;
-
-///
-/// Different label selectors for querying the Kubernetes API.
-///
-public interface ILabelSelector
-{
- ///
- /// Create an expression from the label selector.
- ///
- /// A string that represents the label selector.
- string ToExpression();
-}
diff --git a/src/KubeOps.KubernetesClient/LabelSelectors/LabelSelector.cs b/src/KubeOps.KubernetesClient/LabelSelectors/LabelSelector.cs
new file mode 100644
index 00000000..543b1c03
--- /dev/null
+++ b/src/KubeOps.KubernetesClient/LabelSelectors/LabelSelector.cs
@@ -0,0 +1,20 @@
+namespace KubeOps.KubernetesClient.LabelSelectors;
+
+///
+/// Different label selectors for querying the Kubernetes API.
+///
+public abstract record LabelSelector
+{
+ ///
+ /// Cast the label selector to a string.
+ ///
+ /// The selector.
+ /// A string representation of the label selector.
+ public static implicit operator string(LabelSelector selector) => selector.ToExpression();
+
+ ///
+ /// Create an expression from the label selector.
+ ///
+ /// A string that represents the label selector.
+ protected abstract string ToExpression();
+}
diff --git a/src/KubeOps.KubernetesClient/LabelSelectors/NotEqualsSelector.cs b/src/KubeOps.KubernetesClient/LabelSelectors/NotEqualsSelector.cs
index b78505aa..32a68d09 100644
--- a/src/KubeOps.KubernetesClient/LabelSelectors/NotEqualsSelector.cs
+++ b/src/KubeOps.KubernetesClient/LabelSelectors/NotEqualsSelector.cs
@@ -5,13 +5,9 @@
/// a specific value (out of a list of values).
/// Note that "label notin (value)" is the same as "label != value".
///
-public record NotEqualsSelector : ILabelSelector
+/// The label that must not equal to one of the values.
+/// The possible values.
+public record NotEqualsSelector(string Label, params string[] Values) : LabelSelector
{
- public NotEqualsSelector(string label, params string[] values) => (Label, Values) = (label, values);
-
- public string Label { get; }
-
- public IEnumerable Values { get; }
-
- public string ToExpression() => $"{Label} notin ({string.Join(",", Values)})";
+ protected override string ToExpression() => $"{Label} notin ({string.Join(",", Values)})";
}
diff --git a/src/KubeOps.KubernetesClient/LabelSelectors/NotExistsSelector.cs b/src/KubeOps.KubernetesClient/LabelSelectors/NotExistsSelector.cs
index a047f91a..52db320a 100644
--- a/src/KubeOps.KubernetesClient/LabelSelectors/NotExistsSelector.cs
+++ b/src/KubeOps.KubernetesClient/LabelSelectors/NotExistsSelector.cs
@@ -3,7 +3,8 @@
///
/// Selector that checks if a certain label does not exist.
///
-public record NotExistsSelector(string Label) : ILabelSelector
+/// The label that must not exist on the entity/resource.
+public record NotExistsSelector(string Label) : LabelSelector
{
- public string ToExpression() => $"!{Label}";
+ protected override string ToExpression() => $"!{Label}";
}
diff --git a/src/KubeOps.Operator/Builder/OperatorBuilder.cs b/src/KubeOps.Operator/Builder/OperatorBuilder.cs
index f82bbd75..624524d4 100644
--- a/src/KubeOps.Operator/Builder/OperatorBuilder.cs
+++ b/src/KubeOps.Operator/Builder/OperatorBuilder.cs
@@ -4,7 +4,7 @@
using KubeOps.Abstractions.Builder;
using KubeOps.Abstractions.Controller;
using KubeOps.Abstractions.Entities;
-using KubeOps.Operator.Client;
+using KubeOps.KubernetesClient;
using KubeOps.Operator.Watcher;
using Microsoft.Extensions.DependencyInjection;
@@ -13,20 +13,17 @@ namespace KubeOps.Operator.Builder;
internal class OperatorBuilder : IOperatorBuilder
{
- private readonly IKubernetesClientFactory _entityClientFactory = new KubernetesClientFactory();
-
public OperatorBuilder(IServiceCollection services)
{
Services = services;
- Services.AddSingleton(_entityClientFactory);
}
public IServiceCollection Services { get; }
- public IOperatorBuilder AddEntityMetadata(EntityMetadata metadata)
+ public IOperatorBuilder AddEntity(EntityMetadata metadata)
where TEntity : IKubernetesObject
{
- _entityClientFactory.RegisterMetadata(metadata);
+ Services.AddSingleton>(new KubernetesClient(metadata));
return this;
}
@@ -39,8 +36,8 @@ public IOperatorBuilder AddController()
return this;
}
- public IOperatorBuilder AddController(EntityMetadata metadata)
+ public IOperatorBuilder AddControllerWithEntity(EntityMetadata metadata)
where TImplementation : class, IEntityController
where TEntity : IKubernetesObject =>
- AddController().AddEntityMetadata(metadata);
+ AddController().AddEntity(metadata);
}
diff --git a/src/KubeOps.Operator/Client/IKubernetesClientFactory.cs b/src/KubeOps.Operator/Client/IKubernetesClientFactory.cs
deleted file mode 100644
index d47399ac..00000000
--- a/src/KubeOps.Operator/Client/IKubernetesClientFactory.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.Collections.Immutable;
-
-using k8s;
-using k8s.Models;
-
-using KubeOps.Abstractions.Entities;
-
-namespace KubeOps.Operator.Client;
-
-public interface IKubernetesClientFactory
-{
- IImmutableDictionary RegisteredMetadata { get; }
-
- void RegisterMetadata(EntityMetadata metadata)
- where TEntity : IKubernetesObject;
-
- GenericClient GetClient(IKubernetes? kubernetes = null)
- where TEntity : IKubernetesObject;
-}
diff --git a/src/KubeOps.Operator/Client/KubernetesClientFactory.cs b/src/KubeOps.Operator/Client/KubernetesClientFactory.cs
deleted file mode 100644
index d049436c..00000000
--- a/src/KubeOps.Operator/Client/KubernetesClientFactory.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System.Collections.Immutable;
-
-using k8s;
-using k8s.Models;
-
-using KubeOps.Abstractions.Entities;
-
-namespace KubeOps.Operator.Client;
-
-internal class KubernetesClientFactory : IKubernetesClientFactory
-{
- private readonly Dictionary _registeredMetadata = new();
-
- public IImmutableDictionary RegisteredMetadata => _registeredMetadata.ToImmutableDictionary();
-
- public void RegisterMetadata(EntityMetadata metadata)
- where TEntity : IKubernetesObject
- {
- _registeredMetadata[typeof(TEntity)] = metadata;
- }
-
- public GenericClient GetClient(IKubernetes? kubernetes = null)
- where TEntity : IKubernetesObject
- {
- if (!_registeredMetadata.TryGetValue(typeof(TEntity), out var metadata))
- {
- throw new InvalidOperationException(
- $"No metadata registered for entity {typeof(TEntity).Name}. " +
- "Please register metadata for this entity before using the client.");
- }
-
- return metadata.Group switch
- {
- null => new GenericClient(
- kubernetes ?? new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig()),
- metadata.Version,
- metadata.PluralName),
- _ => new GenericClient(
- kubernetes ?? new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig()),
- metadata.Group,
- metadata.Version,
- metadata.PluralName),
- };
- }
-}
diff --git a/src/KubeOps.Operator/KubeOps.Operator.csproj b/src/KubeOps.Operator/KubeOps.Operator.csproj
index b64a8ffe..f099d2ea 100644
--- a/src/KubeOps.Operator/KubeOps.Operator.csproj
+++ b/src/KubeOps.Operator/KubeOps.Operator.csproj
@@ -22,6 +22,7 @@
+
diff --git a/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs b/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs
index bef1c028..fbc5ab7d 100644
--- a/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs
+++ b/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs
@@ -5,7 +5,7 @@
using k8s.Models;
using KubeOps.Abstractions.Controller;
-using KubeOps.Operator.Client;
+using KubeOps.KubernetesClient;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -18,18 +18,18 @@ internal class ResourceWatcher : IHostedService
{
private readonly ILogger> _logger;
private readonly IServiceProvider _provider;
- private readonly GenericClient _client;
+ private readonly IKubernetesClient _client;
private Watcher? _watcher;
public ResourceWatcher(
ILogger> logger,
IServiceProvider provider,
- IKubernetesClientFactory factory)
+ IKubernetesClient client)
{
_logger = logger;
_provider = provider;
- _client = factory.GetClient();
+ _client = client;
}
public Task StartAsync(CancellationToken cancellationToken)
@@ -61,7 +61,7 @@ private void WatchResource()
}
}
- _watcher = _client.Watch(OnEvent, OnError, OnClosed);
+ _watcher = _client.Watch(OnEvent, OnError, OnClosed);
}
private void StopWatching()
diff --git a/test/KubeOps.KubernetesClient.Test/GlobalUsings.cs b/test/KubeOps.KubernetesClient.Test/GlobalUsings.cs
new file mode 100644
index 00000000..c802f448
--- /dev/null
+++ b/test/KubeOps.KubernetesClient.Test/GlobalUsings.cs
@@ -0,0 +1 @@
+global using Xunit;
diff --git a/test/KubeOps.KubernetesClient.Test/KubeOps.KubernetesClient.Test.csproj b/test/KubeOps.KubernetesClient.Test/KubeOps.KubernetesClient.Test.csproj
new file mode 100644
index 00000000..1104f6dd
--- /dev/null
+++ b/test/KubeOps.KubernetesClient.Test/KubeOps.KubernetesClient.Test.csproj
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/test/KubeOps.KubernetesClient.Test/KubernetesClient.Test.cs b/test/KubeOps.KubernetesClient.Test/KubernetesClient.Test.cs
new file mode 100644
index 00000000..013db68b
--- /dev/null
+++ b/test/KubeOps.KubernetesClient.Test/KubernetesClient.Test.cs
@@ -0,0 +1,138 @@
+using FluentAssertions;
+
+using k8s.Models;
+
+namespace KubeOps.KubernetesClient.Test;
+
+public class KubernetesClientTest : IDisposable
+{
+ private readonly IKubernetesClient _client =
+ new KubernetesClient(new("ConfigMap", "v1", null, "configmaps"));
+
+ private readonly IList _objects = new List();
+
+ [Fact]
+ public async Task Should_Return_Namespace()
+ {
+ var ns = await _client.GetCurrentNamespace();
+ ns.Should().Be("default");
+ }
+
+ [Fact]
+ public async Task Should_Create_Some_Object()
+ {
+ var config = await _client.Create(
+ new V1ConfigMap
+ {
+ Kind = V1ConfigMap.KubeKind,
+ ApiVersion = V1ConfigMap.KubeApiVersion,
+ Metadata = new(name: RandomName(), namespaceProperty: "default"),
+ Data = new Dictionary { { "Hello", "World" } },
+ });
+
+ _objects.Add(config);
+
+ config.Metadata.Should().NotBeNull();
+ config.Metadata.ResourceVersion.Should().NotBeNullOrWhiteSpace();
+ }
+
+ [Fact]
+ public async Task Should_Update_Some_Object()
+ {
+ var config = await _client.Create(
+ new V1ConfigMap
+ {
+ Kind = V1ConfigMap.KubeKind,
+ ApiVersion = V1ConfigMap.KubeApiVersion,
+ Metadata = new V1ObjectMeta(name: RandomName(), namespaceProperty: "default"),
+ Data = new Dictionary { { "Hello", "World" } },
+ });
+ var r1 = config.Metadata.ResourceVersion;
+ _objects.Add(config);
+
+ config.Data.Add("test", "value");
+ config = await _client.Update(config);
+ var r2 = config.Metadata.ResourceVersion;
+
+ r1.Should().NotBe(r2);
+ }
+
+ [Fact]
+ public async Task Should_List_Some_Objects()
+ {
+ var config1 = await _client.Create(
+ new V1ConfigMap
+ {
+ Kind = V1ConfigMap.KubeKind,
+ ApiVersion = V1ConfigMap.KubeApiVersion,
+ Metadata = new(name: RandomName(), namespaceProperty: "default"),
+ Data = new Dictionary { { "Hello", "World" } },
+ });
+ var config2 = await _client.Create(
+ new V1ConfigMap
+ {
+ Kind = V1ConfigMap.KubeKind,
+ ApiVersion = V1ConfigMap.KubeApiVersion,
+ Metadata = new(name: RandomName(), namespaceProperty: "default"),
+ Data = new Dictionary { { "Hello", "World" } },
+ });
+
+ _objects.Add(config1);
+ _objects.Add(config2);
+
+ var configs = await _client.List("default");
+
+ // there are _at least_ 2 config maps (the two that were created)
+ configs.Count.Should().BeGreaterOrEqualTo(2);
+ }
+
+ [Fact]
+ public async Task Should_Delete_Some_Object()
+ {
+ var config1 = await _client.Create(
+ new V1ConfigMap
+ {
+ Kind = V1ConfigMap.KubeKind,
+ ApiVersion = V1ConfigMap.KubeApiVersion,
+ Metadata = new(name: RandomName(), namespaceProperty: "default"),
+ Data = new Dictionary { { "Hello", "World" } },
+ });
+ var config2 = await _client.Create(
+ new V1ConfigMap
+ {
+ Kind = V1ConfigMap.KubeKind,
+ ApiVersion = V1ConfigMap.KubeApiVersion,
+ Metadata = new(name: RandomName(), namespaceProperty: "default"),
+ Data = new Dictionary { { "Hello", "World" } },
+ });
+ _objects.Add(config1);
+
+ var configs = await _client.List("default");
+ configs.Count.Should().BeGreaterOrEqualTo(2);
+
+ await _client.Delete(config2);
+
+ configs = await _client.List("default");
+ configs.Count.Should().BeGreaterOrEqualTo(1);
+ }
+
+ [Fact]
+ public async Task Should_Not_Throw_On_Not_Found_Delete()
+ {
+ var config = new V1ConfigMap
+ {
+ Kind = V1ConfigMap.KubeKind,
+ ApiVersion = V1ConfigMap.KubeApiVersion,
+ Metadata = new(name: RandomName(), namespaceProperty: "default"),
+ Data = new Dictionary { { "Hello", "World" } },
+ };
+ await _client.Delete(config);
+ }
+
+ public void Dispose()
+ {
+ _client.Delete(_objects).Wait();
+ }
+
+ private static string RandomName() => "cm-" + Guid.NewGuid().ToString().ToLower();
+}