diff --git a/docs/docs/operator/cli.mdx b/docs/docs/operator/cli.mdx index a0c44d36..dbec1c8b 100644 --- a/docs/docs/operator/cli.mdx +++ b/docs/docs/operator/cli.mdx @@ -45,6 +45,10 @@ This command: - Generates the necessary CRDs - Installs them into your current Kubernetes context +:::tip[Solution files] +Instead of a `.csproj` file, you can pass a `.sln` or `.slnx` solution file. The CLI will compile all projects in the solution and search them for custom resources. +::: + :::warning[Production Usage] The `install` and `uninstall` commands are primarily intended for development purposes. In production, you should use the generated Kubernetes manifests and your preferred deployment method (e.g., Helm, Kustomize, or GitOps). ::: diff --git a/src/KubeOps.Cli/Arguments.cs b/src/KubeOps.Cli/Arguments.cs index cc70db2b..359f2191 100644 --- a/src/KubeOps.Cli/Arguments.cs +++ b/src/KubeOps.Cli/Arguments.cs @@ -8,7 +8,7 @@ namespace KubeOps.Cli; internal static class Arguments { - public static readonly Argument SolutionOrProjectFile = new("sln/csproj file") + public static readonly Argument SolutionOrProjectFile = new("sln/slnx/csproj file") { DefaultValueFactory = result => { @@ -24,10 +24,17 @@ var slnFile "*.sln") .Select(f => new FileInfo(f)) .FirstOrDefault(); - var file = (projectFile, slnFile) switch + var slnxFile + = Directory.EnumerateFiles( + Directory.GetCurrentDirectory(), + "*.slnx") + .Select(f => new FileInfo(f)) + .FirstOrDefault(); + var file = (projectFile, slnFile, slnxFile) switch { - ({ } prj, _) => prj, - (_, { } sln) => sln, + ({ } prj, _, _) => prj, + (_, { } sln, _) => sln, + (_, _, { } slnx) => slnx, _ => null, }; @@ -40,8 +47,8 @@ var slnFile return new FileInfo("not-found"); }, Description = "A solution or project file where entities are located. " + - "If omitted, the current directory is searched for a *.csproj or *.sln file. " + - "If an *.sln file is used, all projects in the solution (with the newest framework) will be searched for entities. " + + "If omitted, the current directory is searched for a *.csproj, *.sln, or *.slnx file. " + + "If an *.sln or *.slnx file is used, all projects in the solution (with the newest framework) will be searched for entities. " + "This behaviour can be filtered by using the --project and --target-framework option.", }; diff --git a/src/KubeOps.Cli/Commands/Generator/OperatorGenerator.cs b/src/KubeOps.Cli/Commands/Generator/OperatorGenerator.cs index 2229e37c..e4991905 100644 --- a/src/KubeOps.Cli/Commands/Generator/OperatorGenerator.cs +++ b/src/KubeOps.Cli/Commands/Generator/OperatorGenerator.cs @@ -68,13 +68,13 @@ internal static async Task Handler(IAnsiConsole console, ParseResult parseR var parser = file switch { { Extension: ".csproj", Exists: true } => await AssemblyLoader.ForProject(console, file), - { Extension: ".sln", Exists: true } => await AssemblyLoader.ForSolution( + { Extension: ".sln" or ".slnx", Exists: true } => await AssemblyLoader.ForSolution( console, file, parseResult.GetValue(Options.SolutionProjectRegex), parseResult.GetValue(Options.TargetFramework)), { Exists: false } => throw new FileNotFoundException($"The file {file.Name} does not exist."), - _ => throw new NotSupportedException("Only *.csproj and *.sln files are supported."), + _ => throw new NotSupportedException("Only *.csproj, *.sln, and *.slnx files are supported."), }; var mutators = parser.GetMutatedEntities().ToList(); @@ -175,11 +175,11 @@ internal static async Task Handler(IAnsiConsole console, ParseResult parseR } catch (Exception e) { - console.MarkupLine($"[red]Could not clear output path: {e.Message}[/]"); + console.MarkupLineInterpolated($"[red]Could not clear output path: {e.Message}[/]"); } } - console.MarkupLine($"[green]Write output to {outPath}.[/]"); + console.MarkupLineInterpolated($"[green]Write output to {outPath}.[/]"); await result.Write(outPath); } else diff --git a/src/KubeOps.Cli/Commands/Management/Install.cs b/src/KubeOps.Cli/Commands/Management/Install.cs index b0e67dcb..bd6d7c53 100644 --- a/src/KubeOps.Cli/Commands/Management/Install.cs +++ b/src/KubeOps.Cli/Commands/Management/Install.cs @@ -52,13 +52,13 @@ internal static async Task Handler(IAnsiConsole console, IKubernetes client var parser = file switch { { Extension: ".csproj", Exists: true } => await AssemblyLoader.ForProject(console, file), - { Extension: ".sln", Exists: true } => await AssemblyLoader.ForSolution( + { Extension: ".sln" or ".slnx", Exists: true } => await AssemblyLoader.ForSolution( console, file, parseResult.GetValue(Options.SolutionProjectRegex), parseResult.GetValue(Options.TargetFramework)), { Exists: false } => throw new FileNotFoundException($"The file {file.Name} does not exist."), - _ => throw new NotSupportedException("Only *.csproj and *.sln files are supported."), + _ => throw new NotSupportedException("Only *.csproj, *.sln, and *.slnx files are supported."), }; console.WriteLine($"Install CRDs from {file.Name}."); @@ -103,13 +103,13 @@ internal static async Task Handler(IAnsiConsole console, IKubernetes client } catch (HttpOperationException) { - console.WriteLine( + console.MarkupLineInterpolated( $"""[red]There was a http (api) error while installing "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]"""); throw; } catch (Exception) { - console.WriteLine( + console.MarkupLineInterpolated( $"""[red]There was an error while installing "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]"""); throw; } diff --git a/src/KubeOps.Cli/Commands/Management/Uninstall.cs b/src/KubeOps.Cli/Commands/Management/Uninstall.cs index e938e8f5..0615dbcd 100644 --- a/src/KubeOps.Cli/Commands/Management/Uninstall.cs +++ b/src/KubeOps.Cli/Commands/Management/Uninstall.cs @@ -52,13 +52,13 @@ internal static async Task Handler(IAnsiConsole console, IKubernetes client var parser = file switch { { Extension: ".csproj", Exists: true } => await AssemblyLoader.ForProject(console, file), - { Extension: ".sln", Exists: true } => await AssemblyLoader.ForSolution( + { Extension: ".sln" or ".slnx", Exists: true } => await AssemblyLoader.ForSolution( console, file, parseResult.GetValue(Options.SolutionProjectRegex), parseResult.GetValue(Options.TargetFramework)), { Exists: false } => throw new FileNotFoundException($"The file {file.Name} does not exist."), - _ => throw new NotSupportedException("Only *.csproj and *.sln files are supported."), + _ => throw new NotSupportedException("Only *.csproj, *.sln, and *.slnx files are supported."), }; console.WriteLine($"Uninstall CRDs from {file.Name}."); @@ -100,13 +100,13 @@ internal static async Task Handler(IAnsiConsole console, IKubernetes client } catch (HttpOperationException) { - console.WriteLine( + console.MarkupLineInterpolated( $"""[red]There was a http (api) error while uninstalling "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]"""); throw; } catch (Exception) { - console.WriteLine( + console.MarkupLineInterpolated( $"""[red]There was an error while uninstalling "{crd.Spec.Group}/{crd.Spec.Names.Kind}".[/]"""); throw; } diff --git a/src/KubeOps.Cli/Transpilation/AssemblyLoader.cs b/src/KubeOps.Cli/Transpilation/AssemblyLoader.cs index 8812fdec..51fc7a74 100644 --- a/src/KubeOps.Cli/Transpilation/AssemblyLoader.cs +++ b/src/KubeOps.Cli/Transpilation/AssemblyLoader.cs @@ -116,7 +116,10 @@ public static Task ForSolution( .Select(async p => { console.MarkupLineInterpolated( - $"Load compilation context for [aqua]{p.name}[/]{(p.tfm.Length > 0 ? $" [grey]{p.tfm}[/]" : string.Empty)}."); + p.tfm.Length > 0 + ? (FormattableString)$"Load compilation context for [aqua]{p.name}[/] [grey]{p.tfm}[/]." + : (FormattableString)$"Load compilation context for [aqua]{p.name}[/]."); + var compilation = await p.project.GetCompilationAsync(); console.MarkupLineInterpolated($"[green]Compilation context loaded for {p.name}.[/]"); if (compilation is null) @@ -124,9 +127,11 @@ public static Task ForSolution( throw new AggregateException("Compilation could not be found."); } - using var assemblyStream = new MemoryStream(); + await using var assemblyStream = new MemoryStream(); console.MarkupLineInterpolated( - $"Start compilation for [aqua]{p.name}[/]{(p.tfm.Length > 0 ? $" [grey]{p.tfm}[/]" : string.Empty)}."); + p.tfm.Length > 0 + ? (FormattableString)$"Start compilation for [aqua]{p.name}[/] [grey]{p.tfm}[/]." + : (FormattableString)$"Start compilation for [aqua]{p.name}[/]."); switch (compilation.Emit(assemblyStream)) { case { Success: false, Diagnostics: var diag }: