From 3f9e4779d1a8bc95e814adef3ff338948fc5ab80 Mon Sep 17 00:00:00 2001
From: Patrick Evers
Date: Fri, 23 Jan 2026 15:57:41 +0100
Subject: [PATCH] Introduces build tasks for code generation
Moves code generation logic to MSBuild tasks, which improves the build process and allows for more flexibility in customizing the generated output.
Removes the commands for generating launch settings and Dockerfile, replacing them with MSBuild tasks that automatically generate these files during the build process.
Adds a configuration provider to centralize and prioritize operator configuration from assembly attributes, appsettings.json, and the OperatorBuilder, enabling easier customization.
Updates EventWatcher and commands to use the new OperatorConfiguration for settings like operator name, image, and namespace.
Adds new build tasks project and templates.
Fixes #2
---
K8sOperator.NET.slnx | 1 +
examples/SimpleOperator/Program.cs | 3 +-
.../Properties/launchSettings.json | 36 +-
examples/SimpleOperator/SimpleOperator.csproj | 6 +-
.../GenerateDockerfileTask.cs | 110 ++++++
.../GenerateLaunchSettingsTask.cs | 62 ++++
.../K8sOperator.NET.BuildTasks.csproj | 24 ++
.../Templates/.dockerignore.template | 0
.../Templates/Dockerfile.template | 0
.../Templates/launchSettings.json.template | 37 ++
.../Builder/ControllerBuilder.cs | 15 +-
.../Builder/EventWatcherBuilder.cs | 12 +-
.../Commands/GenerateDockerfileCommand.cs | 93 -----
.../Commands/GenerateLaunchSettingsCommand.cs | 83 -----
.../Commands/InstallCommand.cs | 66 ++--
.../Commands/VersionCommand.cs | 14 +-
.../Configuration/OperatorConfiguration.cs | 99 ++++++
.../OperatorConfigurationProvider.cs | 83 +++++
src/K8sOperator.NET/EventWatcher.cs | 12 +-
src/K8sOperator.NET/EventWatcherDatasource.cs | 11 +-
src/K8sOperator.NET/K8sOperator.NET.csproj | 37 +-
.../Metadata/AdditionalPrinterColumn.cs | 10 +-
src/K8sOperator.NET/Operator.targets | 44 ++-
src/K8sOperator.NET/OperatorBuilder.cs | 35 ++
src/K8sOperator.NET/OperatorExtensions.cs | 99 +++---
.../EventWatcherDatasource_Tests.cs | 106 +++---
.../EventWatcher_Tests.cs | 11 +-
.../OperatorConfiguration_Tests.cs | 318 ++++++++++++++++++
28 files changed, 1005 insertions(+), 422 deletions(-)
create mode 100644 src/K8sOperator.NET.BuildTasks/GenerateDockerfileTask.cs
create mode 100644 src/K8sOperator.NET.BuildTasks/GenerateLaunchSettingsTask.cs
create mode 100644 src/K8sOperator.NET.BuildTasks/K8sOperator.NET.BuildTasks.csproj
rename src/{K8sOperator.NET => K8sOperator.NET.BuildTasks}/Templates/.dockerignore.template (100%)
rename src/{K8sOperator.NET => K8sOperator.NET.BuildTasks}/Templates/Dockerfile.template (100%)
create mode 100644 src/K8sOperator.NET.BuildTasks/Templates/launchSettings.json.template
delete mode 100644 src/K8sOperator.NET/Commands/GenerateDockerfileCommand.cs
delete mode 100644 src/K8sOperator.NET/Commands/GenerateLaunchSettingsCommand.cs
create mode 100644 src/K8sOperator.NET/Configuration/OperatorConfiguration.cs
create mode 100644 src/K8sOperator.NET/Configuration/OperatorConfigurationProvider.cs
create mode 100644 src/K8sOperator.NET/OperatorBuilder.cs
create mode 100644 test/K8sOperator.NET.Tests/OperatorConfiguration_Tests.cs
diff --git a/K8sOperator.NET.slnx b/K8sOperator.NET.slnx
index e172296..1ceea1e 100644
--- a/K8sOperator.NET.slnx
+++ b/K8sOperator.NET.slnx
@@ -3,6 +3,7 @@
+
diff --git a/examples/SimpleOperator/Program.cs b/examples/SimpleOperator/Program.cs
index 6d80422..c57ba4e 100644
--- a/examples/SimpleOperator/Program.cs
+++ b/examples/SimpleOperator/Program.cs
@@ -6,8 +6,7 @@
builder.Services.AddOperator(x =>
{
- //x.WithLeaderElection();
-
+ x.WithLeaderElection();
});
var app = builder.Build();
diff --git a/examples/SimpleOperator/Properties/launchSettings.json b/examples/SimpleOperator/Properties/launchSettings.json
index bda3e33..cdcab8f 100644
--- a/examples/SimpleOperator/Properties/launchSettings.json
+++ b/examples/SimpleOperator/Properties/launchSettings.json
@@ -1,24 +1,16 @@
{
"profiles": {
- "Help": {
+ "Operator": {
"commandName": "Project",
- "commandLineArgs": "help",
+ "commandLineArgs": "operator",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true
},
- "Operator": {
- "commandName": "Project",
- "commandLineArgs": "operator",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- },
- "dotnetRunMessages": true
- },
"Install": {
"commandName": "Project",
- "commandLineArgs": "install",
+ "commandLineArgs": "install > install.yaml",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
@@ -32,30 +24,14 @@
},
"dotnetRunMessages": true
},
- "Create": {
- "commandName": "Project",
- "commandLineArgs": "create",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- },
- "dotnetRunMessages": true
- },
- "GenerateLaunchsettings": {
- "commandName": "Project",
- "commandLineArgs": "generate-launchsettings",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- },
- "dotnetRunMessages": true
- },
- "GenerateDockerfile": {
+ "Help": {
"commandName": "Project",
- "commandLineArgs": "generate-dockerfile",
+ "commandLineArgs": "help",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true
}
},
- "schema": "http://json.schemastore.org/launchsettings.json"
+ "$schema": "http://json.schemastore.org/launchsettings.json"
}
diff --git a/examples/SimpleOperator/SimpleOperator.csproj b/examples/SimpleOperator/SimpleOperator.csproj
index 712f7b1..2bea637 100644
--- a/examples/SimpleOperator/SimpleOperator.csproj
+++ b/examples/SimpleOperator/SimpleOperator.csproj
@@ -16,10 +16,10 @@
simple-operator
simple-system
-
+
alpha
diff --git a/src/K8sOperator.NET.BuildTasks/GenerateDockerfileTask.cs b/src/K8sOperator.NET.BuildTasks/GenerateDockerfileTask.cs
new file mode 100644
index 0000000..0afc061
--- /dev/null
+++ b/src/K8sOperator.NET.BuildTasks/GenerateDockerfileTask.cs
@@ -0,0 +1,110 @@
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using System;
+using System.IO;
+using System.Text.RegularExpressions;
+
+namespace K8sOperator.NET.BuildTasks;
+
+public class GenerateDockerfileTask : Task
+{
+ [Required]
+ public string ProjectDirectory { get; set; } = string.Empty;
+
+ [Required]
+ public string ProjectName { get; set; } = string.Empty;
+
+ [Required]
+ public string TargetFramework { get; set; } = string.Empty;
+
+ [Required]
+ public string OperatorName { get; set; } = string.Empty;
+
+ [Required]
+ public string ContainerRegistry { get; set; } = string.Empty;
+
+ [Required]
+ public string ContainerRepository { get; set; } = string.Empty;
+
+ [Required]
+ public string ContainerTag { get; set; } = string.Empty;
+
+ public override bool Execute()
+ {
+ try
+ {
+ var dockerfilePath = Path.Combine(ProjectDirectory, "Dockerfile");
+ var dockerignorePath = Path.Combine(ProjectDirectory, ".dockerignore");
+
+ // Extract .NET version from TargetFramework
+ var dotnetVersion = ExtractDotNetVersion(TargetFramework);
+
+ // Read templates from embedded resources
+ var dockerfileContent = ReadEmbeddedResource("Dockerfile.template");
+ var dockerignoreContent = ReadEmbeddedResource(".dockerignore.template");
+
+ // Replace placeholders
+ dockerfileContent = dockerfileContent
+ .Replace("{PROJECT_NAME}", ProjectName)
+ .Replace("{DOTNET_VERSION}", dotnetVersion);
+
+ if (!File.Exists(dockerfilePath))
+ {
+ File.WriteAllText(dockerfilePath, dockerfileContent);
+
+ Log.LogMessage(MessageImportance.High, $"Generated Dockerfile at: {dockerfilePath}");
+
+ }
+
+ if (!File.Exists(dockerignorePath))
+ {
+ File.WriteAllText(dockerignorePath, dockerignoreContent);
+
+ Log.LogMessage(MessageImportance.High, $"Generated .dockerignore at: {dockerignorePath}");
+ }
+
+ // Log success
+ Log.LogMessage(MessageImportance.High, $"Operator: {OperatorName}");
+ Log.LogMessage(MessageImportance.High, $" .NET Version: {dotnetVersion}");
+ Log.LogMessage(MessageImportance.High, $" Image: {ContainerRegistry}/{ContainerRepository}:{ContainerTag}");
+ Log.LogMessage(MessageImportance.High, "");
+ Log.LogMessage(MessageImportance.High, "To build the image:");
+ Log.LogMessage(MessageImportance.High, $" docker build -t {ContainerRegistry}/{ContainerRepository}:{ContainerTag} .");
+ Log.LogMessage(MessageImportance.High, "");
+ Log.LogMessage(MessageImportance.High, "To push the image:");
+ Log.LogMessage(MessageImportance.High, $" docker push {ContainerRegistry}/{ContainerRepository}:{ContainerTag}");
+ Log.LogMessage(MessageImportance.High, "");
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Log.LogError($"Failed to generate Dockerfile: {ex.Message}");
+ return false;
+ }
+ }
+
+ private static string ExtractDotNetVersion(string targetFramework)
+ {
+ // Handle formats like "net10.0", "net8.0", "netcoreapp3.1"
+ var match = Regex.Match(targetFramework, @"net(?:coreapp)?(\d+\.\d+)");
+ if (match.Success)
+ {
+ return match.Groups[1].Value;
+ }
+
+ // Fallback
+ return "10.0";
+ }
+
+ private static string ReadEmbeddedResource(string resourceName)
+ {
+ var assembly = typeof(GenerateDockerfileTask).Assembly;
+ var fullResourceName = $"K8sOperator.NET.BuildTasks.Templates.{resourceName}";
+
+ using var stream = assembly.GetManifestResourceStream(fullResourceName)
+ ?? throw new InvalidOperationException($"Could not find embedded resource: {fullResourceName}");
+ using var reader = new StreamReader(stream);
+ return reader.ReadToEnd();
+ }
+}
diff --git a/src/K8sOperator.NET.BuildTasks/GenerateLaunchSettingsTask.cs b/src/K8sOperator.NET.BuildTasks/GenerateLaunchSettingsTask.cs
new file mode 100644
index 0000000..536310f
--- /dev/null
+++ b/src/K8sOperator.NET.BuildTasks/GenerateLaunchSettingsTask.cs
@@ -0,0 +1,62 @@
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using System;
+using System.IO;
+
+namespace K8sOperator.NET.BuildTasks;
+
+public class GenerateLaunchSettingsTask : Task
+{
+ [Required]
+ public string ProjectDirectory { get; set; } = string.Empty;
+
+ public override bool Execute()
+ {
+ try
+ {
+ var propertiesDir = Path.Combine(ProjectDirectory, "Properties");
+ var launchSettingsPath = Path.Combine(propertiesDir, "launchSettings.json");
+
+ if (File.Exists(launchSettingsPath))
+ {
+ Log.LogMessage(MessageImportance.Normal, $"launchSettings.json already exists at {launchSettingsPath}, skipping generation");
+ return true;
+ }
+
+ // Read template from embedded resources
+ var launchSettingsContent = ReadEmbeddedResource("launchSettings.json.template");
+
+ // Create Properties directory if needed
+ Directory.CreateDirectory(propertiesDir);
+
+ // Write launchSettings.json
+ File.WriteAllText(launchSettingsPath, launchSettingsContent);
+
+ // Log success
+ Log.LogMessage(MessageImportance.High, "");
+ Log.LogMessage(MessageImportance.High, $"Generated launchSettings.json at: {launchSettingsPath}");
+ Log.LogMessage(MessageImportance.High, $" Profiles: Operator, Install, Version, Help");
+ Log.LogMessage(MessageImportance.High, "");
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Log.LogError($"Failed to generate launchSettings.json: {ex.Message}");
+ return false;
+ }
+ }
+
+ private static string ReadEmbeddedResource(string resourceName)
+ {
+ var assembly = typeof(GenerateLaunchSettingsTask).Assembly;
+ var fullResourceName = $"K8sOperator.NET.BuildTasks.Templates.{resourceName}";
+
+ using var stream = assembly.GetManifestResourceStream(fullResourceName)
+ ?? throw new InvalidOperationException($"Could not find embedded resource: {fullResourceName}");
+ using var reader = new StreamReader(stream);
+ return reader.ReadToEnd();
+ }
+}
+
+
diff --git a/src/K8sOperator.NET.BuildTasks/K8sOperator.NET.BuildTasks.csproj b/src/K8sOperator.NET.BuildTasks/K8sOperator.NET.BuildTasks.csproj
new file mode 100644
index 0000000..8f501c7
--- /dev/null
+++ b/src/K8sOperator.NET.BuildTasks/K8sOperator.NET.BuildTasks.csproj
@@ -0,0 +1,24 @@
+
+
+
+ netstandard2.0
+ latest
+ enable
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/K8sOperator.NET/Templates/.dockerignore.template b/src/K8sOperator.NET.BuildTasks/Templates/.dockerignore.template
similarity index 100%
rename from src/K8sOperator.NET/Templates/.dockerignore.template
rename to src/K8sOperator.NET.BuildTasks/Templates/.dockerignore.template
diff --git a/src/K8sOperator.NET/Templates/Dockerfile.template b/src/K8sOperator.NET.BuildTasks/Templates/Dockerfile.template
similarity index 100%
rename from src/K8sOperator.NET/Templates/Dockerfile.template
rename to src/K8sOperator.NET.BuildTasks/Templates/Dockerfile.template
diff --git a/src/K8sOperator.NET.BuildTasks/Templates/launchSettings.json.template b/src/K8sOperator.NET.BuildTasks/Templates/launchSettings.json.template
new file mode 100644
index 0000000..cdcab8f
--- /dev/null
+++ b/src/K8sOperator.NET.BuildTasks/Templates/launchSettings.json.template
@@ -0,0 +1,37 @@
+{
+ "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
+ },
+ "Version": {
+ "commandName": "Project",
+ "commandLineArgs": "version",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true
+ },
+ "Help": {
+ "commandName": "Project",
+ "commandLineArgs": "help",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true
+ }
+ },
+ "$schema": "http://json.schemastore.org/launchsettings.json"
+}
diff --git a/src/K8sOperator.NET/Builder/ControllerBuilder.cs b/src/K8sOperator.NET/Builder/ControllerBuilder.cs
index 4c04173..e5a6b3d 100644
--- a/src/K8sOperator.NET/Builder/ControllerBuilder.cs
+++ b/src/K8sOperator.NET/Builder/ControllerBuilder.cs
@@ -1,20 +1,22 @@
-using Microsoft.Extensions.DependencyInjection;
+using K8sOperator.NET.Configuration;
+using Microsoft.Extensions.DependencyInjection;
namespace K8sOperator.NET.Builder;
public class ControllerBuilder
{
- private ControllerBuilder(IServiceProvider serviceProvider, Type controllerType, List