Skip to content

Commit

Permalink
feat: initial version of the dotnet operator sdk (DOS)
Browse files Browse the repository at this point in the history
  • Loading branch information
buehler committed May 7, 2020
0 parents commit c34676c
Show file tree
Hide file tree
Showing 56 changed files with 3,082 additions and 0 deletions.
29 changes: 29 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates

# Ci things
node_modules/
tools/
nuget/
artifacts/
coverage/
.tmp/

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/

# Testing results
coverage.json
coverage.info
23 changes: 23 additions & 0 deletions DotnetOperatorSdk.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{95F3A6DD-B421-441D-B263-1B34A1465FF5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dos", "src\Dos\Dos.csproj", "{D7AB6CB9-94B6-4FEB-B7D8-D8AA793BD2A4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{50E9B964-68F7-4B9F-BEA8-165CE45BC5C6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D7AB6CB9-94B6-4FEB-B7D8-D8AA793BD2A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7AB6CB9-94B6-4FEB-B7D8-D8AA793BD2A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7AB6CB9-94B6-4FEB-B7D8-D8AA793BD2A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7AB6CB9-94B6-4FEB-B7D8-D8AA793BD2A4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D7AB6CB9-94B6-4FEB-B7D8-D8AA793BD2A4} = {95F3A6DD-B421-441D-B263-1B34A1465FF5}
EndGlobalSection
EndGlobal
39 changes: 39 additions & 0 deletions src/Dos/Build/DotnetOperatorSdk.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<Project>
<PropertyGroup>
<!-- Configuration for Docker related commands-->
<DosDockerfilePath Condition="'$(DosDockerfilePath)' == '' And '$(SolutionDir)' != '' ">$(SolutionDir)Dockerfile</DosDockerfilePath>
<DosDockerfilePath Condition="'$(DosDockerfilePath)' == '' And '$(SolutionDir)' == '' ">$(MSBuildProjectDirectory)\Dockerfile</DosDockerfilePath>
<DosDockerTag Condition="'$(DosDockerTag)' == ''">latest</DosDockerTag>
</PropertyGroup>

<PropertyGroup>
<!-- Configuration for the pathes where to store the generated yamls and elements -->
<DosConfigRoot Condition="'$(DosConfigRoot)' == '' And '$(SolutionDir)' != '' ">$(SolutionDir)config</DosConfigRoot>
<DosConfigRoot Condition="'$(DosConfigRoot)' == '' And '$(SolutionDir)' == '' ">$(MSBuildProjectDirectory)\config</DosConfigRoot>
</PropertyGroup>

<PropertyGroup>
<!-- Configuration for the crd generation -->
<DosCrdDir Condition="'$(DosCrdDir)' == ''">$(DosConfigRoot)\crds</DosCrdDir>
<DosCrdFormat Condition="'$(DosCrdFormat)' == ''">Yaml</DosCrdFormat>
<DosCrdUseOldCrds Condition="'$(DosCrdUseOldCrds)' == ''">false</DosCrdUseOldCrds>
</PropertyGroup>

<PropertyGroup>
<!-- Configuration for the rbac generation -->
<DosRbacDir Condition="'$(DosRbacDir)' == ''">$(DosConfigRoot)\rbac</DosRbacDir>
<DosRbacFormat Condition="'$(DosRbacFormat)' == ''">Yaml</DosRbacFormat>
</PropertyGroup>

<PropertyGroup>
<!-- Configuration for the operator manifest generation -->
<DosOperatorDir Condition="'$(DosOperatorDir)' == ''">$(DosConfigRoot)\operator</DosOperatorDir>
<DosOperatorFormat Condition="'$(DosOperatorFormat)' == ''">Yaml</DosOperatorFormat>
</PropertyGroup>

<PropertyGroup>
<!-- Configuration for the installer manifest generation -->
<DosInstallerDir Condition="'$(DosInstallerDir)' == ''">$(DosConfigRoot)\install</DosInstallerDir>
<DosInstallerFormat Condition="'$(DosInstallerFormat)' == ''">Yaml</DosInstallerFormat>
</PropertyGroup>
</Project>
49 changes: 49 additions & 0 deletions src/Dos/Build/DotnetOperatorSdk.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<Project DefaultTargets="GenerateAfterBuild">
<Target Name="GenerateDockerfile">
<Message Text="Generating Dockerfile" Importance="high"/>
<Message Text="Dockerfile path: $(DosDockerfilePath)" Importance="normal"/>

<Message Condition="Exists('$(DosDockerfilePath)')" Text="Dockerfile already exists. Don't overwrite." Importance="high"/>
<Exec Condition="!Exists('$(DosDockerfilePath)')"
Command="dotnet $(OutputPath)$(TargetFileName) generator docker --out $(DosDockerfilePath) --dotnet-tag $(DosDockerTag) --solution-dir $(SolutionDir) --target-file $(TargetFileName) --project-path $(ProjectPath)"/>
</Target>

<Target Name="GenerateCrds">
<Message Text="Generating CRDs" Importance="high"/>
<Message Text="Configuration path: $(DosCrdDir)" Importance="normal"/>

<Exec Condition="'$(DosCrdUseOldCrds)' == 'false'" Command="dotnet $(OutputPath)$(TargetFileName) generator crds --out $(DosCrdDir) --format $(DosCrdFormat)"/>
<Exec Condition="'$(DosCrdUseOldCrds)' == 'true'" Command="dotnet $(OutputPath)$(TargetFileName) generator crds --out $(DosCrdDir) --format $(DosCrdFormat) --use-old-crds"/>
</Target>

<Target Name="GenerateRbac">
<Message Text="Generating Rbac roles" Importance="high"/>
<Message Text="Configuration path: $(DosRbacDir)" Importance="normal"/>

<Exec Command="dotnet $(OutputPath)$(TargetFileName) generator rbac --out $(DosRbacDir) --format $(DosRbacFormat)"/>
</Target>

<Target Name="GenerateOperator">
<Message Text="Generating Operator yamls" Importance="high"/>
<Message Text="Configuration path: $(DosOperatorDir)" Importance="normal"/>

<Exec Command="dotnet $(OutputPath)$(TargetFileName) generator operator --out $(DosOperatorDir) --format $(DosOperatorFormat)"/>
</Target>

<Target Name="GenerateInstaller">
<Message Text="Generating Installer yamls" Importance="high"/>
<Message Text="Configuration path: $(DosInstallerDir)" Importance="normal"/>

<Message Condition="Exists('$(DosInstallerDir)')" Text="Installer dir exists, don't overwrite contents." Importance="high"/>
<Exec Condition="!Exists('$(DosInstallerDir)')"
Command="dotnet $(OutputPath)$(TargetFileName) generator installer --out $(DosInstallerDir) --format $(DosInstallerFormat) --crds-dir $(DosCrdDir) --rbac-dir $(DosRbacDir) --operator-dir $(DosOperatorDir)"/>
</Target>

<Target Name="GenerateAfterBuild" AfterTargets="Build">
<CallTarget Condition="'$(DosSkipDockerfile)' == ''" Targets="GenerateDockerfile"/>
<CallTarget Condition="'$(DosSkipCrds)' == ''" Targets="GenerateCrds"/>
<CallTarget Condition="'$(DosSkipRbac)' == ''" Targets="GenerateRbac"/>
<CallTarget Condition="'$(DosSkipOperator)' == ''" Targets="GenerateOperator"/>
<CallTarget Condition="'$(DosSkipInstaller)' == ''" Targets="GenerateInstaller"/>
</Target>
</Project>
10 changes: 10 additions & 0 deletions src/Dos/Build/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Dos.Operator;

namespace Dos.Build
{
internal static class Program
{
public static Task Main(string[] args) => new KubernetesOperator().Run(args);
}
}
33 changes: 33 additions & 0 deletions src/Dos/Dos.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<OutputType>exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>8</LangVersion>
</PropertyGroup>

<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageId>DotnetOperatorSdk</PackageId>
<PackageTags>Kubernetes Operator SDK CustomResourceDefinition</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CompareNETObjects" Version="4.66.0" />
<PackageReference Include="KubernetesClient" Version="2.0.18" />
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="3.0.0" />
<PackageReference Include="McMaster.Extensions.Hosting.CommandLine" Version="3.0.0" />
<PackageReference Include="YamlDotNet" Version="8.1.1" />
</ItemGroup>

<ItemGroup>
<Content Include="Build\DotnetOperatorSdk.props">
<PackagePath>build\</PackagePath>
</Content>
<Content Include="Build\DotnetOperatorSdk.targets">
<PackagePath>build\</PackagePath>
</Content>
</ItemGroup>

</Project>
11 changes: 11 additions & 0 deletions src/Dos/Operator/Caching/CacheComparisonResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Dos.Operator.Caching
{
internal enum CacheComparisonResult
{
New,
Modified,
StatusModified,
FinalizersModified,
NotModified,
}
}
82 changes: 82 additions & 0 deletions src/Dos/Operator/Caching/EntityCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System.Collections.Generic;
using System.Linq;
using Dos.Operator.Entities;
using k8s;
using k8s.Models;
using KellermanSoftware.CompareNetObjects;

namespace Dos.Operator.Caching
{
internal class EntityCache<TEntity>
where TEntity : IKubernetesObject<V1ObjectMeta>
{
private const string ResourceVersion = "ResourceVersion";
private const string Finalizers = "Metadata.Finalizers";
private const string Status = "Status";

private readonly CompareLogic _compare = new CompareLogic(
new ComparisonConfig
{
Caching = true,
AutoClearCache = false,
MembersToIgnore = new List<string> { ResourceVersion },
});

private readonly IDictionary<string, TEntity> _cache = new Dictionary<string, TEntity>();

public TEntity Get(string id) => _cache[id];

public TEntity Upsert(TEntity resource, out CacheComparisonResult result)
{
result = CompareCache(resource);

if (result == CacheComparisonResult.New)
{
_cache.Add(resource.Metadata.Uid, resource.DeepClone());
}
else
{
_cache[resource.Metadata.Uid] = resource.DeepClone();
}

return resource;
}

private CacheComparisonResult CompareCache(TEntity resource)
{
if (!Exists(resource))
{
return CacheComparisonResult.New;
}

var cacheObject = _cache[resource.Metadata.Uid];
var comparison = _compare.Compare(resource, cacheObject);
if (comparison.AreEqual)
{
return CacheComparisonResult.NotModified;
}

if (comparison.Differences.All(d => d.ParentPropertyName == Status))
{
return CacheComparisonResult.StatusModified;
}

if (comparison.Differences.All(d => d.PropertyName == Finalizers))
{
return CacheComparisonResult.FinalizersModified;
}

return CacheComparisonResult.Modified;
}

public void Remove(TEntity resource) => Remove(resource.Metadata.Uid);

public void Clear() => _cache.Clear();

private bool Exists(TEntity resource) => _cache.ContainsKey(resource.Metadata.Uid);

private bool Exists(string id) => _cache.ContainsKey(id);

private void Remove(string resourceUid) => _cache.Remove(resourceUid);
}
}
27 changes: 27 additions & 0 deletions src/Dos/Operator/Client/ClientUrlFixer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Dos.Operator.Client
{
internal class ClientUrlFixer : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.RequestUri.Segments.Count(s => s == "/") > 1)
{
// This request uri contains "empty" segments. (i.e. https://.../apis//v1/...)
// This means, this is a default api group (/api/v1)
var builder = new UriBuilder(request.RequestUri);
builder.Path = builder.Path.Replace("//", "/").Replace("apis", "api");
request.RequestUri = builder.Uri;
}

return await base.SendAsync(request, cancellationToken);
}
}
}
66 changes: 66 additions & 0 deletions src/Dos/Operator/Client/IKubernetesClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Dos.Operator.Client.LabelSelectors;
using k8s;
using k8s.Models;

