Skip to content

Commit 4e0c205

Browse files
ewassefbuehler
andauthored
fix: Change the labels to an array (#881)
This pull request introduces changes to improve type consistency, enhance test coverage, and refactor the `Handler` method in the `OperatorGenerator` class for better dependency injection. Below is a summary of the most important changes: ### Type Consistency Improvements: * Changed the `Labels` property in `KustomizationConfig` from `KustomizationCommonLabels?` to an array type `KustomizationCommonLabels[]?` to allow multiple labels. (`src/KubeOps.Abstractions/Kustomize/KustomizationConfig.cs`) * Updated the `Labels` assignment in the `Handler` method to use array initialization syntax for consistency with the new type. (`src/KubeOps.Cli/Commands/Generator/OperatorGenerator.cs`) ### Refactoring for Dependency Injection: * Refactored the `Handler` method in `OperatorGenerator` to inject `IAnsiConsole` as a parameter instead of directly using `AnsiConsole.Console`. This improves testability and decouples the method from the static console instance. (`src/KubeOps.Cli/Commands/Generator/OperatorGenerator.cs`) [[1]](diffhunk://#diff-cf95f5a9676479797ff0ade11e4dd8d679dd1974142058976f3a35ca6a63b4f2L39-R45) [[2]](diffhunk://#diff-cf95f5a9676479797ff0ade11e4dd8d679dd1974142058976f3a35ca6a63b4f2L54-R62) [[3]](diffhunk://#diff-cf95f5a9676479797ff0ade11e4dd8d679dd1974142058976f3a35ca6a63b4f2L74-R109) [[4]](diffhunk://#diff-cf95f5a9676479797ff0ade11e4dd8d679dd1974142058976f3a35ca6a63b4f2L159-R159) [[5]](diffhunk://#diff-cf95f5a9676479797ff0ade11e4dd8d679dd1974142058976f3a35ca6a63b4f2L170-R174) ### Test Coverage Enhancements: * Added a new integration test `Should_Generate_Valid_Installers_In_Cluster` to validate the generated Kubernetes YAML files and ensure the presence of a valid `kustomization.yaml` file. (`test/KubeOps.Cli.Test/Management/Install.Integration.Test.cs`) * Improved test dependencies by including `FluentAssertions` for better assertions and validation in test cases. (`test/KubeOps.Cli.Test/Management/Install.Integration.Test.cs`) These changes improve the maintainability, testability, and functionality of the codebase. --------- Co-authored-by: Christoph Bühler <[email protected]>
1 parent 74c0a66 commit 4e0c205

File tree

3 files changed

+81
-24
lines changed

3 files changed

+81
-24
lines changed

src/KubeOps.Abstractions/Kustomize/KustomizationConfig.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public KustomizationConfig()
2626
/// <summary>
2727
/// Common labels for the resources.
2828
/// </summary>
29-
public KustomizationCommonLabels? Labels { get; set; }
29+
public KustomizationCommonLabels[]? Labels { get; set; }
3030

3131
/// <summary>
3232
/// Resource list.

src/KubeOps.Cli/Commands/Generator/OperatorGenerator.cs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ public static Command Command
3636
Arguments.SolutionOrProjectFile,
3737
};
3838
cmd.AddAlias("op");
39-
cmd.SetHandler(Handler);
39+
cmd.SetHandler(ctx => Handler(AnsiConsole.Console, ctx));
4040

4141
return cmd;
4242
}
4343
}
4444

