Tim Jones 1 месяц назад
Родитель
Сommit
3fcd5a5992

+ 212 - 152
RackPeek.Web.Viewer/wwwroot/schemas/v1/schema.v1.json

@@ -1,34 +1,54 @@
 {
-  "$schema": "http://json-schema.org/draft-07/schema#",
+  "$schema": "https://json-schema.org/draft/2020-12/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
-    },
+    "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" }
-        ]
-      }
+      "items": { "$ref": "#/$defs/resource" }
     }
   },
+
   "$defs": {
+    "labels": {
+      "type": "object",
+      "additionalProperties": { "type": "string" }
+    },
+
+    "resourceBase": {
+      "type": "object",
+      "required": ["kind", "name"],
+      "properties": {
+        "kind": { "type": "string" },
+        "name": { "type": "string", "minLength": 1 },
+
+        "tags": { "type": "array", "items": { "type": "string" }, "default": [] },
+        "labels": { "$ref": "#/$defs/labels", "default": {} },
+        "notes": { "type": ["string", "null"] },
+
+        "runsOn": { "type": ["string", "null"] }
+      }
+    },
+
+    "resource": {
+      "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" }
+      ]
+    },
+
     "ram": {
       "type": "object",
       "required": ["size"],
@@ -38,6 +58,7 @@
         "mts": { "type": "integer", "minimum": 0 }
       }
     },
+
     "cpu": {
       "type": "object",
       "additionalProperties": false,
@@ -47,6 +68,7 @@
         "threads": { "type": "integer", "minimum": 1 }
       }
     },
+
     "drive": {
       "type": "object",
       "required": ["size"],
@@ -54,11 +76,12 @@
       "properties": {
         "type": {
           "type": "string",
-          "enum": ["nvme","ssd","hdd","sas","sata","usb","sdcard","micro-sd"]
+          "enum": ["nvme", "ssd", "hdd", "sas", "sata", "usb", "sdcard", "micro-sd"]
         },
         "size": { "type": "number", "minimum": 1 }
       }
     },
+
     "gpu": {
       "type": "object",
       "additionalProperties": false,
@@ -67,6 +90,7 @@
         "vram": { "type": "number", "minimum": 0 }
       }
     },
+
     "nic": {
       "type": "object",
       "additionalProperties": false,
@@ -74,18 +98,19 @@
         "type": {
           "type": "string",
           "enum": [
-            "rj45","sfp","sfp+","sfp28","sfp56",
-            "qsfp+","qsfp28","qsfp56","qsfp-dd",
-            "osfp","xfp","cx4","mgmt"
+            "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"],
+      "required": ["type", "speed", "count"],
       "additionalProperties": false,
       "properties": {
         "type": { "type": "string" },
@@ -93,173 +118,208 @@
         "count": { "type": "integer", "minimum": 1 }
       }
     },
+
     "network": {
       "type": "object",
-      "required": ["ip","port","protocol"],
+      "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}$"
+          "pattern": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}$"
         },
         "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
-        "protocol": { "type": "string", "enum": ["TCP","UDP"] },
-        "url": { "type": "string" }
+        "protocol": { "type": "string", "enum": ["TCP", "UDP"] },
+        "url": { "type": "string", "format": "uri" }
       }
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Server" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Desktop" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Laptop" },
+
+            "ram": { "$ref": "#/$defs/ram" },
+            "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+            "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Firewall" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Router" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Switch" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "AccessPoint" },
+
+            "model": { "type": "string" },
+            "speed": { "type": "number", "minimum": 0 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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 }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Ups" },
+
+            "model": { "type": "string" },
+            "va": { "type": "integer", "minimum": 1 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "service": {
-      "type": "object",
-      "required": ["kind","name","network","runsOn"],
-      "additionalProperties": false,
-      "properties": {
-        "kind": { "const": "Service" },
-        "name": { "type": "string" },
-        "runsOn": { "type": "string" },
-        "network": { "$ref": "#/$defs/network" }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["network"],
+          "properties": {
+            "kind": { "const": "Service" },
+            "network": { "$ref": "#/$defs/network" }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["type", "os", "cores", "ram"],
+          "properties": {
+            "kind": { "const": "System" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     }
   }
 }

+ 336 - 0
RackPeek.Web.Viewer/wwwroot/schemas/v2/schema.v2.json

@@ -0,0 +1,336 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "$id": "https://timmoth.github.io/RackPeek/schemas/v2/schema.v2.json",
+  "title": "RackPeek Infrastructure Specification",
+  "type": "object",
+  "additionalProperties": false,
+  "required": ["version", "resources"],
+  "properties": {
+    "version": { "type": "integer", "const": 2 },
+    "resources": {
+      "type": "array",
+      "items": { "$ref": "#/$defs/resource" }
+    }
+  },
+
+  "$defs": {
+    "labels": {
+      "type": "object",
+      "additionalProperties": { "type": "string" }
+    },
+
+    "runsOn": {
+      "type": ["array", "null"],
+      "items": {
+        "type": "string",
+        "minLength": 1
+      }
+    },
+
+    "resourceBase": {
+      "type": "object",
+      "required": ["kind", "name"],
+      "properties": {
+        "kind": { "type": "string" },
+        "name": { "type": "string", "minLength": 1 },
+
+        "tags": { "type": "array", "items": { "type": "string" }, "default": [] },
+        "labels": { "$ref": "#/$defs/labels", "default": {} },
+        "notes": { "type": ["string", "null"] },
+
+        "runsOn": {
+          "type": ["array", "null"],
+          "items": { "type": "string" }
+        }
+      }
+    },
+
+    "resource": {
+      "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" }
+      ]
+    },
+
+    "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]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}$"
+        },
+        "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
+        "protocol": { "type": "string", "enum": ["TCP", "UDP"] },
+        "url": { "type": "string", "format": "uri" }
+      }
+    },
+
+    "server": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Server" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "desktop": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Desktop" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "laptop": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Laptop" },
+
+            "ram": { "$ref": "#/$defs/ram" },
+            "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+            "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "firewall": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Firewall" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "router": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Router" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "switch": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Switch" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "accessPoint": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "AccessPoint" },
+
+            "model": { "type": "string" },
+            "speed": { "type": "number", "minimum": 0 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "ups": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Ups" },
+
+            "model": { "type": "string" },
+            "va": { "type": "integer", "minimum": 1 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "service": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["network"],
+          "properties": {
+            "kind": { "const": "Service" },
+            "network": { "$ref": "#/$defs/network" }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "system": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["type", "os", "cores", "ram"],
+          "properties": {
+            "kind": { "const": "System" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    }
+  }
+}

+ 212 - 152
RackPeek.Web/wwwroot/schemas/v1/schema.v1.json

@@ -1,34 +1,54 @@
 {
-  "$schema": "http://json-schema.org/draft-07/schema#",
+  "$schema": "https://json-schema.org/draft/2020-12/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
-    },
+    "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" }
-        ]
-      }
+      "items": { "$ref": "#/$defs/resource" }
     }
   },
+
   "$defs": {
+    "labels": {
+      "type": "object",
+      "additionalProperties": { "type": "string" }
+    },
+
+    "resourceBase": {
+      "type": "object",
+      "required": ["kind", "name"],
+      "properties": {
+        "kind": { "type": "string" },
+        "name": { "type": "string", "minLength": 1 },
+
+        "tags": { "type": "array", "items": { "type": "string" }, "default": [] },
+        "labels": { "$ref": "#/$defs/labels", "default": {} },
+        "notes": { "type": ["string", "null"] },
+
+        "runsOn": { "type": ["string", "null"] }
+      }
+    },
+
+    "resource": {
+      "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" }
+      ]
+    },
+
     "ram": {
       "type": "object",
       "required": ["size"],
@@ -38,6 +58,7 @@
         "mts": { "type": "integer", "minimum": 0 }
       }
     },
+
     "cpu": {
       "type": "object",
       "additionalProperties": false,
@@ -47,6 +68,7 @@
         "threads": { "type": "integer", "minimum": 1 }
       }
     },
+
     "drive": {
       "type": "object",
       "required": ["size"],
@@ -54,11 +76,12 @@
       "properties": {
         "type": {
           "type": "string",
-          "enum": ["nvme","ssd","hdd","sas","sata","usb","sdcard","micro-sd"]
+          "enum": ["nvme", "ssd", "hdd", "sas", "sata", "usb", "sdcard", "micro-sd"]
         },
         "size": { "type": "number", "minimum": 1 }
       }
     },
+
     "gpu": {
       "type": "object",
       "additionalProperties": false,
@@ -67,6 +90,7 @@
         "vram": { "type": "number", "minimum": 0 }
       }
     },
+
     "nic": {
       "type": "object",
       "additionalProperties": false,
@@ -74,18 +98,19 @@
         "type": {
           "type": "string",
           "enum": [
-            "rj45","sfp","sfp+","sfp28","sfp56",
-            "qsfp+","qsfp28","qsfp56","qsfp-dd",
-            "osfp","xfp","cx4","mgmt"
+            "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"],
+      "required": ["type", "speed", "count"],
       "additionalProperties": false,
       "properties": {
         "type": { "type": "string" },
@@ -93,173 +118,208 @@
         "count": { "type": "integer", "minimum": 1 }
       }
     },
+
     "network": {
       "type": "object",
-      "required": ["ip","port","protocol"],
+      "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}$"
+          "pattern": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}$"
         },
         "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
-        "protocol": { "type": "string", "enum": ["TCP","UDP"] },
-        "url": { "type": "string" }
+        "protocol": { "type": "string", "enum": ["TCP", "UDP"] },
+        "url": { "type": "string", "format": "uri" }
       }
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Server" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Desktop" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Laptop" },
+
+            "ram": { "$ref": "#/$defs/ram" },
+            "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+            "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Firewall" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Router" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Switch" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "AccessPoint" },
+
+            "model": { "type": "string" },
+            "speed": { "type": "number", "minimum": 0 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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 }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Ups" },
+
+            "model": { "type": "string" },
+            "va": { "type": "integer", "minimum": 1 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "service": {
-      "type": "object",
-      "required": ["kind","name","network","runsOn"],
-      "additionalProperties": false,
-      "properties": {
-        "kind": { "const": "Service" },
-        "name": { "type": "string" },
-        "runsOn": { "type": "string" },
-        "network": { "$ref": "#/$defs/network" }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["network"],
+          "properties": {
+            "kind": { "const": "Service" },
+            "network": { "$ref": "#/$defs/network" }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["type", "os", "cores", "ram"],
+          "properties": {
+            "kind": { "const": "System" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     }
   }
 }

+ 336 - 0
RackPeek.Web/wwwroot/schemas/v2/schema.v2.json

@@ -0,0 +1,336 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "$id": "https://timmoth.github.io/RackPeek/schemas/v2/schema.v2.json",
+  "title": "RackPeek Infrastructure Specification",
+  "type": "object",
+  "additionalProperties": false,
+  "required": ["version", "resources"],
+  "properties": {
+    "version": { "type": "integer", "const": 2 },
+    "resources": {
+      "type": "array",
+      "items": { "$ref": "#/$defs/resource" }
+    }
+  },
+
+  "$defs": {
+    "labels": {
+      "type": "object",
+      "additionalProperties": { "type": "string" }
+    },
+
+    "runsOn": {
+      "type": ["array", "null"],
+      "items": {
+        "type": "string",
+        "minLength": 1
+      }
+    },
+
+    "resourceBase": {
+      "type": "object",
+      "required": ["kind", "name"],
+      "properties": {
+        "kind": { "type": "string" },
+        "name": { "type": "string", "minLength": 1 },
+
+        "tags": { "type": "array", "items": { "type": "string" }, "default": [] },
+        "labels": { "$ref": "#/$defs/labels", "default": {} },
+        "notes": { "type": ["string", "null"] },
+
+        "runsOn": {
+          "type": ["array", "null"],
+          "items": { "type": "string" }
+        }
+      }
+    },
+
+    "resource": {
+      "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" }
+      ]
+    },
+
+    "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]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}$"
+        },
+        "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
+        "protocol": { "type": "string", "enum": ["TCP", "UDP"] },
+        "url": { "type": "string", "format": "uri" }
+      }
+    },
+
+    "server": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Server" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "desktop": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Desktop" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "laptop": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Laptop" },
+
+            "ram": { "$ref": "#/$defs/ram" },
+            "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+            "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "firewall": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Firewall" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "router": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Router" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "switch": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Switch" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "accessPoint": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "AccessPoint" },
+
+            "model": { "type": "string" },
+            "speed": { "type": "number", "minimum": 0 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "ups": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Ups" },
+
+            "model": { "type": "string" },
+            "va": { "type": "integer", "minimum": 1 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "service": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["network"],
+          "properties": {
+            "kind": { "const": "Service" },
+            "network": { "$ref": "#/$defs/network" }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    },
+
+    "system": {
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["type", "os", "cores", "ram"],
+          "properties": {
+            "kind": { "const": "System" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
+    }
+  }
+}

+ 1 - 1
Tests/Yaml/SchemaTests.cs

@@ -68,7 +68,7 @@ public class SchemaConformanceTests
     
     [Theory]
     [InlineData(1)]
-    //[InlineData(2)]
+    [InlineData(2)]
     public void All_yaml_files_conform_to_schema(int version)
     {
         // Arrange

+ 212 - 152
Tests/schemas/schema.v1.json

@@ -1,34 +1,54 @@
 {
-  "$schema": "http://json-schema.org/draft-07/schema#",
+  "$schema": "https://json-schema.org/draft/2020-12/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
-    },
+    "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" }
-        ]
-      }
+      "items": { "$ref": "#/$defs/resource" }
     }
   },
+
   "$defs": {
+    "labels": {
+      "type": "object",
+      "additionalProperties": { "type": "string" }
+    },
+
+    "resourceBase": {
+      "type": "object",
+      "required": ["kind", "name"],
+      "properties": {
+        "kind": { "type": "string" },
+        "name": { "type": "string", "minLength": 1 },
+
+        "tags": { "type": "array", "items": { "type": "string" }, "default": [] },
+        "labels": { "$ref": "#/$defs/labels", "default": {} },
+        "notes": { "type": ["string", "null"] },
+
+        "runsOn": { "type": ["string", "null"] }
+      }
+    },
+
+    "resource": {
+      "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" }
+      ]
+    },
+
     "ram": {
       "type": "object",
       "required": ["size"],
@@ -38,6 +58,7 @@
         "mts": { "type": "integer", "minimum": 0 }
       }
     },
+
     "cpu": {
       "type": "object",
       "additionalProperties": false,
@@ -47,6 +68,7 @@
         "threads": { "type": "integer", "minimum": 1 }
       }
     },
+
     "drive": {
       "type": "object",
       "required": ["size"],
@@ -54,11 +76,12 @@
       "properties": {
         "type": {
           "type": "string",
-          "enum": ["nvme","ssd","hdd","sas","sata","usb","sdcard","micro-sd"]
+          "enum": ["nvme", "ssd", "hdd", "sas", "sata", "usb", "sdcard", "micro-sd"]
         },
         "size": { "type": "number", "minimum": 1 }
       }
     },
+
     "gpu": {
       "type": "object",
       "additionalProperties": false,
@@ -67,6 +90,7 @@
         "vram": { "type": "number", "minimum": 0 }
       }
     },
+
     "nic": {
       "type": "object",
       "additionalProperties": false,
@@ -74,18 +98,19 @@
         "type": {
           "type": "string",
           "enum": [
-            "rj45","sfp","sfp+","sfp28","sfp56",
-            "qsfp+","qsfp28","qsfp56","qsfp-dd",
-            "osfp","xfp","cx4","mgmt"
+            "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"],
+      "required": ["type", "speed", "count"],
       "additionalProperties": false,
       "properties": {
         "type": { "type": "string" },
@@ -93,173 +118,208 @@
         "count": { "type": "integer", "minimum": 1 }
       }
     },
+
     "network": {
       "type": "object",
-      "required": ["ip","port","protocol"],
+      "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}$"
+          "pattern": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}$"
         },
         "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
