Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions platforms/Arista/arista-eos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
manufacturer: Arista
name: Arista EOS
slug: arista-eos
description: Arista Extensible Operating System
5 changes: 5 additions & 0 deletions platforms/Cisco/cisco-ios-xe.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
manufacturer: Cisco
name: Cisco IOS-XE
slug: cisco-ios-xe
description: Cisco Internet Operating System XE
5 changes: 5 additions & 0 deletions platforms/Cisco/cisco-nxos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
manufacturer: Cisco
name: Cisco NX-OS
slug: cisco-nxos
description: Cisco Nexus Operating System
5 changes: 5 additions & 0 deletions platforms/Juniper/juniper-junos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
manufacturer: Juniper
name: Juniper Junos
slug: juniper-junos
description: Juniper Networks Junos Operating System
5 changes: 5 additions & 0 deletions platforms/Palo Alto/paloalto-panos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
manufacturer: Palo Alto
name: Palo Alto PAN-OS
slug: paloalto-panos
description: Palo Alto Networks PAN-OS
5 changes: 5 additions & 0 deletions schema/devicetype.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@
},
"comments": {
"type": "string"
},
"default_platform": {
"type": "string",
"maxLength": 100,
"pattern": "^[-a-z0-9_]+$"
}
},
"allOf": [
Expand Down
26 changes: 26 additions & 0 deletions schema/platformtype.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"type": "object",
"$id": "urn:devicetype-library:platform-type",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"properties": {
"manufacturer": {
"type": "string",
"maxLength": 100
},
"name": {
"type": "string",
"maxLength": 100
},
"slug": {
"type": "string",
"maxLength": 100,
"pattern": "^[-a-z0-9_]+$"
},
"description": {
"type": "string",
"maxLength": 200
}
},
"required": ["manufacturer", "name", "slug"],
"additionalProperties": false
}
46 changes: 42 additions & 4 deletions tests/definitions_test.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from test_configuration import COMPONENT_TYPES, IMAGE_FILETYPES, SCHEMAS, SCHEMAS_BASEPATH, KNOWN_SLUGS, ROOT_DIR, USE_LOCAL_KNOWN_SLUGS, NETBOX_DT_LIBRARY_URL, KNOWN_MODULES, USE_UPSTREAM_DIFF, PRECOMMIT_ALL_SWITCHES
import pickle_operations
from yaml_loader import DecimalSafeLoader
from device_types import DeviceType, ModuleType, RackType, verify_filename, validate_components
from device_types import DeviceType, ModuleType, RackType, PlatformType, verify_filename, validate_components
import decimal
import glob
import json
Expand Down Expand Up @@ -131,6 +131,12 @@ def test_environment():
definition_files = _get_definition_files()
image_files = _get_image_files()

# Precompute known platform slugs for default_platform cross-validation
KNOWN_PLATFORMS = {}
for platform_file in sorted(glob.glob(f"platforms{os.path.sep}*{os.path.sep}*.yaml")) + sorted(glob.glob(f"platforms{os.path.sep}*{os.path.sep}*.yml")):
platform_slug = os.path.basename(platform_file).rsplit(".", 1)[0]
KNOWN_PLATFORMS.setdefault(platform_slug, []).append(platform_file)

Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KNOWN_PLATFORMS is built with the intent of detecting duplicate platform slugs, but duplicates are currently only surfaced when a device-type references default_platform. If two platform files share the same slug and no device-type references it, the tests will still pass. Consider adding an unconditional check (e.g., a small test that asserts every platform_slug maps to exactly one file) so duplicate slugs are always rejected.

Suggested change
def test_unique_platform_slugs():
"""
Ensure each platform slug corresponds to exactly one platform definition file.
This prevents silent duplicates when a platform slug is not referenced
by any device-type via ``default_platform``.
"""
duplicates = {
slug: files for slug, files in KNOWN_PLATFORMS.items() if len(files) > 1
}
assert not duplicates, (
"Duplicate platform slugs detected:\n"
+ "\n".join(
f"{slug}: {', '.join(sorted(files))}"
for slug, files in sorted(duplicates.items())
)
)

Copilot uses AI. Check for mistakes.
if USE_LOCAL_KNOWN_SLUGS:
KNOWN_SLUGS = pickle_operations.read_pickle_data(f'{ROOT_DIR}/tests/known-slugs.pickle')
KNOWN_MODULES = pickle_operations.read_pickle_data(f'{ROOT_DIR}/tests/known-modules.pickle')
Expand Down Expand Up @@ -188,7 +194,7 @@ def test_definitions(file_path, schema, change_type):
# Schema validation failure. Ensure you are following the proper format.
pytest.fail(f"{file_path} failed validation: {e}", False)