45-
private static async Task Handler(InvocationContext ctx)
45+
internal static async Task Handler(IAnsiConsole console, InvocationContext ctx)
4646
{
4747
var name = ctx.ParseResult.GetValueForArgument(Arguments.OperatorName);
4848
var file = ctx.ParseResult.GetValueForArgument(Arguments.SolutionOrProjectFile);
@@ -51,15 +51,15 @@ private static async Task Handler(InvocationContext ctx)
5151
var dockerImage = ctx.ParseResult.GetValueForOption(Options.AccessibleDockerImage)!;
5252
var dockerImageTag = ctx.ParseResult.GetValueForOption(Options.AccessibleDockerTag)!;
5353

54-
var result = new ResultOutput(AnsiConsole.Console, format);
55-
AnsiConsole.Console.WriteLine("Generate operator resources.");
54+
var result = new ResultOutput(console, format);
55+
console.WriteLine("Generate operator resources.");
5656

57-
AnsiConsole.Console.MarkupLine("[green]Load Project/Solution file.[/]");
57+
console.MarkupLine("[green]Load Project/Solution file.[/]");
5858
var parser = file switch
5959
{
60-
{ Extension: ".csproj", Exists: true } => await AssemblyLoader.ForProject(AnsiConsole.Console, file),
60+
{ Extension: ".csproj", Exists: true } => await AssemblyLoader.ForProject(console, file),
6161
{ Extension: ".sln", Exists: true } => await AssemblyLoader.ForSolution(
62-
AnsiConsole.Console,
62+
console,
6363
file,
6464
ctx.ParseResult.GetValueForOption(Options.SolutionProjectRegex),
6565
ctx.ParseResult.GetValueForOption(Options.TargetFramework)),
@@ -71,42 +71,42 @@ private static async Task Handler(InvocationContext ctx)
7171
var validators = parser.GetValidatedEntities().ToList();
7272
var hasWebhooks = mutators.Count > 0 || validators.Count > 0 || parser.GetConvertedEntities().Any();
7373

74-
AnsiConsole.Console.MarkupLine("[green]Generate RBAC rules.[/]");
74+
console.MarkupLine("[green]Generate RBAC rules.[/]");
7575
new RbacGenerator(parser, format).Generate(result);
7676

77-
AnsiConsole.Console.MarkupLine("[green]Generate Dockerfile.[/]");
77+
console.MarkupLine("[green]Generate Dockerfile.[/]");
7878
new DockerfileGenerator(hasWebhooks).Generate(result);
7979

8080
if (hasWebhooks)
8181
{
82-
AnsiConsole.Console.MarkupLine(
82+
console.MarkupLine(
8383
"[yellow]The operator contains webhooks of some sort, generating webhook operator specific resources.[/]");
8484

85-
AnsiConsole.Console.MarkupLine("[green]Generate CA and Server certificates.[/]");
85+
console.MarkupLine("[green]Generate CA and Server certificates.[/]");
8686
new CertificateGenerator(name, $"{name}-system").Generate(result);
8787

88-
AnsiConsole.Console.MarkupLine("[green]Generate Deployment and Service.[/]");
88+
console.MarkupLine("[green]Generate Deployment and Service.[/]");
8989
new WebhookDeploymentGenerator(format).Generate(result);
9090

9191
var caBundle =
9292
Encoding.ASCII.GetBytes(
9393
Convert.ToBase64String(Encoding.ASCII.GetBytes(result["ca.pem"].ToString() ?? string.Empty)));
9494

95-
AnsiConsole.Console.MarkupLine("[green]Generate Validation Webhooks.[/]");
95+
console.MarkupLine("[green]Generate Validation Webhooks.[/]");
9696
new ValidationWebhookGenerator(validators, caBundle, format).Generate(result);
9797

98-
AnsiConsole.Console.MarkupLine("[green]Generate Mutation Webhooks.[/]");
98+
console.MarkupLine("[green]Generate Mutation Webhooks.[/]");
9999
new MutationWebhookGenerator(mutators, caBundle, format).Generate(result);
100100

101-
AnsiConsole.Console.MarkupLine("[green]Generate CRDs.[/]");
101+
console.MarkupLine("[green]Generate CRDs.[/]");
102102
new CrdGenerator(parser, caBundle, format).Generate(result);
103103
}
104104
else
105105
{
106-
AnsiConsole.Console.MarkupLine("[green]Generate Deployment.[/]");
106+
console.MarkupLine("[green]Generate Deployment.[/]");
107107
new DeploymentGenerator(format).Generate(result);
108108

109-
AnsiConsole.Console.MarkupLine("[green]Generate CRDs.[/]");
109+
console.MarkupLine("[green]Generate CRDs.[/]");
110110
new CrdGenerator(parser, Array.Empty<byte>(), format).Generate(result);
111111
}
112112

@@ -120,7 +120,7 @@ private static async Task Handler(InvocationContext ctx)
120120
{
121121
NamePrefix = $"{name}-",
122122
Namespace = $"{name}-system",
123-
Labels = new KustomizationCommonLabels(new Dictionary<string, string> { { "operator", name }, }),
123+
Labels = [new KustomizationCommonLabels(new Dictionary<string, string> { { "operator", name }, })],
124124
Resources = result.DefaultFormatFiles.ToList(),
125125
Images =
126126
new List<KustomizationImage>
@@ -156,7 +156,7 @@ private static async Task Handler(InvocationContext ctx)
156156
{
157157
if (ctx.ParseResult.GetValueForOption(Options.ClearOutputPath))
158158
{
159-
AnsiConsole.Console.MarkupLine("[yellow]Clear output path.[/]");
159+
console.MarkupLine("[yellow]Clear output path.[/]");
160160
try
161161
{
162162
Directory.Delete(outPath, true);
@@ -167,11 +167,11 @@ private static async Task Handler(InvocationContext ctx)
167167
}
168168
catch (Exception e)
169169
{
170-
AnsiConsole.Console.MarkupLine($"[red]Could not clear output path: {e.Message}[/]");
170+
console.MarkupLine($"[red]Could not clear output path: {e.Message}[/]");
171171
}
172172
}
173173

174-
AnsiConsole.Console.MarkupLine($"[green]Write output to {outPath}.[/]");
174+
console.MarkupLine($"[green]Write output to {outPath}.[/]");
175175
await result.Write(outPath);
176176
}
177177
else

test/KubeOps.Cli.Test/Management/Install.Integration.Test.cs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
using System.CommandLine;
22
using System.CommandLine.Invocation;
3+
using System.Text;
4+
5+
using FluentAssertions;
36

47
using k8s;
58

9+
using KubeOps.Cli.Commands.Generator;
610
using KubeOps.Cli.Commands.Management;
711

812
using Spectre.Console.Testing;
@@ -15,8 +19,9 @@ public class InstallIntegrationTest
1519
Path.Join(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "examples", "Operator",
1620
"Operator.csproj");
1721

18-
[Fact(Skip =
19-
"For some reason, the MetadataReferences are not loaded when the assembly parser is used from a test project.")]
22+
[Fact
23+
(Skip = "For some reason, the MetadataReferences are not loaded when the assembly parser is used from a test project.")
24+
]
2025
public async Task Should_Install_Crds_In_Cluster()
2126
{
2227
var console = new TestConsole();
@@ -27,4 +32,56 @@ public async Task Should_Install_Crds_In_Cluster()
2732

2833
await Install.Handler(console, client, ctx);
2934
}
35+
36+
[Fact
37+
(Skip = "We need to think of a good way to validate the kustomization given there is not real schema published")
38+
]
39+
public async Task Should_Generate_Valid_Installers_In_Cluster()
40+
{
41+
var console = new TestConsole();
42+
var client = new Kubernetes(KubernetesClientConfiguration.BuildDefaultConfig());
43+
var cmd = OperatorGenerator.Command;
44+
var ctx = new InvocationContext(
45+
cmd.Parse("operator", "test", ProjectPath));
46+
47+
await OperatorGenerator.Handler(console, ctx);
48+
console.Output.Should().NotBeNull();
49+
var separator = console.Lines.GroupBy(x => x).OrderByDescending(x => x.Count()).First().Key;
50+
var groups = new List<string>();
51+
var sb = new StringBuilder();
52+
foreach (string consoleLine in console.Lines)
53+
{
54+
if (consoleLine.Equals(separator))
55+
{
56+
groups.Add(sb.ToString());
57+
sb = new StringBuilder();
58+
}
59+
else
60+
{
61+
sb.AppendLine(consoleLine);
62+
}
63+
}
64+
65+
foreach (string s in groups.Skip(1))
66+
{
67+
// Verify its valid kubernetes yaml
68+
var lines = s.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
69+
// first line should be the filename:
70+
var filename = lines[0].Trim();
71+
var content = lines[1..].ToList();
72+
var file = filename.Split(" ")[1].Trim();
73+
var fileContent = string.Join(Environment.NewLine, content);
74+
// Assert there is a file name and valid kubernetesyaml
75+
file.Should().NotBeNullOrEmpty();
76+
fileContent.Should().NotBeNullOrEmpty();
77+
78+
KubernetesYaml.Deserialize<dynamic>(fileContent)
79+
.Should().NotBeNull();
80+
81+
// Assert the file named kustomization.yaml exists and is a valid Kustomization file
82+
if (file.Equals("kustomization.yaml"))
83+
{
84+
}
85+
}
86+
}
3087
}

0 commit comments

Comments
 (0)