-        "protocol": { "type": "string", "enum": ["TCP","UDP"] },
-        "url": { "type": "string" }
+        "protocol": { "type": "string", "enum": ["TCP", "UDP"] },
+        "url": { "type": "string", "format": "uri" }
       }
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Server" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Desktop" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Laptop" },
+
+            "ram": { "$ref": "#/$defs/ram" },
+            "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+            "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Firewall" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Router" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Switch" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "AccessPoint" },
+
+            "model": { "type": "string" },
+            "speed": { "type": "number", "minimum": 0 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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 }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Ups" },
+
+            "model": { "type": "string" },
+            "va": { "type": "integer", "minimum": 1 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "service": {
-      "type": "object",
-      "required": ["kind","name","network","runsOn"],
-      "additionalProperties": false,
-      "properties": {
-        "kind": { "const": "Service" },
-        "name": { "type": "string" },
-        "runsOn": { "type": "string" },
-        "network": { "$ref": "#/$defs/network" }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["network"],
+          "properties": {
+            "kind": { "const": "Service" },
+            "network": { "$ref": "#/$defs/network" }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["type", "os", "cores", "ram"],
+          "properties": {
+            "kind": { "const": "System" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     }
   }
 }

+ 224 - 159
Tests/schemas/schema.v2.json

@@ -1,34 +1,65 @@
 {
-  "$schema": "http://json-schema.org/draft-07/schema#",
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
   "$id": "https://timmoth.github.io/RackPeek/schemas/v2/schema.v2.json",
   "title": "RackPeek Infrastructure Specification",
   "type": "object",
   "additionalProperties": false,
   "required": ["version", "resources"],
   "properties": {
-    "version": {
-      "type": "integer",
-      "const": 2
-    },
+    "version": { "type": "integer", "const": 2 },
     "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" }
-        ]
-      }
+      "items": { "$ref": "#/$defs/resource" }
     }
   },
+
   "$defs": {
+    "labels": {
+      "type": "object",
+      "additionalProperties": { "type": "string" }
+    },
+
+    "runsOn": {
+      "type": ["array", "null"],
+      "items": {
+        "type": "string",
+        "minLength": 1
+      }
+    },
+
+    "resourceBase": {
+      "type": "object",
+      "required": ["kind", "name"],
+      "properties": {
+        "kind": { "type": "string" },
+        "name": { "type": "string", "minLength": 1 },
+
+        "tags": { "type": "array", "items": { "type": "string" }, "default": [] },
+        "labels": { "$ref": "#/$defs/labels", "default": {} },
+        "notes": { "type": ["string", "null"] },
+
+        "runsOn": {
+          "type": ["array", "null"],
+          "items": { "type": "string" }
+        }
+      }
+    },
+
+    "resource": {
+      "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" }
+      ]
+    },
+
     "ram": {
       "type": "object",
       "required": ["size"],
@@ -38,6 +69,7 @@
         "mts": { "type": "integer", "minimum": 0 }
       }
     },
+
     "cpu": {
       "type": "object",
       "additionalProperties": false,
@@ -47,6 +79,7 @@
         "threads": { "type": "integer", "minimum": 1 }
       }
     },
+
     "drive": {
       "type": "object",
       "required": ["size"],
@@ -54,11 +87,12 @@
       "properties": {
         "type": {
           "type": "string",
-          "enum": ["nvme","ssd","hdd","sas","sata","usb","sdcard","micro-sd"]
+          "enum": ["nvme", "ssd", "hdd", "sas", "sata", "usb", "sdcard", "micro-sd"]
         },
         "size": { "type": "number", "minimum": 1 }
       }
     },
+
     "gpu": {
       "type": "object",
       "additionalProperties": false,
@@ -67,6 +101,7 @@
         "vram": { "type": "number", "minimum": 0 }
       }
     },