# Identify if the definition is for a Device or Module
# Identify if the definition is for a Device, Module, Rack, or Platform
if "device-types" in file_path:
# A device
this_device = DeviceType(definition, file_path, change_type)
Expand All @@ -198,10 +204,22 @@ def test_definitions(file_path, schema, change_type):
elif "rack-types" in file_path:
# A rack type
this_device = RackType(definition, file_path, change_type)
elif "platforms" in file_path:
# A platform
this_device = PlatformType(definition, file_path, change_type)
else:
# A module
this_device = ModuleType(definition, file_path, change_type)

# Validate platform filename matches slug
if "platforms" in file_path:
platform_filename = os.path.basename(file_path).rsplit(".", 1)[0]
if platform_filename != definition.get('slug'):
pytest.fail(
f"{file_path}: filename '{platform_filename}' does not match slug '{definition.get('slug')}'",
pytrace=False,
)

# Validate that front-ports reference existing rear-ports
if any(x in file_path for x in ("device-types", "module-types")):
rear_ports = definition.get("rear-ports", []) or []
Expand Down Expand Up @@ -244,10 +262,12 @@ def test_definitions(file_path, schema, change_type):
assert this_device.verify_slug(KNOWN_SLUGS), pytest.fail(this_device.failureMessage, False)

# Verify the filename is valid. Must either be the model or part_number.
assert verify_filename(this_device, (KNOWN_MODULES if not this_device.isDevice else None)), pytest.fail(this_device.failureMessage, False)
if not isinstance(this_device, PlatformType):
assert verify_filename(this_device, (KNOWN_MODULES if not this_device.isDevice else None)), pytest.fail(this_device.failureMessage, False)

# Check for duplicate components within the definition
assert validate_components(COMPONENT_TYPES, this_device), pytest.fail(this_device.failureMessage, False)
if not isinstance(this_device, PlatformType):
assert validate_components(COMPONENT_TYPES, this_device), pytest.fail(this_device.failureMessage, False)

# Check for empty quotes and fail if found
def iterdict(var):
Expand Down Expand Up @@ -295,4 +315,22 @@ def iterlist(var):

if not rear_image:
pytest.fail(f'{file_path} has rear_image set to True but no matching image found (looking for \'elevation-images{os.path.sep}{file_path.split(os.path.sep)[1]}{os.path.sep}{this_device.get_slug()}.rear.ext\' but only found {manufacturer_images})', False)

# Validate default_platform references an existing platform file
if "device-types" in file_path and definition.get('default_platform'):
platform_slug = definition['default_platform']
matching_platforms = KNOWN_PLATFORMS.get(platform_slug, [])
if not matching_platforms:
pytest.fail(
f"{file_path} has default_platform '{platform_slug}' but no matching platform definition "
f"was found in platforms/*/. Expected file: platforms/<manufacturer>/{platform_slug}.yaml",
pytrace=False,
)
elif len(matching_platforms) > 1:
pytest.fail(
f"{file_path} has default_platform '{platform_slug}' but multiple matching platform definitions "
f"were found: {matching_platforms}. Platform slugs must be globally unique.",
pytrace=False,
)

iterdict(definition)
16 changes: 16 additions & 0 deletions tests/device_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,22 @@ def _slugify_model(self):
slugified = slugified[:-1]
return slugified

class PlatformType:
def __new__(cls, *args, **kwargs):
return super().__new__(cls)

def __init__(self, definition, file_path, change_type):
self.file_path = file_path
self.isDevice = False
self.definition = definition
self.manufacturer = definition.get('manufacturer')
self.name = definition.get('name')
self.slug = definition.get('slug')
self.change_type = change_type

def get_filepath(self):
return self.file_path

def validate_component_names(component_names: (set or None)):
if len(component_names) > 1:
verify_name = list(component_names[0])
Expand Down
1 change: 1 addition & 0 deletions tests/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
('device-types', 'devicetype.json'),
('module-types', 'moduletype.json'),
('rack-types', 'racktype.json'),
('platforms', 'platformtype.json'),
)
SCHEMAS_BASEPATH = f"{os.getcwd()}/schema/"

Expand Down
Loading