Tim Jones 1 месяц назад
Родитель
Сommit
777964589c
31 измененных файлов с 1657 добавлено и 437 удалено
  1. 8 0
      RackPeek.Domain/Resources/Servers/ICpuResource.cs
  2. 8 0
      RackPeek.Domain/Resources/Servers/IDriveResource.cs
  3. 8 0
      RackPeek.Domain/Resources/Servers/IGpuResource.cs
  4. 8 0
      RackPeek.Domain/Resources/Servers/INicResource.cs
  5. 8 0
      RackPeek.Domain/Resources/Servers/IPortResource.cs
  6. 0 25
      RackPeek.Domain/Resources/Servers/Server.cs
  7. 32 9
      RackPeek.Web/Dockerfile
  8. 15 0
      RackPeek.Web/Program.cs
  9. 265 0
      Shared.Rcl/wwwroot/schemas/v1/schema.v1.json
  10. 1 1
      Tests/EndToEnd/FirewallTests/FirewallCommandTests.cs
  11. 1 1
      Tests/EndToEnd/FirewallTests/FirewallErrorTests.cs
  12. 1 1
      Tests/EndToEnd/FirewallTests/FirewallWorkflowTests.cs
  13. 1 1
      Tests/EndToEnd/SystemTests/SystemCommandTests.cs
  14. 1 1
      Tests/EndToEnd/SystemTests/SystemErrorTests.cs
  15. 1 1
      Tests/EndToEnd/SystemTests/SystemWorkflowTests.cs
  16. 32 0
      Tests/TestConfigs/v1/01-server.yaml
  17. 14 0
      Tests/TestConfigs/v1/02-firewall.yaml
  18. 14 0
      Tests/TestConfigs/v1/03-router.yaml
  19. 14 0
      Tests/TestConfigs/v1/04-switch.yaml
  20. 8 0
      Tests/TestConfigs/v1/05-accesspoint.yaml
  21. 8 0
      Tests/TestConfigs/v1/06-ups.yaml
  22. 22 0
      Tests/TestConfigs/v1/07-desktop.yaml
  23. 15 0
      Tests/TestConfigs/v1/08-laptop.yaml
  24. 10 0
      Tests/TestConfigs/v1/09-service.yaml
  25. 13 0
      Tests/TestConfigs/v1/10-system.yaml
  26. 428 0
      Tests/TestConfigs/v1/11-demo-config.yaml
  27. 55 0
      Tests/Tests.csproj
  28. 136 0
      Tests/Yaml/SchemaTests.cs
  29. 265 0
      Tests/schemas/schema.v1.json
  30. 265 0
      schemas/v1/schema.v1.json
  31. 0 397
      servers.yaml

+ 8 - 0
RackPeek.Domain/Resources/Servers/ICpuResource.cs

@@ -0,0 +1,8 @@
+using RackPeek.Domain.Resources.SubResources;
+
+namespace RackPeek.Domain.Resources.Servers;
+
+public interface ICpuResource
+{
+    public List<Cpu>? Cpus { get; set; }
+}

+ 8 - 0
RackPeek.Domain/Resources/Servers/IDriveResource.cs

@@ -0,0 +1,8 @@
+using RackPeek.Domain.Resources.SubResources;
+
+namespace RackPeek.Domain.Resources.Servers;
+
+public interface IDriveResource
+{
+    public List<Drive>? Drives { get; set; }
+}

+ 8 - 0
RackPeek.Domain/Resources/Servers/IGpuResource.cs

@@ -0,0 +1,8 @@
+using RackPeek.Domain.Resources.SubResources;
+
+namespace RackPeek.Domain.Resources.Servers;
+
+public interface IGpuResource
+{
+    public List<Gpu>? Gpus { get; set; }
+}

+ 8 - 0
RackPeek.Domain/Resources/Servers/INicResource.cs

@@ -0,0 +1,8 @@
+using RackPeek.Domain.Resources.SubResources;
+
+namespace RackPeek.Domain.Resources.Servers;
+
+public interface INicResource
+{
+    public List<Nic>? Nics { get; set; }
+}

+ 8 - 0
RackPeek.Domain/Resources/Servers/IPortResource.cs

@@ -0,0 +1,8 @@
+using RackPeek.Domain.Resources.SubResources;
+
+namespace RackPeek.Domain.Resources.Servers;
+
+public interface IPortResource
+{
+    public List<Port>? Ports { get; set; }
+}

+ 0 - 25
RackPeek.Domain/Resources/Servers/Server.cs

@@ -11,29 +11,4 @@ public class Server : Hardware.Hardware, ICpuResource, IDriveResource, IGpuResou
     public List<Drive>? Drives { get; set; }
     public List<Gpu>? Gpus { get; set; }
     public List<Nic>? Nics { get; set; }
-}
-
-public interface ICpuResource
-{
-    public List<Cpu>? Cpus { get; set; }
-}
-
-public interface IDriveResource
-{
-    public List<Drive>? Drives { get; set; }
-}
-
-public interface IPortResource
-{
-    public List<Port>? Ports { get; set; }
-}
-
-public interface IGpuResource
-{
-    public List<Gpu>? Gpus { get; set; }
-}
-
-public interface INicResource
-{
-    public List<Nic>? Nics { get; set; }
 }

+ 32 - 9
RackPeek.Web/Dockerfile

@@ -2,31 +2,54 @@
 USER $APP_UID
 WORKDIR /app
 EXPOSE 8080
-EXPOSE 8081
 
 FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
 ARG BUILD_CONFIGURATION=Release
 WORKDIR /src
+
 COPY ["RackPeek.Web/RackPeek.Web.csproj", "RackPeek.Web/"]
 COPY ["RackPeek.Domain/RackPeek.Domain.csproj", "RackPeek.Domain/"]
 COPY ["Shared.Rcl/Shared.Rcl.csproj", "Shared.Rcl/"]
 COPY ["RackPeek/RackPeek.csproj", "RackPeek/"]
+
 RUN dotnet restore "RackPeek.Web/RackPeek.Web.csproj"
+
 COPY . .
-WORKDIR "/src/RackPeek.Web"
-RUN dotnet build "./RackPeek.Web.csproj" -c $BUILD_CONFIGURATION -o /app/build
 
-FROM build AS publish
-ARG BUILD_CONFIGURATION=Release
-RUN dotnet publish "./RackPeek.Web.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+# Build Web
+WORKDIR "/src/RackPeek.Web"
+RUN dotnet publish "./RackPeek.Web.csproj" -c $BUILD_CONFIGURATION -o /app/web-publish /p:UseAppHost=false
 
+# Build CLI (framework dependent, small)
+WORKDIR "/src/RackPeek"
+RUN dotnet publish "./RackPeek.csproj" -c $BUILD_CONFIGURATION -o /app/cli-publish /p:UseAppHost=false
 FROM base AS final
+
 WORKDIR /app
 
-RUN mkdir -p /app/config && chown -R $APP_UID /app/config
+USER root
 
+# Create shared config dir
+RUN mkdir -p /app/config && chown -R $APP_UID /app/config
 VOLUME ["/app/config"]
 
-COPY --from=publish /app/publish .
-ENTRYPOINT ["dotnet", "RackPeek.Web.dll"]
+# Copy Web app
+COPY --from=build /app/web-publish .
+
+# Copy CLI publish output
+COPY --from=build /app/cli-publish /usr/local/bin/rpk-dir
+
+# If AppHost exists, use it. Otherwise create wrapper.
+RUN if [ -f /usr/local/bin/rpk-dir/RackPeek ]; then \
+        mv /usr/local/bin/rpk-dir/RackPeek /usr/local/bin/rpk; \
+    else \
+        echo '#!/bin/sh\nexec dotnet /usr/local/bin/rpk-dir/RackPeek.dll "$@"' > /usr/local/bin/rpk && \
+        chmod +x /usr/local/bin/rpk; \
+    fi
+
+# Set environment variable so CLI uses shared config
+ENV RPK_YAML_DIR=/app/config
+
+USER $APP_UID
 
+ENTRYPOINT ["dotnet", "RackPeek.Web.dll"]

+ 15 - 0
RackPeek.Web/Program.cs

@@ -17,6 +17,8 @@ public class Program
             builder.Configuration
         );
 
+        builder.Configuration.AddJsonFile($"appsettings.json", optional: true, reloadOnChange: false);
+        
         var yamlDir = builder.Configuration.GetValue<string>("RPK_YAML_DIR") ?? "./config";
         var yamlFileName = "config.yaml";
 
@@ -103,6 +105,19 @@ public class Program
     public static async Task Main(string[] args)
     {
         var builder = WebApplication.CreateBuilder(args);
+        
+        // Remove default config sources
+        builder.Configuration.Sources.Clear();
+
+        // Re-add manually WITHOUT file watchers
+        builder.Configuration
+            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
+            .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json",
+                optional: true,
+                reloadOnChange: false)
+            .AddEnvironmentVariables()
+            .AddCommandLine(args);
+        
         var app = await BuildApp(builder);
         await app.RunAsync();
     }

+ 265 - 0
Shared.Rcl/wwwroot/schemas/v1/schema.v1.json

@@ -0,0 +1,265 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "$id": "https://timmoth.github.io/RackPeek/schemas/v1/schema.v1.json",
+  "title": "RackPeek Infrastructure Specification",
+  "type": "object",
+  "additionalProperties": false,
+  "required": ["version", "resources"],
+  "properties": {
+    "version": {
+      "type": "integer",
+      "const": 1
+    },
+    "resources": {
+      "type": "array",
+      "items": {
+        "oneOf": [
+          { "$ref": "#/$defs/server" },
+          { "$ref": "#/$defs/firewall" },
+          { "$ref": "#/$defs/router" },
+          { "$ref": "#/$defs/switch" },
+          { "$ref": "#/$defs/accessPoint" },
+          { "$ref": "#/$defs/ups" },
+          { "$ref": "#/$defs/desktop" },
+          { "$ref": "#/$defs/laptop" },
+          { "$ref": "#/$defs/service" },
+          { "$ref": "#/$defs/system" }
+        ]
+      }
+    }
+  },
+  "$defs": {
+    "ram": {
+      "type": "object",
+      "required": ["size"],
+      "additionalProperties": false,
+      "properties": {
+        "size": { "type": "number", "minimum": 0 },
+        "mts": { "type": "integer", "minimum": 0 }
+      }
+    },
+    "cpu": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "model": { "type": "string" },
+        "cores": { "type": "integer", "minimum": 1 },
+        "threads": { "type": "integer", "minimum": 1 }
+      }
+    },
+    "drive": {
+      "type": "object",
+      "required": ["size"],
+      "additionalProperties": false,
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": ["nvme","ssd","hdd","sas","sata","usb","sdcard","micro-sd"]
+        },
+        "size": { "type": "number", "minimum": 1 }
+      }
+    },
+    "gpu": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "model": { "type": "string" },
+        "vram": { "type": "number", "minimum": 0 }
+      }
+    },
+    "nic": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "rj45","sfp","sfp+","sfp28","sfp56",
+            "qsfp+","qsfp28","qsfp56","qsfp-dd",
+            "osfp","xfp","cx4","mgmt"
+          ]
+        },
+        "speed": { "type": "number", "minimum": 0 },
+        "ports": { "type": "integer", "minimum": 1 }
+      }
+    },
+    "port": {
+      "type": "object",
+      "required": ["type","speed","count"],
+      "additionalProperties": false,
+      "properties": {
+        "type": { "type": "string" },
+        "speed": { "type": "number", "minimum": 0 },
+        "count": { "type": "integer", "minimum": 1 }
+      }
+    },
+    "network": {
+      "type": "object",
+      "required": ["ip","port","protocol"],
+      "additionalProperties": false,
+      "properties": {
+        "ip": {
+          "type": "string",
+          "pattern": "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}$"
+        },
+        "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
+        "protocol": { "type": "string", "enum": ["TCP","UDP"] },
+        "url": { "type": "string" }
+      }
+    },
+
+    "server": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Server" },
+        "name": { "type": "string" },
+        "tags": { "type": "array", "items": { "type": "string" } },
+        "notes": { "type": "string" },
+        "runsOn": { "type": "string" },
+        "ram": { "$ref": "#/$defs/ram" },
+        "ipmi": { "type": "boolean" },
+        "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+        "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } },
+        "gpus": { "type": "array", "items": { "$ref": "#/$defs/gpu" } },
+        "nics": { "type": "array", "items": { "$ref": "#/$defs/nic" } }
+      }
+    },
+
+    "desktop": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Desktop" },
+        "name": { "type": "string" },
+        "notes": { "type": "string" },
+        "ram": { "$ref": "#/$defs/ram" },
+        "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+        "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } },
+        "gpus": { "type": "array", "items": { "$ref": "#/$defs/gpu" } },
+        "nics": { "type": "array", "items": { "$ref": "#/$defs/nic" } }
+      }
+    },
+
+    "laptop": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Laptop" },
+        "name": { "type": "string" },
+        "notes": { "type": "string" },
+        "ram": { "$ref": "#/$defs/ram" },
+        "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+        "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+      }
+    },
+
+    "firewall": {
+      "type": "object",
+      "required": ["kind","name","ports"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Firewall" },
+        "name": { "type": "string" },
+        "model": { "type": "string" },
+        "managed": { "type": "boolean" },
+        "poe": { "type": "boolean" },
+        "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+      }
+    },
+
+    "router": {
+      "type": "object",
+      "required": ["kind","name","ports"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Router" },
+        "name": { "type": "string" },
+        "model": { "type": "string" },
+        "managed": { "type": "boolean" },
+        "poe": { "type": "boolean" },
+        "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+      }
+    },
+
+    "switch": {
+      "type": "object",
+      "required": ["kind","name","ports"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Switch" },
+        "name": { "type": "string" },
+        "model": { "type": "string" },
+        "managed": { "type": "boolean" },
+        "poe": { "type": "boolean" },
+        "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+      }
+    },
+
+    "accessPoint": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "AccessPoint" },
+        "name": { "type": "string" },
+        "tags": { "type": "array", "items": { "type": "string" } },
+        "model": { "type": "string" },
+        "speed": { "type": "number" }
+      }
+    },
+
+    "ups": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Ups" },
+        "name": { "type": "string" },
+        "tags": { "type": "array", "items": { "type": "string" } },
+        "model": { "type": "string" },
+        "va": { "type": "integer", "minimum": 1 }
+      }
+    },
+
+    "service": {
+      "type": "object",
+      "required": ["kind","name","network","runsOn"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Service" },
+        "name": { "type": "string" },
+        "runsOn": { "type": "string" },
+        "network": { "$ref": "#/$defs/network" }
+      }
+    },
+
+    "system": {
+      "type": "object",
+      "required": ["kind","name","type","os","cores","ram"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "System" },
+        "name": { "type": "string" },
+        "notes": { "type": "string" },
+        "runsOn": { "type": "string" },
+        "type": {
+          "type": "string",
+          "enum": [
+            "baremetal","Baremetal",
+            "hypervisor","Hypervisor",
+            "vm","VM",
+            "container","embedded","cloud","other"
+          ]
+        },
+        "os": { "type": "string" },
+        "cores": { "type": "integer", "minimum": 1 },
+        "ram": { "type": "number", "minimum": 0 },
+        "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+      }
+    }
+  }
+}

+ 1 - 1
Tests/EndToEnd/FirewallTests/FirewallCommandTests.cs

@@ -1,7 +1,7 @@
 using Tests.EndToEnd.Infra;
 using Xunit.Abstractions;
 
-namespace Tests.EndToEnd;
+namespace Tests.EndToEnd.FirewallTests;
 
 [Collection("Yaml CLI tests")]
 public class FirewallCommandTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)

+ 1 - 1
Tests/EndToEnd/FirewallTests/FirewallErrorTests.cs

@@ -1,7 +1,7 @@
 using Tests.EndToEnd.Infra;
 using Xunit.Abstractions;
 
-namespace Tests.EndToEnd;
+namespace Tests.EndToEnd.FirewallTests;
 
 [Collection("Yaml CLI tests")]
 public class FirewallErrorTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)

+ 1 - 1
Tests/EndToEnd/FirewallTests/FirewallWorkflowTests.cs

@@ -1,7 +1,7 @@
 using Tests.EndToEnd.Infra;
 using Xunit.Abstractions;
 
-namespace Tests.EndToEnd;
+namespace Tests.EndToEnd.FirewallTests;
 
 [Collection("Yaml CLI tests")]
 public class FirewallWorkflowTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)

+ 1 - 1
Tests/EndToEnd/SystemTests/SystemCommandTests.cs

@@ -1,7 +1,7 @@
 using Tests.EndToEnd.Infra;
 using Xunit.Abstractions;
 
-namespace Tests.EndToEnd;
+namespace Tests.EndToEnd.SystemTests;
 
 [Collection("Yaml CLI tests")]
 public class SystemCommandTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)

+ 1 - 1
Tests/EndToEnd/SystemTests/SystemErrorTests.cs

@@ -1,7 +1,7 @@
 using Tests.EndToEnd.Infra;
 using Xunit.Abstractions;
 
-namespace Tests.EndToEnd;
+namespace Tests.EndToEnd.SystemTests;
 
 [Collection("Yaml CLI tests")]
 public class SystemErrorTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)

+ 1 - 1
Tests/EndToEnd/SystemTests/SystemWorkflowTests.cs

@@ -1,7 +1,7 @@
 using Tests.EndToEnd.Infra;
 using Xunit.Abstractions;
 
-namespace Tests.EndToEnd;
+namespace Tests.EndToEnd.SystemTests;
 
 [Collection("Yaml CLI tests")]
 public class SystemWorkflowTests(TempYamlCliFixture fs, ITestOutputHelper outputHelper)

+ 32 - 0
Tests/TestConfigs/v1/01-server.yaml

@@ -0,0 +1,32 @@
+version: 1
+resources:
+  - kind: Server
+    name: example-server
+    tags:
+      - production
+      - compute
+    notes: Primary hypervisor host
+    runsOn: rack-a1
+    ram:
+      size: 128
+      mts: 3200
+    ipmi: true
+    cpus:
+      - model: AMD EPYC 7302P
+        cores: 16
+        threads: 32
+    drives:
+      - type: nvme
+        size: 1024
+      - type: ssd
+        size: 2048
+    gpus:
+      - model: NVIDIA RTX 4000
+        vram: 16
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 2
+      - type: sfp+
+        speed: 10
+        ports: 2

+ 14 - 0
Tests/TestConfigs/v1/02-firewall.yaml

@@ -0,0 +1,14 @@
+version: 1
+resources:
+  - kind: Firewall
+    name: example-firewall
+    model: Netgate-6100
+    managed: true
+    poe: false
+    ports:
+      - type: rj45
+        speed: 1
+        count: 4
+      - type: sfp+
+        speed: 10
+        count: 2

+ 14 - 0
Tests/TestConfigs/v1/03-router.yaml

@@ -0,0 +1,14 @@
+version: 1
+resources:
+  - kind: Router
+    name: example-router
+    model: Ubiquiti-ER-4
+    managed: true
+    poe: false
+    ports:
+      - type: rj45
+        speed: 1
+        count: 4
+      - type: sfp
+        speed: 10
+        count: 1

+ 14 - 0
Tests/TestConfigs/v1/04-switch.yaml

@@ -0,0 +1,14 @@
+version: 1
+resources:
+  - kind: Switch
+    name: example-switch
+    model: UniFi-USW-Enterprise-24
+    managed: true
+    poe: true
+    ports:
+      - type: rj45
+        speed: 1
+        count: 12
+      - type: sfp+
+        speed: 10
+        count: 4

+ 8 - 0
Tests/TestConfigs/v1/05-accesspoint.yaml

@@ -0,0 +1,8 @@
+version: 1
+resources:
+  - kind: AccessPoint
+    name: example-accesspoint
+    tags:
+      - wireless
+    model: UniFi-U6-Pro
+    speed: 2.5

+ 8 - 0
Tests/TestConfigs/v1/06-ups.yaml

@@ -0,0 +1,8 @@
+version: 1
+resources:
+  - kind: Ups
+    name: example-ups
+    tags:
+      - power
+    model: APC-SmartUPS-2200
+    va: 2200

+ 22 - 0
Tests/TestConfigs/v1/07-desktop.yaml

@@ -0,0 +1,22 @@
+version: 1
+resources:
+  - kind: Desktop
+    name: example-desktop
+    notes: Engineering workstation
+    ram:
+      size: 64
+      mts: 3600
+    cpus:
+      - model: Intel Core i9-13900K
+        cores: 24
+        threads: 32
+    drives:
+      - type: ssd
+        size: 2048
+    gpus:
+      - model: NVIDIA RTX 4090
+        vram: 24
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 1

+ 15 - 0
Tests/TestConfigs/v1/08-laptop.yaml

@@ -0,0 +1,15 @@
+version: 1
+resources:
+  - kind: Laptop
+    name: example-laptop
+    notes: Developer machine
+    ram:
+      size: 32
+      mts: 5200
+    cpus:
+      - model: Intel Core i7-1260P
+        cores: 12
+        threads: 16
+    drives:
+      - type: ssd
+        size: 1024

+ 10 - 0
Tests/TestConfigs/v1/09-service.yaml

@@ -0,0 +1,10 @@
+version: 1
+resources:
+  - kind: Service
+    name: example-service
+    runsOn: example-system
+    network:
+      ip: 192.168.1.10
+      port: 8080
+      protocol: TCP
+      url: http://example.local:8080

+ 13 - 0
Tests/TestConfigs/v1/10-system.yaml

@@ -0,0 +1,13 @@
+version: 1
+resources:
+  - kind: System
+    name: example-system
+    notes: Virtual machine instance
+    runsOn: example-server
+    type: VM
+    os: ubuntu-22.04
+    cores: 4
+    ram: 8
+    drives:
+      - size: 128
+      - size: 256

+ 428 - 0
Tests/TestConfigs/v1/11-demo-config.yaml

@@ -0,0 +1,428 @@
+version: 1
+resources:
+  - kind: Server
+    ram:
+      size: 128
+      mts: 3200
+    ipmi: true
+    cpus:
+      - model: AMD EPYC 7302P
+        cores: 16
+        threads: 32
+    drives:
+      - type: ssd
+        size: 1024
+      - type: ssd
+        size: 1024
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 2
+      - type: sfp+
+        speed: 10
+        ports: 2
+    name: proxmox-node01
+  - kind: Server
+    ram:
+      size: 96
+      mts: 2666
+    ipmi: true
+    cpus:
+      - model: Intel Xeon Silver 4210
+        cores: 10
+        threads: 20
+    drives:
+      - type: ssd
+        size: 1024
+      - type: hdd
+        size: 4096
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 2
+      - type: sfp+
+        speed: 10
+        ports: 1
+    name: proxmox-node02
+  - kind: Server
+    ram:
+      size: 64
+      mts: 2666
+    ipmi: true
+    cpus:
+      - model: Intel Xeon E-2236
+        cores: 6
+        threads: 12
+    drives:
+      - type: hdd
+        size: 8192
+      - type: hdd
+        size: 8192
+      - type: hdd
+        size: 8192
+      - type: hdd
+        size: 8192
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 1
+      - type: sfp+
+        speed: 10
+        ports: 1
+    name: truenas-storage
+  - kind: Firewall
+    model: Netgate-6100
+    managed: true
+    poe: false
+    ports:
+      - type: rj45
+        speed: 1
+        count: 4
+      - type: sfp+
+        speed: 10
+        count: 2
+    name: pfsense-fw
+  - kind: Router
+    model: Ubiquiti-ER-4
+    managed: true
+    poe: false
+    ports:
+      - type: rj45
+        speed: 1
+        count: 4
+      - type: sfp
+        speed: 10
+        count: 1
+    name: core-router
+  - kind: Switch
+    model: UniFi-USW-Enterprise-24
+    managed: true
+    poe: true
+    ports:
+      - type: rj45
+        speed: 1
+        count: 12
+      - type: rj45
+        speed: 2.5
+        count: 8
+      - type: sfp+
+        speed: 10
+        count: 4
+    name: core-switch
+  - kind: Switch
+    model: UniFi-USW-16-PoE
+    managed: true
+    poe: true
+    ports:
+      - type: rj45
+        speed: 1
+        count: 16
+      - type: sfp
+        speed: 1
+        count: 2
+    name: access-switch
+  - kind: AccessPoint
+    model: UniFi-U6-Pro
+    speed: 2.5
+    name: lounge-ap
+  - kind: Ups
+    model: APC-SmartUPS-2200
+    va: 2200
+    name: rack-ups
+  - kind: Desktop
+    ram:
+      size: 64
+      mts: 3600
+    cpus:
+      - model: AMD Ryzen 9 5900X
+        cores: 12
+        threads: 24
+    drives:
+      - type: ssd
+        size: 1024
+      - type: ssd
+        size: 2048
+    gpus:
+      - model: NVIDIA RTX 3080
+        vram: 10
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 1
+    name: workstation-linux
+  - kind: Desktop
+    ram:
+      size: 32
+      mts: 3200
+    cpus:
+      - model: Intel Core i7-12700K
+        cores: 12
+        threads: 20
+    drives:
+      - type: ssd
+        size: 1024
+    gpus:
+      - model: NVIDIA RTX 3070
+        vram: 8
+    nics:
+      - type: rj45
+        speed: 1
+        ports: 1
+    name: gaming-pc
+  - kind: Laptop
+    ram:
+      size: 32
+      mts: 5200
+    cpus:
+      - model: Intel Core i7-1260P
+        cores: 12
+        threads: 16
+    drives:
+      - type: ssd
+        size: 1024
+    name: dev-laptop
+  - kind: Service
+    network:
+      ip: 192.168.0.10
+      port: 8123
+      protocol: TCP
+      url: http://homeassistant.lan:8123
+    name: home-assistant
+    runsOn: vm-home-assistant
+  - kind: Service
+    network:
+      ip: 192.168.0.20
+      port: 32400
+      protocol: TCP
+      url: http://plex.lan:32400
+    name: plex
+    runsOn: vm-media-server
+  - kind: Service
+    network:
+      ip: 192.168.0.21
+      port: 8096
+      protocol: TCP
+      url: http://jellyfin.lan:8096
+    name: jellyfin
+    runsOn: vm-media-server
+  - kind: Service
+    network:
+      ip: 192.168.0.22
+      port: 8080
+      protocol: TCP
+      url: http://immich.lan:8080
+    name: immich
+    runsOn: vm-media-server
+  - kind: Service
+    network:
+      ip: 192.168.0.30
+      port: 443
+      protocol: TCP
+      url: https://truenas.lan
+    name: truenas-webui
+    runsOn: truenas-core-os
+  - kind: Service
+    network:
+      ip: 192.168.0.31
+      port: 9000
+      protocol: TCP
+      url: http://minio.lan:9000
+    name: minio
+    runsOn: vm-media-server
+  - kind: Service
+    network:
+      ip: 192.168.0.40
+      port: 9090
+      protocol: TCP
+      url: http://prometheus.lan:9090
+    name: prometheus
+    runsOn: vm-monitoring
+  - kind: Service
+    network:
+      ip: 192.168.0.41
+      port: 3000
+      protocol: TCP
+      url: http://grafana.lan:3000
+    name: grafana
+    runsOn: vm-monitoring
+  - kind: Service
+    network:
+      ip: 192.168.0.42
+      port: 9093
+      protocol: TCP
+      url: http://alertmanager.lan:9093
+    name: alertmanager
+    runsOn: vm-monitoring
+  - kind: Service
+    network:
+      ip: 192.168.0.50
+      port: 3001
+      protocol: TCP
+      url: http://git.lan:3001
+    name: gitea
+    runsOn: vm-monitoring
+  - kind: Service
+    network:
+      ip: 192.168.0.51
+      port: 5000
+      protocol: TCP
+      url: http://registry.lan:5000
+    name: docker-registry
+    runsOn: vm-monitoring
+  - kind: Service
+    network:
+      ip: 192.168.0.52
+      port: 9000
+      protocol: TCP
+      url: http://portainer.lan:9000
+    name: portainer
+    runsOn: vm-monitoring
+  - kind: Service
+    network:
+      ip: 192.168.0.53
+      port: 80
+      protocol: TCP
+      url: http://pihole.lan
+    name: pihole
+    runsOn: vm-monitoring
+  - kind: Service
+    network:
+      ip: 192.168.0.1
+      port: 443
+      protocol: TCP
+      url: https://firewall.lan
+    name: firewall-webui
+    runsOn: firewall-os
+  - kind: Service
+    network:
+      ip: 192.168.0.254
+      port: 443
+      protocol: TCP
+      url: https://router.lan
+    name: router-webui
+    runsOn: router-os
+  - kind: System
+    type: Hypervisor
+    os: proxmox
+    cores: 16
+    ram: 128
+    drives:
+      - size: 1024
+      - size: 1024
+    name: proxmox-cluster-node01
+    runsOn: proxmox-node01
+  - kind: System
+    type: Hypervisor
+    os: proxmox
+    cores: 10
+    ram: 96
+    drives:
+      - size: 1024
+      - size: 4096
+    name: proxmox-cluster-node02
+    runsOn: proxmox-node02
+  - kind: System
+    type: Baremetal
+    os: truenas
+    cores: 6
+    ram: 64
+    drives:
+      - size: 8192
+      - size: 8192
+      - size: 8192
+      - size: 8192
+    name: truenas-core-os
+    runsOn: truenas-storage
+  - kind: System
+    type: Baremetal
+    os: idrac
+    cores: 1
+    ram: 1
+    name: ipmi-proxmox-node01
+    runsOn: proxmox-node01
+  - kind: System
+    type: Baremetal
+    os: ipmi
+    cores: 1
+    ram: 1
+    name: ipmi-proxmox-node02
+    runsOn: proxmox-node02
+  - kind: System
+    type: Baremetal
+    os: ipmi
+    cores: 1
+    ram: 1
+    name: ipmi-truenas-storage
+    runsOn: truenas-storage
+  - kind: System
+    type: Baremetal
+    os: pfsense
+    cores: 4
+    ram: 8
+    drives:
+      - size: 32
+    name: firewall-os
+    runsOn: pfsense-fw
+  - kind: System
+    type: Baremetal
+    os: edgeos
+    cores: 4
+    ram: 4
+    drives:
+      - size: 4
+    name: router-os
+    runsOn: core-router
+  - kind: System
+    type: Baremetal
+    os: unifi-os
+    cores: 2
+    ram: 2
+    drives:
+      - size: 8
+    name: unifi-core-switch-os
+    runsOn: core-switch
+  - kind: System
+    type: Baremetal
+    os: unifi-os
+    cores: 2
+    ram: 2
+    drives:
+      - size: 8
+    name: unifi-access-switch-os
+    runsOn: access-switch
+  - kind: System
+    type: Baremetal
+    os: unifi-firmware
+    cores: 2
+    ram: 1
+    drives:
+      - size: 4
+    name: unifi-lounge-ap-os
+    runsOn: lounge-ap
+  - kind: System
+    type: VM
+    os: hassos
+    cores: 2
+    ram: 4
+    drives:
+      - size: 64
+    name: vm-home-assistant
+    runsOn: proxmox-node01
+  - kind: System
+    type: VM
+    os: ubuntu-22.04
+    cores: 4
+    ram: 8
+    drives:
+      - size: 500
+    name: vm-media-server
+    runsOn: proxmox-node02
+  - kind: System
+    type: VM
+    os: debian-12
+    cores: 2
+    ram: 4
+    drives:
+      - size: 64
+    name: vm-monitoring
+    runsOn: proxmox-node01

+ 55 - 0
Tests/Tests.csproj

@@ -9,6 +9,7 @@
 
     <ItemGroup>
         <PackageReference Include="coverlet.collector" Version="6.0.4"/>
+        <PackageReference Include="JsonSchema.Net" Version="9.1.1" />
         <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
         <PackageReference Include="NSubstitute" Version="5.3.0"/>
         <PackageReference Include="xunit" Version="2.9.3"/>
@@ -33,4 +34,58 @@
       <Folder Include="EndToEnd\ServiceTests\" />
     </ItemGroup>
 
+    <ItemGroup>
+        <None Include="TestConfigs\**\*.yaml">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+
+        <None Include="schemas\schema.v1.json">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+
+        <None Update="TestConfigs\v1\valid-config-1.yaml">
+          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+
+        <None Update="TestConfigs\v1\01-server.yaml">
+          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+
+        <None Update="TestConfigs\v1\02-firewall.yaml">
+          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+
+        <None Update="TestConfigs\v1\03-router.yaml">
+          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+
+        <None Update="TestConfigs\v1\04-switch.yaml">
+          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+
+        <None Update="TestConfigs\v1\05-accesspoint.yaml">
+          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+
+        <None Update="TestConfigs\v1\06-ups.yaml">
+          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+
+        <None Update="TestConfigs\v1\07-desktop.yaml">
+          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+
+        <None Update="TestConfigs\v1\08-laptop.yaml">
+          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+
+        <None Update="TestConfigs\v1\09-service.yaml">
+          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+
+        <None Update="TestConfigs\v1\10-system.yaml">
+          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </None>
+    </ItemGroup>
+    
 </Project>

+ 136 - 0
Tests/Yaml/SchemaTests.cs

@@ -0,0 +1,136 @@
+using System.Text.Json;
+using Json.Schema;
+using YamlDotNet.RepresentationModel;
+
+namespace Tests.Yaml;
+
+public class SchemaConformanceTests
+{
+    private static JsonSchema LoadSchema()
+    {
+        var schemaText = File.ReadAllText("schemas/schema.v1.json");
+        return JsonSchema.FromText(schemaText);
+    }
+    private static JsonElement ConvertYamlToJsonElement(string yaml)
+    {
+        // Load YAML into YAML DOM
+        var yamlStream = new YamlStream();
+        yamlStream.Load(new StringReader(yaml));
+
+        var root = yamlStream.Documents[0].RootNode;
+
+        // Convert YAML node → JSON string
+        var json = ConvertYamlNodeToJson(root);
+
+        using var document = JsonDocument.Parse(json);
+        return document.RootElement.Clone();
+    }
+
+    private static string ConvertYamlNodeToJson(YamlNode node)
+    {
+        if (node is YamlScalarNode scalar)
+        {
+            // Try numeric
+            if (int.TryParse(scalar.Value, out var i))
+                return i.ToString();
+
+            if (double.TryParse(scalar.Value, out var d))
+                return d.ToString(System.Globalization.CultureInfo.InvariantCulture);
+
+            if (bool.TryParse(scalar.Value, out var b))
+                return b.ToString().ToLowerInvariant();
+
+            // Otherwise string
+            return JsonSerializer.Serialize(scalar.Value);
+        }
+
+        if (node is YamlSequenceNode sequence)
+        {
+            var items = sequence.Children
+                .Select(ConvertYamlNodeToJson);
+
+            return "[" + string.Join(",", items) + "]";
+        }
+
+        if (node is YamlMappingNode mapping)
+        {
+            var props = mapping.Children
+                .Select(kvp =>
+                    JsonSerializer.Serialize(((YamlScalarNode)kvp.Key).Value)
+                    + ":"
+                    + ConvertYamlNodeToJson(kvp.Value));
+
+            return "{" + string.Join(",", props) + "}";
+        }
+
+        return "null";
+    }
+    [Fact]
+    public void All_v1_yaml_files_conform_to_schema()
+    {
+        // Arrange
+        var schema = LoadSchema();
+        
+        var yamlFolder = Path.Combine(
+            AppContext.BaseDirectory,
+            "TestConfigs",
+            "v1");
+
+        var yamlFiles = Directory
+            .EnumerateFiles(yamlFolder, "*.yaml", SearchOption.AllDirectories)
+            .ToList();
+
+        Assert.NotEmpty(yamlFiles);
+
+        var failures = new List<string>();
+
+        // Act
+        foreach (var file in yamlFiles)
+        {
+            var yaml = File.ReadAllText(file);
+            var jsonNode = ConvertYamlToJsonElement(yaml);
+
+            var options = new EvaluationOptions
+            {
+                OutputFormat = OutputFormat.Hierarchical            };
+
+            var result = schema.Evaluate(jsonNode, options);
+            
+            if (!result.IsValid)
+            {
+                var errors = new List<string>();
+
+                void CollectErrors(EvaluationResults node)
+                {
+                    if (node.Errors != null)
+                    {
+                        foreach (var error in node.Errors)
+                            errors.Add($"{error.Key}: {error.Value}");
+                    }
+
+                    if (node.Details != null)
+                    {
+                        foreach (var child in node.Details)
+                            CollectErrors(child);
+                    }
+                }
+
+                CollectErrors(result);
+
+                failures.Add(
+                    $"File: {file}{Environment.NewLine}" +
+                    string.Join(Environment.NewLine, errors));
+            }
+        }
+
+        // Assert
+        if (failures.Any())
+        {
+            var message = string.Join(
+                $"{Environment.NewLine}--------------------{Environment.NewLine}",
+                failures);
+
+            Assert.Fail(message);
+        }
+    }
+}

+ 265 - 0
Tests/schemas/schema.v1.json

@@ -0,0 +1,265 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "$id": "https://timmoth.github.io/RackPeek/schemas/v1/schema.v1.json",
+  "title": "RackPeek Infrastructure Specification",
+  "type": "object",
+  "additionalProperties": false,
+  "required": ["version", "resources"],
+  "properties": {
+    "version": {
+      "type": "integer",
+      "const": 1
+    },
+    "resources": {
+      "type": "array",
+      "items": {
+        "oneOf": [
+          { "$ref": "#/$defs/server" },
+          { "$ref": "#/$defs/firewall" },
+          { "$ref": "#/$defs/router" },
+          { "$ref": "#/$defs/switch" },
+          { "$ref": "#/$defs/accessPoint" },
+          { "$ref": "#/$defs/ups" },
+          { "$ref": "#/$defs/desktop" },
+          { "$ref": "#/$defs/laptop" },
+          { "$ref": "#/$defs/service" },
+          { "$ref": "#/$defs/system" }
+        ]
+      }
+    }
+  },
+  "$defs": {
+    "ram": {
+      "type": "object",
+      "required": ["size"],
+      "additionalProperties": false,
+      "properties": {
+        "size": { "type": "number", "minimum": 0 },
+        "mts": { "type": "integer", "minimum": 0 }
+      }
+    },
+    "cpu": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "model": { "type": "string" },
+        "cores": { "type": "integer", "minimum": 1 },
+        "threads": { "type": "integer", "minimum": 1 }
+      }
+    },
+    "drive": {
+      "type": "object",
+      "required": ["size"],
+      "additionalProperties": false,
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": ["nvme","ssd","hdd","sas","sata","usb","sdcard","micro-sd"]
+        },
+        "size": { "type": "number", "minimum": 1 }
+      }
+    },
+    "gpu": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "model": { "type": "string" },
+        "vram": { "type": "number", "minimum": 0 }
+      }
+    },
+    "nic": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "rj45","sfp","sfp+","sfp28","sfp56",
+            "qsfp+","qsfp28","qsfp56","qsfp-dd",
+            "osfp","xfp","cx4","mgmt"
+          ]
+        },
+        "speed": { "type": "number", "minimum": 0 },
+        "ports": { "type": "integer", "minimum": 1 }
+      }
+    },
+    "port": {
+      "type": "object",
+      "required": ["type","speed","count"],
+      "additionalProperties": false,
+      "properties": {
+        "type": { "type": "string" },
+        "speed": { "type": "number", "minimum": 0 },
+        "count": { "type": "integer", "minimum": 1 }
+      }
+    },
+    "network": {
+      "type": "object",
+      "required": ["ip","port","protocol"],
+      "additionalProperties": false,
+      "properties": {
+        "ip": {
+          "type": "string",
+          "pattern": "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}$"
+        },
+        "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
+        "protocol": { "type": "string", "enum": ["TCP","UDP"] },
+        "url": { "type": "string" }
+      }
+    },
+
+    "server": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Server" },
+        "name": { "type": "string" },
+        "tags": { "type": "array", "items": { "type": "string" } },
+        "notes": { "type": "string" },
+        "runsOn": { "type": "string" },
+        "ram": { "$ref": "#/$defs/ram" },
+        "ipmi": { "type": "boolean" },
+        "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+        "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } },
+        "gpus": { "type": "array", "items": { "$ref": "#/$defs/gpu" } },
+        "nics": { "type": "array", "items": { "$ref": "#/$defs/nic" } }
+      }
+    },
+
+    "desktop": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Desktop" },
+        "name": { "type": "string" },
+        "notes": { "type": "string" },
+        "ram": { "$ref": "#/$defs/ram" },
+        "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+        "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } },
+        "gpus": { "type": "array", "items": { "$ref": "#/$defs/gpu" } },
+        "nics": { "type": "array", "items": { "$ref": "#/$defs/nic" } }
+      }
+    },
+
+    "laptop": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Laptop" },
+        "name": { "type": "string" },
+        "notes": { "type": "string" },
+        "ram": { "$ref": "#/$defs/ram" },
+        "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+        "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+      }
+    },
+
+    "firewall": {
+      "type": "object",
+      "required": ["kind","name","ports"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Firewall" },
+        "name": { "type": "string" },
+        "model": { "type": "string" },
+        "managed": { "type": "boolean" },
+        "poe": { "type": "boolean" },
+        "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+      }
+    },
+
+    "router": {
+      "type": "object",
+      "required": ["kind","name","ports"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Router" },
+        "name": { "type": "string" },
+        "model": { "type": "string" },
+        "managed": { "type": "boolean" },
+        "poe": { "type": "boolean" },
+        "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+      }
+    },
+
+    "switch": {
+      "type": "object",
+      "required": ["kind","name","ports"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Switch" },
+        "name": { "type": "string" },
+        "model": { "type": "string" },
+        "managed": { "type": "boolean" },
+        "poe": { "type": "boolean" },
+        "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+      }
+    },
+
+    "accessPoint": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "AccessPoint" },
+        "name": { "type": "string" },
+        "tags": { "type": "array", "items": { "type": "string" } },
+        "model": { "type": "string" },
+        "speed": { "type": "number" }
+      }
+    },
+
+    "ups": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Ups" },
+        "name": { "type": "string" },
+        "tags": { "type": "array", "items": { "type": "string" } },
+        "model": { "type": "string" },
+        "va": { "type": "integer", "minimum": 1 }
+      }
+    },
+
+    "service": {
+      "type": "object",
+      "required": ["kind","name","network","runsOn"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Service" },
+        "name": { "type": "string" },
+        "runsOn": { "type": "string" },
+        "network": { "$ref": "#/$defs/network" }
+      }
+    },
+
+    "system": {
+      "type": "object",
+      "required": ["kind","name","type","os","cores","ram"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "System" },
+        "name": { "type": "string" },
+        "notes": { "type": "string" },
+        "runsOn": { "type": "string" },
+        "type": {
+          "type": "string",
+          "enum": [
+            "baremetal","Baremetal",
+            "hypervisor","Hypervisor",
+            "vm","VM",
+            "container","embedded","cloud","other"
+          ]
+        },
+        "os": { "type": "string" },
+        "cores": { "type": "integer", "minimum": 1 },
+        "ram": { "type": "number", "minimum": 0 },
+        "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+      }
+    }
+  }
+}