namespace Dos.Operator.Client
{
public interface IKubernetesClient
{
IKubernetes ApiClient { get; }

Task<TResource?> Get<TResource>(string name, string? @namespace = null)
where TResource : class, IKubernetesObject<V1ObjectMeta>;

// Task<TResource?> Get<TResource>(string name, string? @namespace = null, TResource? requireClass = null)
// where TResource : class, IKubernetesObject<V1ObjectMeta>;

// Task<TResource> Get<TResource>(string name, string? @namespace = null, TResource? requireStruct = null)
// where TResource : struct, IKubernetesObject<V1ObjectMeta>;

Task<IList<TResource>> List<TResource>(
string? @namespace = null,
string? labelSelector = null)
where TResource : IKubernetesObject<V1ObjectMeta>;

Task<IList<TResource>> List<TResource>(
string? @namespace = null,
params ILabelSelector[] labelSelectors)
where TResource : IKubernetesObject<V1ObjectMeta>;

Task<TResource> Save<TResource>(TResource resource)
where TResource : class, IKubernetesObject<V1ObjectMeta>;

Task<TResource> Create<TResource>(TResource resource)
where TResource : IKubernetesObject<V1ObjectMeta>;

Task<TResource> Update<TResource>(TResource resource)
where TResource : IKubernetesObject<V1ObjectMeta>;

public Task UpdateStatus<TStatus>(IStatus<TStatus> resource);

Task Delete<TResource>(TResource resource)
where TResource : IKubernetesObject<V1ObjectMeta>;

Task Delete<TResource>(IEnumerable<TResource> resources)
where TResource : IKubernetesObject<V1ObjectMeta>;

Task Delete<TResource>(params TResource[] resources)
where TResource : IKubernetesObject<V1ObjectMeta>;

Task Delete<TResource>(string name, string? @namespace = null)
where TResource : IKubernetesObject<V1ObjectMeta>;

Task<Watcher<TResource>> Watch<TResource>(
TimeSpan timeout,
Action<WatchEventType, TResource> onEvent,
Action<Exception>? onError = null,
Action? onClose = null,
string? @namespace = null,
CancellationToken cancellationToken = default)
where TResource : IKubernetesObject<V1ObjectMeta>;
}
}
Loading

0 comments on commit c34676c

Please sign in to comment.