Skip to content

Commit

Permalink
feat(client): Add Kubernetes Client package
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The IKubernetesClient interface
and implementation now require the TEntity typeparam
instead of each method providing one. The implementation
is instanced with EntityMetadata to allow the operator
to inject the clients for each entity.
  • Loading branch information
buehler committed Sep 29, 2023
1 parent bcd1f52 commit 42bc8ee
Show file tree
Hide file tree
Showing 24 changed files with 523 additions and 396 deletions.
14 changes: 14 additions & 0 deletions KubeOps.sln
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
6 changes: 3 additions & 3 deletions src/KubeOps.Abstractions/Builder/IOperatorBuilder.cs
Expand Up @@ -19,14 +19,14 @@ public interface IOperatorBuilder
IServiceCollection Services { get; }

/// <summary>
/// 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.
/// </summary>
/// <param name="metadata">The metadata of the entity.</param>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <returns>The builder for chaining.</returns>
IOperatorBuilder AddEntityMetadata<TEntity>(EntityMetadata metadata)
IOperatorBuilder AddEntity<TEntity>(EntityMetadata metadata)
where TEntity : IKubernetesObject<V1ObjectMeta>;

/// <summary>
Expand All @@ -48,7 +48,7 @@ IOperatorBuilder AddEntityMetadata<TEntity>(EntityMetadata metadata)
/// <typeparam name="TImplementation">Implementation type of the controller.</typeparam>
/// <typeparam name="TEntity">Entity type.</typeparam>
/// <returns>The builder for chaining.</returns>
IOperatorBuilder AddController<TImplementation, TEntity>(EntityMetadata metadata)
IOperatorBuilder AddControllerWithEntity<TImplementation, TEntity>(EntityMetadata metadata)
where TImplementation : class, IEntityController<TEntity>
where TEntity : IKubernetesObject<V1ObjectMeta>;
}
22 changes: 22 additions & 0 deletions src/KubeOps.Abstractions/Entities/EntityList.cs
@@ -0,0 +1,22 @@
using k8s;
using k8s.Models;

namespace KubeOps.Abstractions.Entities;

/// <summary>
/// Type for a list of entities.
/// </summary>
/// <typeparam name="T">Type for the list entries.</typeparam>
public class EntityList<T> : KubernetesObject
where T : IKubernetesObject
{
/// <summary>
/// Official list metadata object of kubernetes.
/// </summary>
public V1ListMeta Metadata { get; set; } = new();

/// <summary>
/// The list of items.
/// </summary>
public IList<T> Items { get; set; } = new List<T>();
}
42 changes: 42 additions & 0 deletions src/KubeOps.Abstractions/Entities/Extensions.cs
@@ -0,0 +1,42 @@
using k8s;
using k8s.Models;

namespace KubeOps.Abstractions.Entities;

/// <summary>
/// Method extensions for <see cref="IKubernetesObject{TMetadata}"/>.
/// </summary>
public static class Extensions
{
/// <summary>
/// Sets the resource version of the specified Kubernetes object to the specified value.
/// </summary>
/// <typeparam name="TEntity">The type of the Kubernetes object.</typeparam>
/// <param name="entity">The Kubernetes object.</param>
/// <param name="resourceVersion">The resource version to set.</param>
/// <returns>The Kubernetes object with the updated resource version.</returns>
public static TEntity WithResourceVersion<TEntity>(
this TEntity entity,
string resourceVersion)
where TEntity : IKubernetesObject<V1ObjectMeta>
{
entity.EnsureMetadata().ResourceVersion = resourceVersion;
return entity;
}

/// <summary>
/// Sets the resource version of the specified Kubernetes object to the resource version of another object.
/// </summary>
/// <typeparam name="TEntity">The type of the Kubernetes object.</typeparam>
/// <param name="entity">The Kubernetes object.</param>
/// <param name="other">The other Kubernetes object.</param>
/// <returns>The Kubernetes object with the updated resource version.</returns>
public static TEntity WithResourceVersion<TEntity>(
this TEntity entity,
TEntity other)
where TEntity : IKubernetesObject<V1ObjectMeta>
{
entity.EnsureMetadata().ResourceVersion = other.ResourceVersion();
return entity;
}
}
@@ -1,3 +1,5 @@
using KubeOps.Generator.SyntaxReceiver;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -93,7 +95,7 @@ public void Execute(GeneratorExecutionContext context)
MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName("builder"),
GenericName(Identifier("AddEntityMetadata"))
GenericName(Identifier("AddEntity"))
.WithTypeArgumentList(
TypeArgumentList(
SingletonSeparatedList<TypeSyntax>(
Expand Down
@@ -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);
@@ -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<AttributedEntity> 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<AttributedEntity> 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;
}

0 comments on commit 42bc8ee

Please sign in to comment.