diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml
index 2b6a338..c725c85 100644
--- a/.github/workflows/build-publish.yml
+++ b/.github/workflows/build-publish.yml
@@ -35,7 +35,11 @@ jobs:
run: dotnet build -c Release
- name: Test
- run: dotnet test -c Release
+ run: |
+ for project in test/*/*.csproj; do
+ echo "Running tests for $project..."
+ dotnet run --project "$project" -c Release
+ done
- name: DotNet Pack
if: startsWith(github.ref, 'refs/tags/')
diff --git a/.github/workflows/security-analysis.yml b/.github/workflows/security-analysis.yml
index da71f37..e10fe6c 100644
--- a/.github/workflows/security-analysis.yml
+++ b/.github/workflows/security-analysis.yml
@@ -28,7 +28,7 @@ jobs:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Initialize CodeQL
- uses: github/codeql-action/init@v3
+ uses: github/codeql-action/init@v4
with:
languages: csharp
@@ -41,4 +41,4 @@ jobs:
run: dotnet build --configuration Release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v3
+ uses: github/codeql-action/analyze@v4
diff --git a/Directory.Packages.props b/Directory.Packages.props
deleted file mode 100644
index 3db64ff..0000000
--- a/Directory.Packages.props
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/K8sOperator.NET.slnx b/K8sOperator.NET.slnx
new file mode 100644
index 0000000..e172296
--- /dev/null
+++ b/K8sOperator.NET.slnx
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/K8sOperator.sln b/K8sOperator.sln
deleted file mode 100644
index 4b8b1fc..0000000
--- a/K8sOperator.sln
+++ /dev/null
@@ -1,79 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 18
-VisualStudioVersion = 18.1.11312.151 d18.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{898CC489-C84A-49BD-9D77-3CEA1F6A7180}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "props", "props", "{029923F0-FD53-4B75-BA07-F102BBE9C429}"
- ProjectSection(SolutionItems) = preProject
- src\Directory.Build.props = src\Directory.Build.props
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{9320CC2F-6BB6-4B29-B625-EB427EE87891}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "props", "props", "{0CCCC7F3-A522-4535-8D5A-1E53815936D3}"
- ProjectSection(SolutionItems) = preProject
- test\Directory.Build.props = test\Directory.Build.props
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{4D1F501E-294C-4B22-9792-1BBB2B553C69}"
- ProjectSection(SolutionItems) = preProject
- assets\logo.png = assets\logo.png
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "K8sOperator.NET.Tests", "test\K8sOperator.NET.Tests\K8sOperator.NET.Tests.csproj", "{C0360068-BBDE-4ABF-B357-765C792CDCF5}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "K8sOperator.NET", "src\K8sOperator.NET\K8sOperator.NET.csproj", "{160DFED1-DD63-412C-9D60-84D965626DD9}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{ED5FF81E-F3EA-4BEF-9B72-31A24F9386E3}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleOperator", "examples\SimpleOperator\SimpleOperator.csproj", "{B7588B10-BDD2-4622-BB16-18EFFBE7BE25}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "K8sOperator.NET.Generators", "src\K8sOperator.NET.Generators\K8sOperator.NET.Generators.csproj", "{183BE367-0544-4AD1-B741-2BA2F186BC0E}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "K8sOperator.NET.Generators.Tests", "test\K8sOperator.NET.Generators.Tests\K8sOperator.NET.Generators.Tests.csproj", "{D07BFDEA-0E46-4822-9F38-9581425FD93F}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {C0360068-BBDE-4ABF-B357-765C792CDCF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C0360068-BBDE-4ABF-B357-765C792CDCF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C0360068-BBDE-4ABF-B357-765C792CDCF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C0360068-BBDE-4ABF-B357-765C792CDCF5}.Release|Any CPU.Build.0 = Release|Any CPU
- {160DFED1-DD63-412C-9D60-84D965626DD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {160DFED1-DD63-412C-9D60-84D965626DD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {160DFED1-DD63-412C-9D60-84D965626DD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {160DFED1-DD63-412C-9D60-84D965626DD9}.Release|Any CPU.Build.0 = Release|Any CPU
- {B7588B10-BDD2-4622-BB16-18EFFBE7BE25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B7588B10-BDD2-4622-BB16-18EFFBE7BE25}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B7588B10-BDD2-4622-BB16-18EFFBE7BE25}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B7588B10-BDD2-4622-BB16-18EFFBE7BE25}.Release|Any CPU.Build.0 = Release|Any CPU
- {183BE367-0544-4AD1-B741-2BA2F186BC0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {183BE367-0544-4AD1-B741-2BA2F186BC0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {183BE367-0544-4AD1-B741-2BA2F186BC0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {183BE367-0544-4AD1-B741-2BA2F186BC0E}.Release|Any CPU.Build.0 = Release|Any CPU
- {D07BFDEA-0E46-4822-9F38-9581425FD93F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {D07BFDEA-0E46-4822-9F38-9581425FD93F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {D07BFDEA-0E46-4822-9F38-9581425FD93F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {D07BFDEA-0E46-4822-9F38-9581425FD93F}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {029923F0-FD53-4B75-BA07-F102BBE9C429} = {898CC489-C84A-49BD-9D77-3CEA1F6A7180}
- {0CCCC7F3-A522-4535-8D5A-1E53815936D3} = {9320CC2F-6BB6-4B29-B625-EB427EE87891}
- {C0360068-BBDE-4ABF-B357-765C792CDCF5} = {9320CC2F-6BB6-4B29-B625-EB427EE87891}
- {160DFED1-DD63-412C-9D60-84D965626DD9} = {898CC489-C84A-49BD-9D77-3CEA1F6A7180}
- {B7588B10-BDD2-4622-BB16-18EFFBE7BE25} = {ED5FF81E-F3EA-4BEF-9B72-31A24F9386E3}
- {183BE367-0544-4AD1-B741-2BA2F186BC0E} = {898CC489-C84A-49BD-9D77-3CEA1F6A7180}
- {D07BFDEA-0E46-4822-9F38-9581425FD93F} = {9320CC2F-6BB6-4B29-B625-EB427EE87891}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {49DE7562-1920-49AD-A220-E10302CEC632}
- EndGlobalSection
-EndGlobal
diff --git a/examples/Directory.Build.props b/examples/Directory.Build.props
new file mode 100644
index 0000000..72cf253
--- /dev/null
+++ b/examples/Directory.Build.props
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/examples/SimpleOperator/.dockerignore b/examples/SimpleOperator/.dockerignore
new file mode 100644
index 0000000..0ada9b3
--- /dev/null
+++ b/examples/SimpleOperator/.dockerignore
@@ -0,0 +1,48 @@
+# Git
+.git
+.gitignore
+.gitattributes
+
+# Build results
+bin/
+obj/
+[Bb]uild/
+[Dd]ebug/
+[Rr]elease/
+
+# Visual Studio
+.vs/
+.vscode/
+*.user
+*.suo
+*.userosscache
+*.sln.docstates
+
+# Test results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+TestResults/
+
+# NuGet
+*.nupkg
+*.snupkg
+packages/
+
+# Docker
+Dockerfile
+.dockerignore
+
+# Kubernetes
+*.yaml
+*.yml
+
+# Documentation
+*.md
+README*
+LICENSE
+
+# IDE
+.idea/
+*.swp
+*.swo
+*~
diff --git a/examples/SimpleOperator/Controllers/TodoController.cs b/examples/SimpleOperator/Controllers/TodoController.cs
new file mode 100644
index 0000000..b63e3d1
--- /dev/null
+++ b/examples/SimpleOperator/Controllers/TodoController.cs
@@ -0,0 +1,89 @@
+using K8sOperator.NET;
+using SimpleOperator.Resources;
+
+namespace SimpleOperator.Controllers;
+
+public class TodoController : OperatorController
+{
+ private readonly ILogger _logger;
+
+ public TodoController(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public override async Task AddOrModifyAsync(TodoItem resource, CancellationToken cancellationToken)
+ {
+ _logger.LogInformation(
+ "Processing TodoItem: {Name} - Title: {Title}, Priority: {Priority}",
+ resource.Metadata.Name,
+ resource.Spec.Title,
+ resource.Spec.Priority);
+
+ // Initialize status if needed
+ if (resource.Status == null)
+ {
+ resource.Status = new TodoItem.TodoStatus();
+ }
+
+ // Update reconciliation count
+ resource.Status.ReconciliationCount++;
+
+ // Business logic: Auto-complete if due date is passed
+ if (resource.Spec.DueDate.HasValue &&
+ resource.Spec.DueDate.Value < DateTime.UtcNow &&
+ resource.Status.State != "completed")
+ {
+ resource.Status.State = "overdue";
+ resource.Status.Message = $"Task is overdue by {(DateTime.UtcNow - resource.Spec.DueDate.Value).Days} days";
+ _logger.LogWarning("TodoItem {Name} is overdue!", resource.Metadata.Name);
+ }
+ else if (resource.Status.State == "pending")
+ {
+ resource.Status.State = "in-progress";
+ resource.Status.Message = "Task is being processed";
+ _logger.LogInformation("TodoItem {Name} moved to in-progress", resource.Metadata.Name);
+ }
+
+ // Simulate some async work
+ await Task.Delay(100, cancellationToken);
+ }
+
+ public override async Task DeleteAsync(TodoItem resource, CancellationToken cancellationToken)
+ {
+ _logger.LogInformation(
+ "Deleting TodoItem: {Name}. Final state was: {State}",
+ resource.Metadata.Name,
+ resource.Status?.State ?? "unknown");
+
+ // Cleanup logic here (e.g., remove external resources)
+ await Task.CompletedTask;
+ }
+
+ public override async Task FinalizeAsync(TodoItem resource, CancellationToken cancellationToken)
+ {
+ _logger.LogInformation(
+ "Finalizing TodoItem: {Name}",
+ resource.Metadata.Name);
+
+ // Perform cleanup before resource is completely deleted
+ // For example: remove related resources, notify external systems, etc.
+
+ await Task.CompletedTask;
+ }
+
+ public override async Task ErrorAsync(TodoItem resource, CancellationToken cancellationToken)
+ {
+ _logger.LogError(
+ "Error occurred for TodoItem: {Name}",
+ resource.Metadata.Name);
+
+ if (resource.Status != null)
+ {
+ resource.Status.State = "error";
+ resource.Status.Message = "An error occurred during reconciliation";
+ }
+
+ await Task.CompletedTask;
+ }
+}
diff --git a/examples/SimpleOperator/Dockerfile b/examples/SimpleOperator/Dockerfile
new file mode 100644
index 0000000..22d9682
--- /dev/null
+++ b/examples/SimpleOperator/Dockerfile
@@ -0,0 +1,44 @@
+# Build stage
+FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
+WORKDIR /src
+
+# Copy project file and restore dependencies
+COPY ["SimpleOperator.csproj", "./"]
+RUN dotnet restore
+
+# Copy source code and build
+COPY . .
+RUN dotnet build -c Release -o /app/build
+
+# Publish stage
+FROM build AS publish
+RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false
+
+# Runtime stage
+FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
+WORKDIR /app
+
+# Create non-root user
+RUN groupadd -r operator && useradd -r -g operator operator
+
+# Copy published app
+COPY --from=publish /app/publish .
+
+# Set ownership
+RUN chown -R operator:operator /app
+
+# Switch to non-root user
+USER operator
+
+# Set environment variables
+ENV ASPNETCORE_ENVIRONMENT=Production \
+ DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \
+ DOTNET_EnableDiagnostics=0
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD dotnet SimpleOperator.dll version || exit 1
+
+# Entrypoint
+ENTRYPOINT ["dotnet", "SimpleOperator.dll"]
+CMD ["operator"]
diff --git a/examples/SimpleOperator/Program.cs b/examples/SimpleOperator/Program.cs
index a466e79..a44848d 100644
--- a/examples/SimpleOperator/Program.cs
+++ b/examples/SimpleOperator/Program.cs
@@ -1,21 +1,12 @@
using K8sOperator.NET;
-using K8sOperator.NET.Extensions;
-using K8sOperator.NET.Generators;
-using SimpleOperator.Projects;
+using SimpleOperator.Controllers;
+var builder = WebApplication.CreateBuilder(args);
-var builder = OperatorHost.CreateOperatorApplicationBuilder(args)
- //.WithName("simple-operator")
- .WithNamespace("simple-ops-system");
-
-builder.AddController()
- .WithFinalizer("testitem.local.finalizer");
-
-builder.AddController()
- .WithFinalizer("project.local.finalizer");
+builder.Services.AddOperator();
var app = builder.Build();
-app.AddInstall();
+app.MapController();
-await app.RunAsync();
+await app.RunOperatorAsync();
diff --git a/examples/SimpleOperator/Projects/Project.cs b/examples/SimpleOperator/Projects/Project.cs
deleted file mode 100644
index 0f5fbec..0000000
--- a/examples/SimpleOperator/Projects/Project.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using k8s.Models;
-using K8sOperator.NET.Models;
-
-namespace SimpleOperator.Projects;
-
-[KubernetesEntity(Group = "operator.io", ApiVersion = "v1alpha1", Kind = "Project", PluralName = "projects")]
-public class Project : CustomResource
-{
- public class Specs
- {
- public string Name { get; set; } = string.Empty;
- public string Organization { get; set; } = string.Empty;
- public string Project { get; set; } = string.Empty;
- }
-
- public class ProjectStatus
- {
- public string Result { get; set; } = string.Empty;
- }
-}
diff --git a/examples/SimpleOperator/Projects/ProjectController.cs b/examples/SimpleOperator/Projects/ProjectController.cs
deleted file mode 100644
index 2065c4c..0000000
--- a/examples/SimpleOperator/Projects/ProjectController.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using K8sOperator.NET;
-using K8sOperator.NET.Metadata;
-
-namespace SimpleOperator.Projects;
-
-[Namespace("default")]
-public class ProjectController(ILoggerFactory logger) : Controller
-{
- private readonly ILogger _logger = logger.CreateLogger();
-
- public override Task AddOrModifyAsync(Project resource, CancellationToken cancellationToken)
- {
- _logger.LogDebug("Controller AddOrModify received.");
-
- resource.Metadata.Labels = new Dictionary
- {
- { "created", "created" }
- };
-
- resource.Status.Result = "HEHE";
-
- return base.AddOrModifyAsync(resource, cancellationToken);
- }
-
- public override Task DeleteAsync(Project resource, CancellationToken cancellationToken)
- {
- _logger.LogDebug("Controller Delete received.");
- return base.DeleteAsync(resource, cancellationToken);
- }
-
- public override Task FinalizeAsync(Project resource, CancellationToken cancellationToken)
- {
- _logger.LogDebug("Controller Finalize received.");
- return base.FinalizeAsync(resource, cancellationToken);
- }
-}
diff --git a/examples/SimpleOperator/Projects/TestItem.cs b/examples/SimpleOperator/Projects/TestItem.cs
deleted file mode 100644
index 3858835..0000000
--- a/examples/SimpleOperator/Projects/TestItem.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using k8s.Models;
-using K8sOperator.NET.Models;
-using static SimpleOperator.Projects.TestItem;
-
-namespace SimpleOperator.Projects;
-
-[KubernetesEntity(Group = "operator.io", ApiVersion = "v1alpha1", Kind = "TestItem", PluralName = "testitems")]
-public class TestItem : CustomResource
-{
- public class TestItemSpec
- {
- public string? String { get; set; }
- }
-
- public class TestItemStatus
- {
-
- }
-}
diff --git a/examples/SimpleOperator/Projects/TestItemController.cs b/examples/SimpleOperator/Projects/TestItemController.cs
deleted file mode 100644
index e58c71e..0000000
--- a/examples/SimpleOperator/Projects/TestItemController.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-using K8sOperator.NET;
-
-namespace SimpleOperator.Projects;
-
-public class TestItemController : Controller
-{
- public override Task AddOrModifyAsync(TestItem resource, CancellationToken cancellationToken)
- {
- return base.AddOrModifyAsync(resource, cancellationToken);
- }
-
- public override Task BookmarkAsync(TestItem resource, CancellationToken cancellationToken)
- {
- return base.BookmarkAsync(resource, cancellationToken);
- }
-
- public override Task DeleteAsync(TestItem resource, CancellationToken cancellationToken)
- {
- return base.DeleteAsync(resource, cancellationToken);
- }
-
- public override Task ErrorAsync(TestItem resource, CancellationToken cancellationToken)
- {
- return base.ErrorAsync(resource, cancellationToken);
- }
-
- public override Task FinalizeAsync(TestItem resource, CancellationToken cancellationToken)
- {
- return base.FinalizeAsync(resource, cancellationToken);
- }
-}
diff --git a/examples/SimpleOperator/Properties/launchSettings.json b/examples/SimpleOperator/Properties/launchSettings.json
index ef260a2..d9d9d4e 100644
--- a/examples/SimpleOperator/Properties/launchSettings.json
+++ b/examples/SimpleOperator/Properties/launchSettings.json
@@ -1,37 +1,53 @@
{
- "profiles": {
- "Operator": {
- "commandName": "Project",
- "commandLineArgs": "operator",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- },
- "dotnetRunMessages": true
- },
- "Install": {
- "commandName": "Project",
- "commandLineArgs": "install > ./install.yaml",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- },
- "dotnetRunMessages": true
- },
- "Help": {
- "commandName": "Project",
- "commandLineArgs": "",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- },
- "dotnetRunMessages": true
- },
- "Version": {
- "commandName": "Project",
- "commandLineArgs": "version",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- },
- "dotnetRunMessages": true
- }
+ "profiles": {
+ "Help": {
+ "commandName": "Project",
+ "commandLineArgs": "help",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true
},
- "$schema": "http://json.schemastore.org/launchsettings.json"
-}
+ "Operator": {
+ "commandName": "Project",
+ "commandLineArgs": "operator",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true
+ },
+ "Install": {
+ "commandName": "Project",
+ "commandLineArgs": "install",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true
+ },
+ "Version": {
+ "commandName": "Project",
+ "commandLineArgs": "version",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true
+ },
+ "GenerateLaunchsettings": {
+ "commandName": "Project",
+ "commandLineArgs": "generate-launchsettings",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true
+ },
+ "GenerateDockerfile": {
+ "commandName": "Project",
+ "commandLineArgs": "generate-dockerfile",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true
+ }
+ },
+ "schema": "http://json.schemastore.org/launchsettings.json"
+}
\ No newline at end of file
diff --git a/examples/SimpleOperator/Resources/TodoItem.cs b/examples/SimpleOperator/Resources/TodoItem.cs
new file mode 100644
index 0000000..84b1fff
--- /dev/null
+++ b/examples/SimpleOperator/Resources/TodoItem.cs
@@ -0,0 +1,24 @@
+using k8s.Models;
+using K8sOperator.NET;
+
+namespace SimpleOperator.Resources;
+
+[KubernetesEntity(Group = "app.example.com", ApiVersion = "v1", Kind = "TodoItem", PluralName = "todoitems")]
+public class TodoItem : CustomResource
+{
+ public class TodoSpec
+ {
+ public string Title { get; set; } = string.Empty;
+ public string Description { get; set; } = string.Empty;
+ public string Priority { get; set; } = "medium"; // low, medium, high
+ public DateTime? DueDate { get; set; }
+ }
+
+ public class TodoStatus
+ {
+ public string State { get; set; } = "pending"; // pending, in-progress, completed
+ public DateTime? CompletedAt { get; set; }
+ public string Message { get; set; } = string.Empty;
+ public int ReconciliationCount { get; set; }
+ }
+}
diff --git a/examples/SimpleOperator/SimpleOperator.csproj b/examples/SimpleOperator/SimpleOperator.csproj
index df57e67..878d5ca 100644
--- a/examples/SimpleOperator/SimpleOperator.csproj
+++ b/examples/SimpleOperator/SimpleOperator.csproj
@@ -4,20 +4,29 @@
net10.0
enable
enable
- pmdevers
+ true
+
+ true
+ true
+ pmdevers
+ 1.0.0
+
+
+ simple-operator
+ simple-system
ghcr.io
- pmdevers/simple-operator
- 1.0.0
- -alpha0011
-
+ simple-operator
+ alpha
-
-
+
+
+
+
diff --git a/examples/SimpleOperator/appsettings.Development.json b/examples/SimpleOperator/appsettings.Development.json
index a6e86ac..0c208ae 100644
--- a/examples/SimpleOperator/appsettings.Development.json
+++ b/examples/SimpleOperator/appsettings.Development.json
@@ -1,7 +1,7 @@
{
"Logging": {
"LogLevel": {
- "Default": "Debug",
+ "Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
diff --git a/examples/SimpleOperator/appsettings.json b/examples/SimpleOperator/appsettings.json
index 23039ed..10f68b8 100644
--- a/examples/SimpleOperator/appsettings.json
+++ b/examples/SimpleOperator/appsettings.json
@@ -1,7 +1,7 @@
{
"Logging": {
"LogLevel": {
- "Default": "Debug",
+ "Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
diff --git a/examples/SimpleOperator/install.yaml b/examples/SimpleOperator/install.yaml
deleted file mode 100644
index e69de29..0000000
diff --git a/global.json b/global.json
index 588227b..441008e 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,9 @@
{
"sdk": {
- "version": "10.0.100",
- "rollForward": "latestFeature"
+ "version": "10.0.100",
+ "rollForward": "latestFeature"
+ },
+ "test": {
+ "runner": "Microsoft.Testing.Platform"
}
- }
\ No newline at end of file
+}
diff --git a/scripts/hooks/README.md b/scripts/hooks/README.md
new file mode 100644
index 0000000..304360c
--- /dev/null
+++ b/scripts/hooks/README.md
@@ -0,0 +1,35 @@
+# Git Hooks
+
+This directory contains git hooks that can be installed to improve the development workflow.
+
+## Available Hooks
+
+### pre-commit
+Runs all unit tests before allowing a commit. If any tests fail, the commit will be aborted.
+
+## Installation
+
+### Windows (PowerShell)
+```powershell
+.\scripts\hooks\install-hooks.ps1
+```
+
+### Linux/Mac (Bash)
+```bash
+chmod +x scripts/hooks/install-hooks.sh
+./scripts/hooks/install-hooks.sh
+```
+
+### Manual Installation
+Copy the hooks to your `.git/hooks` directory:
+```bash
+cp scripts/hooks/pre-commit .git/hooks/pre-commit
+chmod +x .git/hooks/pre-commit
+```
+
+## Bypassing Hooks
+
+If you need to bypass the hooks for a specific commit (not recommended), use:
+```bash
+git commit --no-verify
+```
diff --git a/scripts/hooks/install-hooks.ps1 b/scripts/hooks/install-hooks.ps1
new file mode 100644
index 0000000..8d94017
--- /dev/null
+++ b/scripts/hooks/install-hooks.ps1
@@ -0,0 +1,25 @@
+# PowerShell script to install git hooks
+Write-Host "Installing git hooks..." -ForegroundColor Cyan
+
+$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
+$repoRoot = Split-Path -Parent (Split-Path -Parent $scriptDir)
+$gitHooksDir = Join-Path $repoRoot ".git\hooks"
+
+# Check if .git directory exists
+if (-not (Test-Path (Join-Path $repoRoot ".git"))) {
+ Write-Error "Not in a git repository. Please run this script from the repository root."
+ exit 1
+}
+
+# Copy pre-commit hook
+$sourceHook = Join-Path $scriptDir "pre-commit"
+$targetHook = Join-Path $gitHooksDir "pre-commit"
+
+Copy-Item -Path $sourceHook -Destination $targetHook -Force
+Write-Host "✓ Installed pre-commit hook" -ForegroundColor Green
+
+# Make executable (for Git Bash/WSL)
+git update-index --chmod=+x "$targetHook"
+
+Write-Host "`nGit hooks installed successfully!" -ForegroundColor Green
+Write-Host "The pre-commit hook will now run tests before each commit." -ForegroundColor Gray
diff --git a/scripts/hooks/install-hooks.sh b/scripts/hooks/install-hooks.sh
new file mode 100644
index 0000000..b4add1d
--- /dev/null
+++ b/scripts/hooks/install-hooks.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+# Shell script to install git hooks
+
+echo "Installing git hooks..."
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
+GIT_HOOKS_DIR="$REPO_ROOT/.git/hooks"
+
+# Check if .git directory exists
+if [ ! -d "$REPO_ROOT/.git" ]; then
+ echo "Error: Not in a git repository."
+ exit 1
+fi
+
+# Copy pre-commit hook
+cp "$SCRIPT_DIR/pre-commit" "$GIT_HOOKS_DIR/pre-commit"
+chmod +x "$GIT_HOOKS_DIR/pre-commit"
+
+echo "✓ Installed pre-commit hook"
+echo ""
+echo "Git hooks installed successfully!"
+echo "The pre-commit hook will now run tests before each commit."
diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit
new file mode 100644
index 0000000..fc828aa
--- /dev/null
+++ b/scripts/hooks/pre-commit
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Pre-commit hook to run unit tests before allowing a commit
+
+echo "Running tests before commit..."
+
+# Run tests for all projects in test directory
+for project in test/*/*.csproj; do
+ echo "Running tests for $project..."
+ dotnet run --project "$project"
+
+ # Capture the exit code
+ TEST_EXIT_CODE=$?
+
+ # If tests failed, prevent the commit
+ if [ $TEST_EXIT_CODE -ne 0 ]; then
+ echo "Tests failed in $project. Commit aborted."
+ exit 1
+ fi
+done
+
+echo "All tests passed. Proceeding with commit."
+exit 0
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
deleted file mode 100644
index fb860a7..0000000
--- a/src/Directory.Build.props
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
- net10.0
- true
- enable
- enable
- true
- PMDEvers
-
- $(ProjectName)
-
- K8sOperator.NET is a powerful and intuitive library designed for creating Kubernetes Operators using C#. It simplifies the development of robust, cloud-native operators by leveraging the full capabilities of the .NET ecosystem, making it easier than ever to manage complex Kubernetes workloads with custom automation.
-
- Patrick Evers
- $(CompanyName)
-
-
-
-
-
- $(CompanyName)
- $(MSBuildProjectName.Replace(" ", "_"))
- README.md
- logo.png
- https://github.com/$(CompanyName)/$(ProjectName)
- MIT
- git
- https://github.com/$(CompanyName)/$(ProjectName)
- true
- true
-
-
-
-
-
- True
- \
- false
-
-
- True
- \
-
-
-
-
-
diff --git a/src/K8sOperator.NET.Generators/K8sOperator.NET.Generators.csproj b/src/K8sOperator.NET.Generators/K8sOperator.NET.Generators.csproj
deleted file mode 100644
index 9daa8f6..0000000
--- a/src/K8sOperator.NET.Generators/K8sOperator.NET.Generators.csproj
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/src/K8sOperator.NET.Generators/OperatorApplicationExtensions.cs b/src/K8sOperator.NET.Generators/OperatorApplicationExtensions.cs
deleted file mode 100644
index f7e875b..0000000
--- a/src/K8sOperator.NET.Generators/OperatorApplicationExtensions.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using K8sOperator.NET.Builder;
-
-namespace K8sOperator.NET.Generators;
-
-///
-///
-///
-public static class OperatorApplicationExtensions
-{
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public static IOperatorCommandConventionBuilder AddInstall(this TBuilder builder, int? order = null)
- where TBuilder : IOperatorApplication
- {
- return builder.AddCommand(order ?? -1);
- }
-}
diff --git a/src/K8sOperator.NET.Generators/Utilities.cs b/src/K8sOperator.NET.Generators/Utilities.cs
deleted file mode 100644
index 18bdbdb..0000000
--- a/src/K8sOperator.NET.Generators/Utilities.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Reflection;
-
-namespace K8sOperator.NET.Generators;
-internal static class Utilities
-{
- ///
- /// Check if a type is nullable.
- ///
- /// The type.
- /// True if the type is nullable (i.e. contains "nullable" in its name).
- public static bool IsNullable(this Type type)
- => type.FullName?.Contains("Nullable") == true;
- ///
- /// Check if a property is nullable.
- ///
- /// The property.
- /// True if the type is nullable (i.e. contains "nullable" in its name).
- public static bool IsNullable(this PropertyInfo prop)
- => new NullabilityInfoContext().Create(prop).ReadState == NullabilityState.Nullable ||
- prop.PropertyType.FullName?.Contains("Nullable") == true;
-}
diff --git a/src/K8sOperator.NET/Builder/CommandBuilder.cs b/src/K8sOperator.NET/Builder/CommandBuilder.cs
new file mode 100644
index 0000000..01b2309
--- /dev/null
+++ b/src/K8sOperator.NET/Builder/CommandBuilder.cs
@@ -0,0 +1,47 @@
+using K8sOperator.NET.Helpers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System.Reflection;
+
+namespace K8sOperator.NET.Builder;
+
+public class CommandBuilder(IServiceProvider serviceProvider, Type commandType)
+{
+ public IServiceProvider ServiceProvider { get; } = serviceProvider;
+ public Type CommandType { get; } = commandType;
+ public List