+ 265 - 0
schemas/v1/schema.v1.json

@@ -0,0 +1,265 @@
+{
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "$id": "https://timmoth.github.io/RackPeek/schemas/v1/schema.v1.json",
+  "title": "RackPeek Infrastructure Specification",
+  "type": "object",
+  "additionalProperties": false,
+  "required": ["version", "resources"],
+  "properties": {
+    "version": {
+      "type": "integer",
+      "const": 1
+    },
+    "resources": {
+      "type": "array",
+      "items": {
+        "oneOf": [
+          { "$ref": "#/$defs/server" },
+          { "$ref": "#/$defs/firewall" },
+          { "$ref": "#/$defs/router" },
+          { "$ref": "#/$defs/switch" },
+          { "$ref": "#/$defs/accessPoint" },
+          { "$ref": "#/$defs/ups" },
+          { "$ref": "#/$defs/desktop" },
+          { "$ref": "#/$defs/laptop" },
+          { "$ref": "#/$defs/service" },
+          { "$ref": "#/$defs/system" }
+        ]
+      }
+    }
+  },
+  "$defs": {
+    "ram": {
+      "type": "object",
+      "required": ["size"],
+      "additionalProperties": false,
+      "properties": {
+        "size": { "type": "number", "minimum": 0 },
+        "mts": { "type": "integer", "minimum": 0 }
+      }
+    },
+    "cpu": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "model": { "type": "string" },
+        "cores": { "type": "integer", "minimum": 1 },
+        "threads": { "type": "integer", "minimum": 1 }
+      }
+    },
+    "drive": {
+      "type": "object",
+      "required": ["size"],
+      "additionalProperties": false,
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": ["nvme","ssd","hdd","sas","sata","usb","sdcard","micro-sd"]
+        },
+        "size": { "type": "number", "minimum": 1 }
+      }
+    },
+    "gpu": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "model": { "type": "string" },
+        "vram": { "type": "number", "minimum": 0 }
+      }
+    },
+    "nic": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "type": {
+          "type": "string",
+          "enum": [
+            "rj45","sfp","sfp+","sfp28","sfp56",
+            "qsfp+","qsfp28","qsfp56","qsfp-dd",
+            "osfp","xfp","cx4","mgmt"
+          ]
+        },
+        "speed": { "type": "number", "minimum": 0 },
+        "ports": { "type": "integer", "minimum": 1 }
+      }
+    },
+    "port": {
+      "type": "object",
+      "required": ["type","speed","count"],
+      "additionalProperties": false,
+      "properties": {
+        "type": { "type": "string" },
+        "speed": { "type": "number", "minimum": 0 },
+        "count": { "type": "integer", "minimum": 1 }
+      }
+    },
+    "network": {
+      "type": "object",
+      "required": ["ip","port","protocol"],
+      "additionalProperties": false,
+      "properties": {
+        "ip": {
+          "type": "string",
+          "pattern": "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.|$)){4}$"
+        },
+        "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
+        "protocol": { "type": "string", "enum": ["TCP","UDP"] },
+        "url": { "type": "string" }
+      }
+    },
+
+    "server": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Server" },
+        "name": { "type": "string" },
+        "tags": { "type": "array", "items": { "type": "string" } },
+        "notes": { "type": "string" },
+        "runsOn": { "type": "string" },
+        "ram": { "$ref": "#/$defs/ram" },
+        "ipmi": { "type": "boolean" },
+        "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+        "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } },
+        "gpus": { "type": "array", "items": { "$ref": "#/$defs/gpu" } },
+        "nics": { "type": "array", "items": { "$ref": "#/$defs/nic" } }
+      }
+    },
+
+    "desktop": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Desktop" },
+        "name": { "type": "string" },
+        "notes": { "type": "string" },
+        "ram": { "$ref": "#/$defs/ram" },
+        "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+        "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } },
+        "gpus": { "type": "array", "items": { "$ref": "#/$defs/gpu" } },
+        "nics": { "type": "array", "items": { "$ref": "#/$defs/nic" } }
+      }
+    },
+
+    "laptop": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Laptop" },
+        "name": { "type": "string" },
+        "notes": { "type": "string" },
+        "ram": { "$ref": "#/$defs/ram" },
+        "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+        "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+      }
+    },
+
+    "firewall": {
+      "type": "object",
+      "required": ["kind","name","ports"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Firewall" },
+        "name": { "type": "string" },
+        "model": { "type": "string" },
+        "managed": { "type": "boolean" },
+        "poe": { "type": "boolean" },
+        "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+      }
+    },
+
+    "router": {
+      "type": "object",
+      "required": ["kind","name","ports"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Router" },
+        "name": { "type": "string" },
+        "model": { "type": "string" },
+        "managed": { "type": "boolean" },
+        "poe": { "type": "boolean" },
+        "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+      }
+    },
+
+    "switch": {
+      "type": "object",
+      "required": ["kind","name","ports"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Switch" },
+        "name": { "type": "string" },
+        "model": { "type": "string" },
+        "managed": { "type": "boolean" },
+        "poe": { "type": "boolean" },
+        "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+      }
+    },
+
+    "accessPoint": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "AccessPoint" },
+        "name": { "type": "string" },
+        "tags": { "type": "array", "items": { "type": "string" } },
+        "model": { "type": "string" },
+        "speed": { "type": "number" }
+      }
+    },
+
+    "ups": {
+      "type": "object",
+      "required": ["kind","name"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Ups" },
+        "name": { "type": "string" },
+        "tags": { "type": "array", "items": { "type": "string" } },
+        "model": { "type": "string" },
+        "va": { "type": "integer", "minimum": 1 }
+      }
+    },
+
+    "service": {
+      "type": "object",
+      "required": ["kind","name","network","runsOn"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "Service" },
+        "name": { "type": "string" },
+        "runsOn": { "type": "string" },
+        "network": { "$ref": "#/$defs/network" }
+      }
+    },
+
+    "system": {
+      "type": "object",
+      "required": ["kind","name","type","os","cores","ram"],
+      "additionalProperties": false,
+      "properties": {
+        "kind": { "const": "System" },
+        "name": { "type": "string" },
+        "notes": { "type": "string" },
+        "runsOn": { "type": "string" },
+        "type": {
+          "type": "string",
+          "enum": [
+            "baremetal","Baremetal",
+            "hypervisor","Hypervisor",
+            "vm","VM",
+            "container","embedded","cloud","other"
+          ]
+        },
+        "os": { "type": "string" },
+        "cores": { "type": "integer", "minimum": 1 },
+        "ram": { "type": "number", "minimum": 0 },
+        "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+      }
+    }
+  }
+}