+
     "nic": {
       "type": "object",
       "additionalProperties": false,
@@ -74,18 +109,19 @@
         "type": {
           "type": "string",
           "enum": [
-            "rj45","sfp","sfp+","sfp28","sfp56",
-            "qsfp+","qsfp28","qsfp56","qsfp-dd",
-            "osfp","xfp","cx4","mgmt"
+            "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"],
+      "required": ["type", "speed", "count"],
       "additionalProperties": false,
       "properties": {
         "type": { "type": "string" },
@@ -93,179 +129,208 @@
         "count": { "type": "integer", "minimum": 1 }
       }
     },
+
     "network": {
       "type": "object",
-      "required": ["ip","port","protocol"],
+      "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}$"
+          "pattern": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}$"
         },
         "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
-        "protocol": { "type": "string", "enum": ["TCP","UDP"] },
-        "url": { "type": "string" }
-      }
-    },
-    "runsOn": {
-      "type": "array",
-      "items": {
-        "type": "string"
+        "protocol": { "type": "string", "enum": ["TCP", "UDP"] },
+        "url": { "type": "string", "format": "uri" }
       }
     },
 
     "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": { "$ref": "#/$defs/runsOn" },
-        "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Server" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Desktop" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Laptop" },
+
+            "ram": { "$ref": "#/$defs/ram" },
+            "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+            "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Firewall" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Router" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Switch" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "AccessPoint" },
+
+            "model": { "type": "string" },
+            "speed": { "type": "number", "minimum": 0 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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 }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Ups" },
+
+            "model": { "type": "string" },
+            "va": { "type": "integer", "minimum": 1 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "service": {
-      "type": "object",
-      "required": ["kind","name","network","runsOn"],
-      "additionalProperties": false,
-      "properties": {
-        "kind": { "const": "Service" },
-        "name": { "type": "string" },
-        "runsOn": { "$ref": "#/$defs/runsOn" },
-        "network": { "$ref": "#/$defs/network" }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["network"],
+          "properties": {
+            "kind": { "const": "Service" },
+            "network": { "$ref": "#/$defs/network" }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "system": {
-      "type": "object",
-      "required": ["kind","name","type","os","cores","ram"],
-      "additionalProperties": false,
-      "properties": {
-        "kind": { "const": "System" },
-        "name": { "type": "string" },
-        "notes": { "type": "string" },
-        "runsOn": { "$ref": "#/$defs/runsOn" },
-        "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["type", "os", "cores", "ram"],
+          "properties": {
+            "kind": { "const": "System" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     }
   }
-}
+}

+ 212 - 152
schemas/v1/schema.v1.json

@@ -1,34 +1,54 @@
 {
-  "$schema": "http://json-schema.org/draft-07/schema#",
+  "$schema": "https://json-schema.org/draft/2020-12/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
-    },
+    "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" }
-        ]
-      }
+      "items": { "$ref": "#/$defs/resource" }
     }
   },
+
   "$defs": {
+    "labels": {
+      "type": "object",
+      "additionalProperties": { "type": "string" }
+    },
+
+    "resourceBase": {
+      "type": "object",
+      "required": ["kind", "name"],
+      "properties": {
+        "kind": { "type": "string" },
+        "name": { "type": "string", "minLength": 1 },
+
+        "tags": { "type": "array", "items": { "type": "string" }, "default": [] },
+        "labels": { "$ref": "#/$defs/labels", "default": {} },
+        "notes": { "type": ["string", "null"] },
+
+        "runsOn": { "type": ["string", "null"] }
+      }
+    },
+
+    "resource": {
+      "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" }
+      ]
+    },
+
     "ram": {
       "type": "object",
       "required": ["size"],
@@ -38,6 +58,7 @@
         "mts": { "type": "integer", "minimum": 0 }
       }
     },
+
     "cpu": {
       "type": "object",
       "additionalProperties": false,
@@ -47,6 +68,7 @@
         "threads": { "type": "integer", "minimum": 1 }
       }
     },
+
     "drive": {
       "type": "object",
       "required": ["size"],
@@ -54,11 +76,12 @@
       "properties": {
         "type": {
           "type": "string",
-          "enum": ["nvme","ssd","hdd","sas","sata","usb","sdcard","micro-sd"]
+          "enum": ["nvme", "ssd", "hdd", "sas", "sata", "usb", "sdcard", "micro-sd"]
         },
         "size": { "type": "number", "minimum": 1 }
       }
     },
+
     "gpu": {
       "type": "object",
       "additionalProperties": false,
@@ -67,6 +90,7 @@
         "vram": { "type": "number", "minimum": 0 }
       }
     },
+
     "nic": {
       "type": "object",
       "additionalProperties": false,
@@ -74,18 +98,19 @@
         "type": {
           "type": "string",
           "enum": [
-            "rj45","sfp","sfp+","sfp28","sfp56",
-            "qsfp+","qsfp28","qsfp56","qsfp-dd",
-            "osfp","xfp","cx4","mgmt"
+            "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"],
+      "required": ["type", "speed", "count"],
       "additionalProperties": false,
       "properties": {
         "type": { "type": "string" },
@@ -93,173 +118,208 @@
         "count": { "type": "integer", "minimum": 1 }
       }
     },
+
     "network": {
       "type": "object",
-      "required": ["ip","port","protocol"],
+      "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}$"
+          "pattern": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}$"
         },
         "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
-        "protocol": { "type": "string", "enum": ["TCP","UDP"] },
-        "url": { "type": "string" }
+        "protocol": { "type": "string", "enum": ["TCP", "UDP"] },
+        "url": { "type": "string", "format": "uri" }
       }
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Server" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Desktop" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Laptop" },
+
+            "ram": { "$ref": "#/$defs/ram" },
+            "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+            "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Firewall" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Router" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Switch" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "AccessPoint" },
+
+            "model": { "type": "string" },
+            "speed": { "type": "number", "minimum": 0 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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 }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Ups" },
+
+            "model": { "type": "string" },
+            "va": { "type": "integer", "minimum": 1 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "service": {
-      "type": "object",
-      "required": ["kind","name","network","runsOn"],
-      "additionalProperties": false,
-      "properties": {
-        "kind": { "const": "Service" },
-        "name": { "type": "string" },
-        "runsOn": { "type": "string" },
-        "network": { "$ref": "#/$defs/network" }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["network"],
+          "properties": {
+            "kind": { "const": "Service" },
+            "network": { "$ref": "#/$defs/network" }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["type", "os", "cores", "ram"],
+          "properties": {
+            "kind": { "const": "System" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     }
   }
 }

+ 225 - 160
schemas/v2/schema.v2.json

@@ -1,34 +1,65 @@
 {
-  "$schema": "http://json-schema.org/draft-07/schema#",
-  "$id": "https://timmoth.github.io/RackPeek/schemas/v1/schema.v1.json",
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "$id": "https://timmoth.github.io/RackPeek/schemas/v2/schema.v2.json",
   "title": "RackPeek Infrastructure Specification",
   "type": "object",
   "additionalProperties": false,
   "required": ["version", "resources"],
   "properties": {
-    "version": {
-      "type": "integer",
-      "const": 1
-    },
+    "version": { "type": "integer", "const": 2 },
     "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" }
-        ]
-      }
+      "items": { "$ref": "#/$defs/resource" }
     }
   },
+
   "$defs": {
+    "labels": {
+      "type": "object",
+      "additionalProperties": { "type": "string" }
+    },
+
+    "runsOn": {
+      "type": ["array", "null"],
+      "items": {
+        "type": "string",
+        "minLength": 1
+      }
+    },
+
+    "resourceBase": {
+      "type": "object",
+      "required": ["kind", "name"],
+      "properties": {
+        "kind": { "type": "string" },
+        "name": { "type": "string", "minLength": 1 },
+
+        "tags": { "type": "array", "items": { "type": "string" }, "default": [] },
+        "labels": { "$ref": "#/$defs/labels", "default": {} },
+        "notes": { "type": ["string", "null"] },
+
+        "runsOn": {
+          "type": ["array", "null"],
+          "items": { "type": "string" }
+        }
+      }
+    },
+
+    "resource": {
+      "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" }
+      ]
+    },
+
     "ram": {
       "type": "object",
       "required": ["size"],
@@ -38,6 +69,7 @@
         "mts": { "type": "integer", "minimum": 0 }
       }
     },
