Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions K8sOperator.NET.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<Project Path="examples/SimpleOperator/SimpleOperator.csproj" Id="ed579abf-9eb5-4f74-95e3-8f4ac03f760a" />
</Folder>
<Folder Name="/src/">
<Project Path="src/K8sOperator.NET.BuildTasks/K8sOperator.NET.BuildTasks.csproj" Id="4f783416-fb78-4362-a8c4-48df7d68bebe" />
<Project Path="src/K8sOperator.NET/K8sOperator.NET.csproj" />
</Folder>
<Folder Name="/test/">
Expand Down
3 changes: 1 addition & 2 deletions examples/SimpleOperator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@

builder.Services.AddOperator(x =>
{
//x.WithLeaderElection();

x.WithLeaderElection();
});

var app = builder.Build();
Expand Down
36 changes: 6 additions & 30 deletions examples/SimpleOperator/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -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": {
Comment on lines 11 to 14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Shell redirection won’t work in launchSettings.

commandLineArgs is passed directly to dotnet run; the > redirection isn’t interpreted, so install.yaml won’t be created. Prefer a supported output flag in the command or keep redirection as a manual shell step.

🔧 Suggested fix (avoid redirection in launchSettings)
-      "commandLineArgs": "install > install.yaml",
+      "commandLineArgs": "install",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"Install": {
"commandName": "Project",
"commandLineArgs": "install",
"commandLineArgs": "install > install.yaml",
"environmentVariables": {
"Install": {
"commandName": "Project",
"commandLineArgs": "install",
"environmentVariables": {
🤖 Prompt for AI Agents
In `@examples/SimpleOperator/Properties/launchSettings.json` around lines 11 - 14,
The "Install" profile in launchSettings.json uses shell redirection in the
"commandLineArgs" value ("install > install.yaml"), but dotnet run does not
interpret '>' so the file won't be created; update the "Install" profile's
commandLineArgs to remove the redirection and either use the CLI's supported
output flag (e.g., replace the redirection with the command's explicit output
option) or leave it as just "install" and document running the redirected
command manually in a shell instead; look for the "Install" object and its
"commandLineArgs" property to make this change.

"ASPNETCORE_ENVIRONMENT": "Development"
},
Expand All @@ -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"
}
6 changes: 3 additions & 3 deletions examples/SimpleOperator/SimpleOperator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
<PropertyGroup>
<OperatorName>simple-operator</OperatorName>
<OperatorNamespace>simple-system</OperatorNamespace>
<!--
<ContainerRegistry>ghcr.io</ContainerRegistry>

<ContainerRegistry>test.ghcr.io</ContainerRegistry>
<ContainerRepository>simple-operator</ContainerRepository>
-->

<ContainerFamily>alpha</ContainerFamily>
</PropertyGroup>

Expand Down
110 changes: 110 additions & 0 deletions src/K8sOperator.NET.BuildTasks/GenerateDockerfileTask.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
62 changes: 62 additions & 0 deletions src/K8sOperator.NET.BuildTasks/GenerateLaunchSettingsTask.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}


24 changes: 24 additions & 0 deletions src/K8sOperator.NET.BuildTasks/K8sOperator.NET.BuildTasks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="17.12.*" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.12.*" />
</ItemGroup>

<!-- Embed template files as resources -->
<ItemGroup>
<EmbeddedResource Include="Templates\Dockerfile.template" />
<EmbeddedResource Include="Templates\.dockerignore.template" />
<EmbeddedResource Include="Templates\launchSettings.json.template" />
</ItemGroup>

</Project>

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"profiles": {
"Operator": {
"commandName": "Project",
"commandLineArgs": "operator",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true
},
"Install": {
"commandName": "Project",
"commandLineArgs": "install > install.yaml",
"environmentVariables": {
Comment on lines +11 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Shell redirection won’t work in generated launchSettings.

The task writes this into launchSettings, but dotnet run won’t interpret >; the file won’t be produced.

🔧 Suggested fix (avoid redirection in template)
-      "commandLineArgs": "install > install.yaml",
+      "commandLineArgs": "install",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"Install": {
"commandName": "Project",
"commandLineArgs": "install > install.yaml",
"environmentVariables": {
"Install": {
"commandName": "Project",
"commandLineArgs": "install",
"environmentVariables": {
🤖 Prompt for AI Agents
In `@src/K8sOperator.NET.BuildTasks/Templates/launchSettings.json.template` around
lines 11 - 14, The launchSettings profile "Install" currently uses shell
redirection in commandLineArgs ("install > install.yaml"), which dotnet run
won't interpret; update the "Install" profile (commandName "Project") to avoid
shell redirection by replacing the argument with a CLI-supported output flag
(e.g., "install --output install.yaml") or another method the install command
provides to write its output to a file, so the generated
launchSettings.json.template no longer relies on ">" for file creation.

"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"
}
15 changes: 10 additions & 5 deletions src/K8sOperator.NET/Builder/ControllerBuilder.cs
Original file line number Diff line number Diff line change
@@ -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<object> metadata)
private ControllerBuilder(IServiceProvider serviceProvider, Type controllerType, OperatorConfiguration configuration)
{
ServiceProvider = serviceProvider;
ControllerType = controllerType;
Metadata = metadata;
Configuration = configuration;
}
public IServiceProvider ServiceProvider { get; }
public Type ControllerType { get; set; }
public OperatorConfiguration Configuration { get; }

public static ControllerBuilder Create(IServiceProvider serviceProvider, Type controllerType, List<object> metadata)
=> new(serviceProvider, controllerType, metadata);
public static ControllerBuilder Create(IServiceProvider serviceProvider, Type controllerType, OperatorConfiguration configuration)
=> new(serviceProvider, controllerType, configuration);

public IOperatorController Build()
{
Expand All @@ -30,3 +32,6 @@ public IOperatorController Build()
}





Loading
Loading