+ 0 - 397
servers.yaml

@@ -1,397 +0,0 @@
-resources:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) Silver 4110
-        cores: 8
-        threads: 16
-    ram:
-      size: 64
-      mts: 2400
-    drives:
-      - type: ssd
-        size: 480
-      - type: ssd
-        size: 480
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 2
-      - type: sfp+
-        speed: 10
-        ports: 2
-    gpus:
-    ipmi: true
-    name: dell-c6400-node01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) Silver 4110
-        cores: 8
-        threads: 16
-    ram:
-      size: 128
-      mts: 2400
-    drives:
-      - type: ssd
-        size: 960
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 2
-      - type: sfp+
-        speed: 10
-        ports: 2
-    gpus:
-    ipmi: true
-    name: dell-c6400-node02
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) Silver 4110
-        cores: 8
-        threads: 16
-    ram:
-      size: 64
-      mts: 2400
-    drives:
-      - type: ssd
-        size: 480
-      - type: ssd
-        size: 480
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 2
-      - type: sfp+
-        speed: 10
-        ports: 2
-    gpus:
-    ipmi: true
-    name: dell-c6400-node03
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) Silver 4110
-        cores: 8
-        threads: 16
-    ram:
-      size: 128
-      mts: 2400
-    drives:
-      - type: ssd
-        size: 960
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 2
-      - type: sfp+
-        speed: 10
-        ports: 2
-    gpus:
-    ipmi: true
-    name: dell-c6400-node04
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) E5-2620 v4
-        cores: 8
-        threads: 16
-    ram:
-      size: 64
-      mts: 2133
-    drives:
-      - type: hdd
-        size: 8192
-      - type: hdd
-        size: 8192
-      - type: hdd
-        size: 8192
-      - type: hdd
-        size: 8192
-      - type: ssd
-        size: 120
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 1
-      - type: sfp+
-        speed: 10
-        ports: 1
-    gpus:
-    ipmi: true
-    name: truenas-storage01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Core(TM) i5-8500
-        cores: 6
-        threads: 6
-    ram:
-      size: 32
-      mts: 2666
-    drives:
-      - type: ssd
-        size: 512
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 4
-    gpus:
-    ipmi: false
-    name: proxmox-edge01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Celeron(R) J4125
-        cores: 4
-        threads: 4
-    ram:
-      size: 8
-      mts: 2400
-    drives:
-      - type: ssd
-        size: 64
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 4
-    gpus:
-    ipmi: false
-    name: opnsense-fw01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) E3-1270 v6
-        cores: 4
-        threads: 8
-    ram:
-      size: 16
-      mts: 2400
-    drives:
-      - type: ssd
-        size: 256
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 1
-    gpus:
-    ipmi: true
-    name: mgmt-bastion01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) E5-2630 v4
-        cores: 10
-        threads: 20
-    ram:
-      size: 64
-      mts: 2133
-    drives:
-      - type: hdd
-        size: 6144
-      - type: hdd
-        size: 6144
-      - type: hdd
-        size: 6144
-      - type: hdd
-        size: 6144
-      - type: ssd
-        size: 240
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 2
-      - type: sfp+
-        speed: 10
-        ports: 1
-    gpus:
-    ipmi: true
-    name: truenas-backup01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) Silver 4214
-        cores: 12
-        threads: 24
-    ram:
-      size: 128
-      mts: 2666
-    drives:
-      - type: ssd
-        size: 1024
-    nics:
-      - type: sfp+
-        speed: 10
-        ports: 2
-    gpus:
-      - model: NVIDIA Tesla P40
-        vram: 24
-      - model: NVIDIA Tesla P40
-        vram: 24
-      - model: NVIDIA Tesla P4
-        vram: 8
-    ipmi: true
-    name: compute-gpu01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) E3-1240 v5
-        cores: 4
-        threads: 8
-    ram:
-      size: 32
-      mts: 2133
-    drives:
-      - type: ssd
-        size: 512
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 2
-    gpus:
-    ipmi: true
-    name: proxmox-lab01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) E-2224
-        cores: 4
-        threads: 4
-    ram:
-      size: 16
-      mts: 2666
-    drives:
-      - type: ssd
-        size: 256
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 2
-    gpus:
-    ipmi: true
-    name: k8s-control01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) E-2224
-        cores: 4
-        threads: 4
-    ram:
-      size: 16
-      mts: 2666
-    drives:
-      - type: ssd
-        size: 256
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 2
-    gpus:
-    ipmi: true
-    name: k8s-control02
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) Silver 4108
-        cores: 8
-        threads: 16
-    ram:
-      size: 64
-      mts: 2400
-    drives:
-      - type: ssd
-        size: 1024
-      - type: ssd
-        size: 1024
-    nics:
-      - type: sfp+
-        speed: 10
-        ports: 1
-    gpus:
-    ipmi: true
-    name: elk-logging01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Core(TM) i3-8100
-        cores: 4
-        threads: 4
-    ram:
-      size: 16
-      mts: 2400
-    drives:
-      - type: ssd
-        size: 256
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 2
-    gpus:
-    ipmi: false
-    name: edge-node01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) E5-1650 v3
-        cores: 6
-        threads: 12
-    ram:
-      size: 64
-      mts: 2133
-    drives:
-      - type: ssd
-        size: 480
-      - type: hdd
-        size: 4096
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 4
-    gpus:
-    ipmi: true
-    name: backup-proxmox01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Core(TM) i7-8700
-        cores: 6
-        threads: 12
-    ram:
-      size: 32
-      mts: 2666
-    drives:
-      - type: ssd
-        size: 512
-    nics:
-      - type: rj45
-        speed: 1
-        ports: 1
-    gpus:
-    ipmi: false
-    name: lab-general01
-    tags:
-  - kind: Server
-    cpus:
-      - model: Intel(R) Xeon(R) E5-2650 v3
-        cores: 10
-        threads: 20
-    ram:
-      size: 128
-      mts: 2133
-    drives:
-      - type: hdd
-        size: 4096
-      - type: hdd
-        size: 4096
-      - type: hdd
-        size: 4096
-      - type: hdd
-        size: 4096
-    nics:
-      - type: sfp+
-        speed: 10
-        ports: 2
-    gpus:
-    ipmi: true
-    name: dell-r730-archive01
-    tags: