Browse Source

Merge pull request #40 from Timmoth/Desktop-Tree-Command

Add Desktop Tree Command
Tim Jones 2 months ago
parent
commit
dea2ec00bd

+ 52 - 0
RackPeek.Domain/Resources/Hardware/Desktops/GetDesktopSystemTreeUseCase.cs

@@ -0,0 +1,52 @@
+using RackPeek.Domain.Resources.Hardware.Models;
+using RackPeek.Domain.Resources.Services;
+using RackPeek.Domain.Resources.SystemResources;
+
+namespace RackPeek.Domain.Resources.Hardware.Desktops;
+
+public class GetDesktopSystemTreeUseCase(
+    IHardwareRepository hardwareRepository,
+    ISystemRepository systemRepository,
+    IServiceRepository serviceRepository) : IUseCase
+{
+    public async Task<HardwareDependencyTree?> ExecuteAsync(string hardwareName)
+    {
+        if (string.IsNullOrWhiteSpace(hardwareName))
+            return null;
+
+        var desktop = await hardwareRepository.GetByNameAsync(hardwareName) as Desktop;
+        if (desktop is null)
+            return null;
+
+        return await BuildDependencyTreeAsync(desktop);
+    }
+
+    private async Task<HardwareDependencyTree> BuildDependencyTreeAsync(Desktop desktop)
+    {
+        var systems = await systemRepository.GetByPhysicalHostAsync(desktop.Name);
+
+        var systemTrees = new List<SystemDependencyTree>();
+        foreach (var system in systems)
+            systemTrees.Add(await BuildSystemDependencyTreeAsync(system));
+
+        return new HardwareDependencyTree(desktop, systemTrees);
+    }
+
+    private async Task<SystemDependencyTree> BuildSystemDependencyTreeAsync(SystemResource system)
+    {
+        var services = await serviceRepository.GetBySystemHostAsync(system.Name);
+        return new SystemDependencyTree(system, services);
+    }
+}
+
+public sealed class HardwareDependencyTree(Desktop hardware, IEnumerable<SystemDependencyTree> systems)
+{
+    public Desktop Hardware { get; } = hardware;
+    public IEnumerable<SystemDependencyTree> Systems { get; } = systems;
+}
+
+public sealed class SystemDependencyTree(SystemResource system, IEnumerable<Service> services)
+{
+    public SystemResource System { get; } = system;
+    public IEnumerable<Service> Services { get; } = services;
+}

+ 2 - 0
RackPeek/CliBootstrap.cs

@@ -271,6 +271,8 @@ public static class CliBootstrap
                     desktops.AddCommand<DesktopDeleteCommand>("del");
                     desktops.AddCommand<DesktopReportCommand>("summary")
                         .WithDescription("Show desktop hardware report");
+                    desktops.AddCommand<DesktopTreeCommand>("tree");
+
 
                     // CPU
                     desktops.AddBranch("cpu", cpu =>

+ 35 - 0
RackPeek/Commands/Desktops/DesktopTreeCommand.cs

@@ -0,0 +1,35 @@
+using RackPeek.Domain.Resources.Hardware.Desktops;
+using Spectre.Console;
+using Spectre.Console.Cli;
+
+namespace RackPeek.Commands.Desktops;
+
+public sealed class DesktopTreeCommand(GetDesktopSystemTreeUseCase useCase)
+    : AsyncCommand<DesktopNameSettings>
+{
+    public override async Task<int> ExecuteAsync(
+        CommandContext context,
+        DesktopNameSettings settings,
+        CancellationToken cancellationToken)
+    {
+        var tree = await useCase.ExecuteAsync(settings.Name);
+
+        if (tree is null)
+        {
+            AnsiConsole.MarkupLine($"[red]Error:[/] Desktop '{settings.Name}' not found.");
+            return -1;
+        }
+
+        var root = new Tree($"[bold]{tree.Hardware.Name}[/]");
+
+        foreach (var system in tree.Systems)
+        {
+            var systemNode = root.AddNode($"[green]System:[/] {system.System.Name}");
+            foreach (var service in system.Services)
+                systemNode.AddNode($"[green]Service:[/] {service.Name}");
+        }
+
+        AnsiConsole.Write(root);
+        return 0;
+    }
+}

+ 93 - 0
Tests/EndToEnd/DesktopYamlE2ETest.cs

@@ -0,0 +1,93 @@
+using Tests.EndToEnd.Infra;
+using Xunit.Abstractions;
+
+namespace Tests.EndToEnd;
+
+[Collection("Yaml CLI tests")]
+public class DesktopYamlE2ETests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)
+    : IClassFixture<TempYamlCliFixture>
+{
+    private async Task<(string, string)> ExecuteAsync(params string[] args)
+    {
+        outputHelper.WriteLine($"rpk {string.Join(" ", args)}");
+
+        var inputArgs = args.ToArray();
+        var output = await YamlCliTestHost.RunAsync(
+            inputArgs,
+            fs.Root,
+            outputHelper
+        );
+
+        outputHelper.WriteLine(output);
+
+        var yaml = await File.ReadAllTextAsync(Path.Combine(fs.Root, "config.yaml"));
+        return (output, yaml);
+    }
+
+    [Fact]
+    public async Task desktops_cli_workflow_test()
+    {
+        await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), "");
+
+        var (output, yaml) = await ExecuteAsync("desktops", "add", "workstation01");
+        Assert.Equal("Desktop 'workstation01' added.\n", output);
+        Assert.Contains("name: workstation01", yaml);
+    }
+
+    [Fact]
+    public async Task desktops_tree_cli_workflow_test()
+    {
+        await File.WriteAllTextAsync(Path.Combine(fs.Root, "config.yaml"), "");
+
+        // Add desktop
+        var (output, yaml) = await ExecuteAsync("desktops", "add", "workstation01");
+        Assert.Equal("Desktop 'workstation01' added.\n", output);
+        Assert.Contains("name: workstation01", yaml);
+
+        // Add systems
+        (output, yaml) = await ExecuteAsync("systems", "add", "sys01");
+        Assert.Equal("System 'sys01' added.\n", output);
+
+        (output, yaml) = await ExecuteAsync("systems", "add", "sys02");
+        Assert.Equal("System 'sys02' added.\n", output);
+
+        (output, yaml) = await ExecuteAsync("systems", "add", "sys03");
+        Assert.Equal("System 'sys03' added.\n", output);
+
+        // Attach systems to desktop
+        (output, yaml) = await ExecuteAsync("systems", "set", "sys01", "--runs-on", "workstation01");
+        Assert.Equal("System 'sys01' updated.\n", output);
+
+        (output, yaml) = await ExecuteAsync("systems", "set", "sys02", "--runs-on", "workstation01");
+        Assert.Equal("System 'sys02' updated.\n", output);
+
+        (output, yaml) = await ExecuteAsync("systems", "set", "sys03", "--runs-on", "workstation01");
+        Assert.Equal("System 'sys03' updated.\n", output);
+
+        // Add services
+        (output, yaml) = await ExecuteAsync("services", "add", "immich");
+        Assert.Equal("Service 'immich' added.\n", output);
+
+        (output, yaml) = await ExecuteAsync("services", "add", "paperless");
+        Assert.Equal("Service 'paperless' added.\n", output);
+
+        // Attach services to sys01
+        (output, yaml) = await ExecuteAsync("services", "set", "immich", "--runs-on", "sys01");
+        Assert.Equal("Service 'immich' updated.\n", output);
+
+        (output, yaml) = await ExecuteAsync("services", "set", "paperless", "--runs-on", "sys01");
+        Assert.Equal("Service 'paperless' updated.\n", output);
+
+        // Render tree
+        (output, yaml) = await ExecuteAsync("desktops", "tree", "workstation01");
+        Assert.Equal("""
+                     workstation01
+                     ├── System: sys01
+                     │   ├── Service: immich
+                     │   └── Service: paperless
+                     ├── System: sys02
+                     └── System: sys03
+
+                     """, output);
+    }
+}