+
     "cpu": {
       "type": "object",
       "additionalProperties": false,
@@ -47,6 +79,7 @@
         "threads": { "type": "integer", "minimum": 1 }
       }
     },
+
     "drive": {
       "type": "object",
       "required": ["size"],
@@ -54,11 +87,12 @@
       "properties": {
         "type": {
           "type": "string",
-          "enum": ["nvme","ssd","hdd","sas","sata","usb","sdcard","micro-sd"]
+          "enum": ["nvme", "ssd", "hdd", "sas", "sata", "usb", "sdcard", "micro-sd"]
         },
         "size": { "type": "number", "minimum": 1 }
       }
     },
+
     "gpu": {
       "type": "object",
       "additionalProperties": false,
@@ -67,6 +101,7 @@
         "vram": { "type": "number", "minimum": 0 }
       }
     },
+
     "nic": {
       "type": "object",
       "additionalProperties": false,
@@ -74,18 +109,19 @@
         "type": {
           "type": "string",
           "enum": [
-            "rj45","sfp","sfp+","sfp28","sfp56",
-            "qsfp+","qsfp28","qsfp56","qsfp-dd",
-            "osfp","xfp","cx4","mgmt"
+            "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"],
+      "required": ["type", "speed", "count"],
       "additionalProperties": false,
       "properties": {
         "type": { "type": "string" },
@@ -93,179 +129,208 @@
         "count": { "type": "integer", "minimum": 1 }
       }
     },
+
     "network": {
       "type": "object",
-      "required": ["ip","port","protocol"],
+      "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}$"
+          "pattern": "^(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}$"
         },
         "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
-        "protocol": { "type": "string", "enum": ["TCP","UDP"] },
-        "url": { "type": "string" }
-      }
-    },
-    "runsOn": {
-      "type": "array",
-      "items": {
-        "type": "string"
+        "protocol": { "type": "string", "enum": ["TCP", "UDP"] },
+        "url": { "type": "string", "format": "uri" }
       }
     },
 
     "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": { "$ref": "#/$defs/runsOn" },
-        "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Server" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Desktop" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Laptop" },
+
+            "ram": { "$ref": "#/$defs/ram" },
+            "cpus": { "type": "array", "items": { "$ref": "#/$defs/cpu" } },
+            "drives": { "type": "array", "items": { "$ref": "#/$defs/drive" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Firewall" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Router" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["ports"],
+          "properties": {
+            "kind": { "const": "Switch" },
+
+            "model": { "type": "string" },
+            "managed": { "type": "boolean" },
+            "poe": { "type": "boolean" },
+            "ports": { "type": "array", "items": { "$ref": "#/$defs/port" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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" }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "AccessPoint" },
+
+            "model": { "type": "string" },
+            "speed": { "type": "number", "minimum": 0 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "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 }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "properties": {
+            "kind": { "const": "Ups" },
+
+            "model": { "type": "string" },
+            "va": { "type": "integer", "minimum": 1 }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "service": {
-      "type": "object",
-      "required": ["kind","name","network","runsOn"],
-      "additionalProperties": false,
-      "properties": {
-        "kind": { "const": "Service" },
-        "name": { "type": "string" },
-        "runsOn": { "$ref": "#/$defs/runsOn" },
-        "network": { "$ref": "#/$defs/network" }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["network"],
+          "properties": {
+            "kind": { "const": "Service" },
+            "network": { "$ref": "#/$defs/network" }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     },
 
     "system": {
-      "type": "object",
-      "required": ["kind","name","type","os","cores","ram"],
-      "additionalProperties": false,
-      "properties": {
-        "kind": { "const": "System" },
-        "name": { "type": "string" },
-        "notes": { "type": "string" },
-        "runsOn": { "$ref": "#/$defs/runsOn" },
-        "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" } }
-      }
+      "allOf": [
+        { "$ref": "#/$defs/resourceBase" },
+        {
+          "type": "object",
+          "required": ["type", "os", "cores", "ram"],
+          "properties": {
+            "kind": { "const": "System" },
+
+            "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" } }
+          }
+        }
+      ],
+      "unevaluatedProperties": false
     }
   }
-}
+}