From 6fc4b7e7567bba7b97e5819148e98a310e371d7c Mon Sep 17 00:00:00 2001 From: "Daniel W. Anner" Date: Wed, 2 Aug 2023 20:28:35 +0000 Subject: [PATCH 01/10] Schema generation is working --- .../extras/management/commands/buildschema.py | 40 ++++++++ .../management/templates/generated_schema.j2 | 93 +++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 netbox/extras/management/commands/buildschema.py create mode 100644 netbox/extras/management/templates/generated_schema.j2 diff --git a/netbox/extras/management/commands/buildschema.py b/netbox/extras/management/commands/buildschema.py new file mode 100644 index 00000000000..117457e4979 --- /dev/null +++ b/netbox/extras/management/commands/buildschema.py @@ -0,0 +1,40 @@ +from os import path as os_path +from jinja2 import FileSystemLoader, Environment +from json import dumps as json_dumps + +from django.core.management.base import BaseCommand + +from dcim.choices import DeviceAirflowChoices +from dcim.choices import SubdeviceRoleChoices +from dcim.choices import ConsolePortTypeChoices +from dcim.choices import PowerPortTypeChoices +from dcim.choices import PowerOutletTypeChoices, PowerOutletFeedLegChoices +from dcim.choices import InterfaceTypeChoices, InterfacePoEModeChoices, InterfacePoETypeChoices +from dcim.choices import PortTypeChoices +from dcim.choices import WeightUnitChoices + + +class Command(BaseCommand): + help = "Generate the NetBox validation schemas." + + def handle(self, *args, **options): + schemas = {} + schemas["airflow_choices"] = json_dumps(DeviceAirflowChoices.values()) + schemas["weight_unit_choices"] = json_dumps(WeightUnitChoices.values()) + schemas["subdevice_role_choices"] = json_dumps(SubdeviceRoleChoices.values()) + schemas["console_port_type_choices"] = json_dumps(ConsolePortTypeChoices.values()) # console-ports and console-server-ports + schemas["power_port_type_choices"] = json_dumps(PowerPortTypeChoices.values()) + schemas["power_outlet_type_choices"] = json_dumps(PowerOutletTypeChoices.values()) + schemas["power_outlet_feedleg_choices"] = json_dumps(PowerOutletFeedLegChoices.values()) + schemas["interface_type_choices"] = json_dumps(InterfaceTypeChoices.values()) + schemas["interface_poe_mode_choices"] = json_dumps(InterfacePoEModeChoices.values()) + schemas["interface_poe_type_choices"] = json_dumps(InterfacePoETypeChoices.values()) + schemas["port_type_choices"] = json_dumps(PortTypeChoices.values()) # front-ports and rear-ports + + template_loader = FileSystemLoader(searchpath=f'{os_path.dirname(__file__)}/../templates') + template_env = Environment(loader=template_loader) + TEMPLATE_FILE = 'generated_schema.j2' + template = template_env.get_template(TEMPLATE_FILE) + outputText = template.render(schemas=schemas) + + print(outputText) diff --git a/netbox/extras/management/templates/generated_schema.j2 b/netbox/extras/management/templates/generated_schema.j2 new file mode 100644 index 00000000000..552ab0429c3 --- /dev/null +++ b/netbox/extras/management/templates/generated_schema.j2 @@ -0,0 +1,93 @@ +{ + "type": "object", + "additionalProperties": false, + "definitions": { + "airflow": { + "type": "string", + "enum": {{ schemas["airflow_choices"] }} + }, + "weight-unit": { + "type": "string", + "enum": {{ schemas["weight_unit_choices"] }} + }, + "subdevice-role": { + "type": "string", + "enum": {{ schemas["subdevice_role_choices"] }} + }, + "console-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ schemas["console_port_type_choices"] }} + } + } + }, + "console-server-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ schemas["console_port_type_choices"] }} + } + } + }, + "power-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ schemas["power_port_type_choices"] }} + } + } + }, + "power-outlet": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ schemas["power_outlet_type_choices"] }} + }, + "feed-leg": { + "type": "string", + "enum": {{ schemas["power_outlet_feedleg_choices"] }} + } + } + }, + "interface": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ schemas["interface_type_choices"] }} + }, + "poe_mode": { + "type": "string", + "enum": {{ schemas["interface_poe_mode_choices"] }} + }, + "poe_type": { + "type": "string", + "enum": {{ schemas["interface_poe_type_choices"] }} + } + } + }, + "front-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ schemas["port_type_choices"] }} + } + } + }, + "rear-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": {{ schemas["port_type_choices"]}} + } + } + } + } +} \ No newline at end of file From d42626b7f8db4d5569ada6169bdef37fdc84e9f4 Mon Sep 17 00:00:00 2001 From: "Daniel W. Anner" Date: Wed, 2 Aug 2023 21:03:36 +0000 Subject: [PATCH 02/10] Added option to either dump to a file or the console --- contrib/generated_schema.json | 561 ++++++++++++++++++ .../extras/management/commands/buildschema.py | 28 +- ...ted_schema.j2 => generated_schema.json.j2} | 0 3 files changed, 585 insertions(+), 4 deletions(-) create mode 100644 contrib/generated_schema.json rename netbox/extras/management/templates/{generated_schema.j2 => generated_schema.json.j2} (100%) diff --git a/contrib/generated_schema.json b/contrib/generated_schema.json new file mode 100644 index 00000000000..073654b4008 --- /dev/null +++ b/contrib/generated_schema.json @@ -0,0 +1,561 @@ +{ + "type": "object", + "additionalProperties": false, + "definitions": { + "airflow": { + "type": "string", + "enum": [ + "front-to-rear", + "rear-to-front", + "left-to-right", + "right-to-left", + "side-to-rear", + "passive", + "mixed" + ] + }, + "weight-unit": { + "type": "string", + "enum": [ + "kg", + "g", + "lb", + "oz" + ] + }, + "subdevice-role": { + "type": "string", + "enum": [ + "parent", + "child" + ] + }, + "console-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "de-9", + "db-25", + "rj-11", + "rj-12", + "rj-45", + "mini-din-8", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other" + ] + } + } + }, + "console-server-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "de-9", + "db-25", + "rj-11", + "rj-12", + "rj-45", + "mini-din-8", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "other" + ] + } + } + }, + "power-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "iec-60320-c6", + "iec-60320-c8", + "iec-60320-c14", + "iec-60320-c16", + "iec-60320-c20", + "iec-60320-c22", + "iec-60309-p-n-e-4h", + "iec-60309-p-n-e-6h", + "iec-60309-p-n-e-9h", + "iec-60309-2p-e-4h", + "iec-60309-2p-e-6h", + "iec-60309-2p-e-9h", + "iec-60309-3p-e-4h", + "iec-60309-3p-e-6h", + "iec-60309-3p-e-9h", + "iec-60309-3p-n-e-4h", + "iec-60309-3p-n-e-6h", + "iec-60309-3p-n-e-9h", + "iec-60906-1", + "nbr-14136-10a", + "nbr-14136-20a", + "nema-1-15p", + "nema-5-15p", + "nema-5-20p", + "nema-5-30p", + "nema-5-50p", + "nema-6-15p", + "nema-6-20p", + "nema-6-30p", + "nema-6-50p", + "nema-10-30p", + "nema-10-50p", + "nema-14-20p", + "nema-14-30p", + "nema-14-50p", + "nema-14-60p", + "nema-15-15p", + "nema-15-20p", + "nema-15-30p", + "nema-15-50p", + "nema-15-60p", + "nema-l1-15p", + "nema-l5-15p", + "nema-l5-20p", + "nema-l5-30p", + "nema-l5-50p", + "nema-l6-15p", + "nema-l6-20p", + "nema-l6-30p", + "nema-l6-50p", + "nema-l10-30p", + "nema-l14-20p", + "nema-l14-30p", + "nema-l14-50p", + "nema-l14-60p", + "nema-l15-20p", + "nema-l15-30p", + "nema-l15-50p", + "nema-l15-60p", + "nema-l21-20p", + "nema-l21-30p", + "nema-l22-30p", + "cs6361c", + "cs6365c", + "cs8165c", + "cs8265c", + "cs8365c", + "cs8465c", + "ita-c", + "ita-e", + "ita-f", + "ita-ef", + "ita-g", + "ita-h", + "ita-i", + "ita-j", + "ita-k", + "ita-l", + "ita-m", + "ita-n", + "ita-o", + "usb-a", + "usb-b", + "usb-c", + "usb-mini-a", + "usb-mini-b", + "usb-micro-a", + "usb-micro-b", + "usb-micro-ab", + "usb-3-b", + "usb-3-micro-b", + "dc-terminal", + "saf-d-grid", + "neutrik-powercon-20", + "neutrik-powercon-32", + "neutrik-powercon-true1", + "neutrik-powercon-true1-top", + "ubiquiti-smartpower", + "hardwired", + "other" + ] + } + } + }, + "power-outlet": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "iec-60320-c5", + "iec-60320-c7", + "iec-60320-c13", + "iec-60320-c15", + "iec-60320-c19", + "iec-60320-c21", + "iec-60309-p-n-e-4h", + "iec-60309-p-n-e-6h", + "iec-60309-p-n-e-9h", + "iec-60309-2p-e-4h", + "iec-60309-2p-e-6h", + "iec-60309-2p-e-9h", + "iec-60309-3p-e-4h", + "iec-60309-3p-e-6h", + "iec-60309-3p-e-9h", + "iec-60309-3p-n-e-4h", + "iec-60309-3p-n-e-6h", + "iec-60309-3p-n-e-9h", + "iec-60906-1", + "nbr-14136-10a", + "nbr-14136-20a", + "nema-1-15r", + "nema-5-15r", + "nema-5-20r", + "nema-5-30r", + "nema-5-50r", + "nema-6-15r", + "nema-6-20r", + "nema-6-30r", + "nema-6-50r", + "nema-10-30r", + "nema-10-50r", + "nema-14-20r", + "nema-14-30r", + "nema-14-50r", + "nema-14-60r", + "nema-15-15r", + "nema-15-20r", + "nema-15-30r", + "nema-15-50r", + "nema-15-60r", + "nema-l1-15r", + "nema-l5-15r", + "nema-l5-20r", + "nema-l5-30r", + "nema-l5-50r", + "nema-l6-15r", + "nema-l6-20r", + "nema-l6-30r", + "nema-l6-50r", + "nema-l10-30r", + "nema-l14-20r", + "nema-l14-30r", + "nema-l14-50r", + "nema-l14-60r", + "nema-l15-20r", + "nema-l15-30r", + "nema-l15-50r", + "nema-l15-60r", + "nema-l21-20r", + "nema-l21-30r", + "nema-l22-30r", + "CS6360C", + "CS6364C", + "CS8164C", + "CS8264C", + "CS8364C", + "CS8464C", + "ita-e", + "ita-f", + "ita-g", + "ita-h", + "ita-i", + "ita-j", + "ita-k", + "ita-l", + "ita-m", + "ita-n", + "ita-o", + "ita-multistandard", + "usb-a", + "usb-micro-b", + "usb-c", + "dc-terminal", + "hdot-cx", + "saf-d-grid", + "neutrik-powercon-20a", + "neutrik-powercon-32a", + "neutrik-powercon-true1", + "neutrik-powercon-true1-top", + "ubiquiti-smartpower", + "hardwired", + "other" + ] + }, + "feed-leg": { + "type": "string", + "enum": [ + "A", + "B", + "C" + ] + } + } + }, + "interface": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "virtual", + "bridge", + "lag", + "100base-fx", + "100base-lfx", + "100base-tx", + "100base-t1", + "1000base-t", + "2.5gbase-t", + "5gbase-t", + "10gbase-t", + "10gbase-cx4", + "1000base-x-gbic", + "1000base-x-sfp", + "10gbase-x-sfpp", + "10gbase-x-xfp", + "10gbase-x-xenpak", + "10gbase-x-x2", + "25gbase-x-sfp28", + "50gbase-x-sfp56", + "40gbase-x-qsfpp", + "50gbase-x-sfp28", + "100gbase-x-cfp", + "100gbase-x-cfp2", + "200gbase-x-cfp2", + "100gbase-x-cfp4", + "100gbase-x-cxp", + "100gbase-x-cpak", + "100gbase-x-dsfp", + "100gbase-x-sfpdd", + "100gbase-x-qsfp28", + "100gbase-x-qsfpdd", + "200gbase-x-qsfp56", + "200gbase-x-qsfpdd", + "400gbase-x-qsfpdd", + "400gbase-x-osfp", + "400gbase-x-cdfp", + "400gbase-x-cfp8", + "800gbase-x-qsfpdd", + "800gbase-x-osfp", + "1000base-kx", + "10gbase-kr", + "10gbase-kx4", + "25gbase-kr", + "40gbase-kr4", + "50gbase-kr", + "100gbase-kp4", + "100gbase-kr2", + "100gbase-kr4", + "ieee802.11a", + "ieee802.11g", + "ieee802.11n", + "ieee802.11ac", + "ieee802.11ad", + "ieee802.11ax", + "ieee802.11ay", + "ieee802.15.1", + "other-wireless", + "gsm", + "cdma", + "lte", + "sonet-oc3", + "sonet-oc12", + "sonet-oc48", + "sonet-oc192", + "sonet-oc768", + "sonet-oc1920", + "sonet-oc3840", + "1gfc-sfp", + "2gfc-sfp", + "4gfc-sfp", + "8gfc-sfpp", + "16gfc-sfpp", + "32gfc-sfp28", + "64gfc-qsfpp", + "128gfc-qsfp28", + "infiniband-sdr", + "infiniband-ddr", + "infiniband-qdr", + "infiniband-fdr10", + "infiniband-fdr", + "infiniband-edr", + "infiniband-hdr", + "infiniband-ndr", + "infiniband-xdr", + "t1", + "e1", + "t3", + "e3", + "xdsl", + "docsis", + "gpon", + "xg-pon", + "xgs-pon", + "ng-pon2", + "epon", + "10g-epon", + "cisco-stackwise", + "cisco-stackwise-plus", + "cisco-flexstack", + "cisco-flexstack-plus", + "cisco-stackwise-80", + "cisco-stackwise-160", + "cisco-stackwise-320", + "cisco-stackwise-480", + "cisco-stackwise-1t", + "juniper-vcp", + "extreme-summitstack", + "extreme-summitstack-128", + "extreme-summitstack-256", + "extreme-summitstack-512", + "other" + ] + }, + "poe_mode": { + "type": "string", + "enum": [ + "pd", + "pse" + ] + }, + "poe_type": { + "type": "string", + "enum": [ + "type1-ieee802.3af", + "type2-ieee802.3at", + "type3-ieee802.3bt", + "type4-ieee802.3bt", + "passive-24v-2pair", + "passive-24v-4pair", + "passive-48v-2pair", + "passive-48v-4pair" + ] + } + } + }, + "front-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "8p8c", + "8p6c", + "8p4c", + "8p2c", + "6p6c", + "6p4c", + "6p2c", + "4p4c", + "4p2c", + "gg45", + "tera-4p", + "tera-2p", + "tera-1p", + "110-punch", + "bnc", + "f", + "n", + "mrj21", + "fc", + "lc", + "lc-pc", + "lc-upc", + "lc-apc", + "lsh", + "lsh-pc", + "lsh-upc", + "lsh-apc", + "lx5", + "lx5-pc", + "lx5-upc", + "lx5-apc", + "mpo", + "mtrj", + "sc", + "sc-pc", + "sc-upc", + "sc-apc", + "st", + "cs", + "sn", + "sma-905", + "sma-906", + "urm-p2", + "urm-p4", + "urm-p8", + "splice", + "other" + ] + } + } + }, + "rear-port": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "8p8c", + "8p6c", + "8p4c", + "8p2c", + "6p6c", + "6p4c", + "6p2c", + "4p4c", + "4p2c", + "gg45", + "tera-4p", + "tera-2p", + "tera-1p", + "110-punch", + "bnc", + "f", + "n", + "mrj21", + "fc", + "lc", + "lc-pc", + "lc-upc", + "lc-apc", + "lsh", + "lsh-pc", + "lsh-upc", + "lsh-apc", + "lx5", + "lx5-pc", + "lx5-upc", + "lx5-apc", + "mpo", + "mtrj", + "sc", + "sc-pc", + "sc-upc", + "sc-apc", + "st", + "cs", + "sn", + "sma-905", + "sma-906", + "urm-p2", + "urm-p4", + "urm-p8", + "splice", + "other" + ] + } + } + } + } +} \ No newline at end of file diff --git a/netbox/extras/management/commands/buildschema.py b/netbox/extras/management/commands/buildschema.py index 117457e4979..e697765166e 100644 --- a/netbox/extras/management/commands/buildschema.py +++ b/netbox/extras/management/commands/buildschema.py @@ -1,6 +1,7 @@ from os import path as os_path -from jinja2 import FileSystemLoader, Environment from json import dumps as json_dumps +from json import loads as json_loads +from jinja2 import FileSystemLoader, Environment from django.core.management.base import BaseCommand @@ -17,7 +18,19 @@ class Command(BaseCommand): help = "Generate the NetBox validation schemas." - def handle(self, *args, **options): + def add_arguments(self, parser): + parser.add_argument( + '--console', + action='store_true', + help="Print the generated schema to stdout" + ) + parser.add_argument( + '--file', + action='store_true', + help="Print the generated schema to the generated file" + ) + + def handle(self, *args, **kwargs): schemas = {} schemas["airflow_choices"] = json_dumps(DeviceAirflowChoices.values()) schemas["weight_unit_choices"] = json_dumps(WeightUnitChoices.values()) @@ -33,8 +46,15 @@ def handle(self, *args, **options): template_loader = FileSystemLoader(searchpath=f'{os_path.dirname(__file__)}/../templates') template_env = Environment(loader=template_loader) - TEMPLATE_FILE = 'generated_schema.j2' + TEMPLATE_FILE = 'generated_schema.json.j2' template = template_env.get_template(TEMPLATE_FILE) outputText = template.render(schemas=schemas) - print(outputText) + if kwargs['console']: + print(json_dumps(json_loads(outputText), indent=4)) + + if kwargs['file']: + print() + with open(f'{os_path.dirname(__file__)}/../../../../contrib/generated_schema.json', 'w') as generated_json_file: + generated_json_file.write(json_dumps(json_loads(outputText), indent=4)) + generated_json_file.close() diff --git a/netbox/extras/management/templates/generated_schema.j2 b/netbox/extras/management/templates/generated_schema.json.j2 similarity index 100% rename from netbox/extras/management/templates/generated_schema.j2 rename to netbox/extras/management/templates/generated_schema.json.j2 From cdd3bc31ba798745cb3047328c1afca5ece3184b Mon Sep 17 00:00:00 2001 From: "Daniel W. Anner" Date: Fri, 4 Aug 2023 15:32:47 +0000 Subject: [PATCH 03/10] Moving schema file and utilizing settings definition for file paths --- netbox/extras/management/commands/buildschema.py | 7 ++++--- .../extras/generated_schema.json} | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) rename netbox/{extras/management/templates/generated_schema.json.j2 => templates/extras/generated_schema.json} (99%) diff --git a/netbox/extras/management/commands/buildschema.py b/netbox/extras/management/commands/buildschema.py index e697765166e..82edc81b3eb 100644 --- a/netbox/extras/management/commands/buildschema.py +++ b/netbox/extras/management/commands/buildschema.py @@ -4,6 +4,7 @@ from jinja2 import FileSystemLoader, Environment from django.core.management.base import BaseCommand +from django.conf import settings from dcim.choices import DeviceAirflowChoices from dcim.choices import SubdeviceRoleChoices @@ -44,9 +45,9 @@ def handle(self, *args, **kwargs): schemas["interface_poe_type_choices"] = json_dumps(InterfacePoETypeChoices.values()) schemas["port_type_choices"] = json_dumps(PortTypeChoices.values()) # front-ports and rear-ports - template_loader = FileSystemLoader(searchpath=f'{os_path.dirname(__file__)}/../templates') + template_loader = FileSystemLoader(searchpath=f'{settings.TEMPLATES_DIR}/extras/') template_env = Environment(loader=template_loader) - TEMPLATE_FILE = 'generated_schema.json.j2' + TEMPLATE_FILE = 'generated_schema.json' template = template_env.get_template(TEMPLATE_FILE) outputText = template.render(schemas=schemas) @@ -55,6 +56,6 @@ def handle(self, *args, **kwargs): if kwargs['file']: print() - with open(f'{os_path.dirname(__file__)}/../../../../contrib/generated_schema.json', 'w') as generated_json_file: + with open(f'{settings.BASE_DIR}/../contrib/generated_schema.json', 'w') as generated_json_file: generated_json_file.write(json_dumps(json_loads(outputText), indent=4)) generated_json_file.close() diff --git a/netbox/extras/management/templates/generated_schema.json.j2 b/netbox/templates/extras/generated_schema.json similarity index 99% rename from netbox/extras/management/templates/generated_schema.json.j2 rename to netbox/templates/extras/generated_schema.json index 552ab0429c3..978cfc755b4 100644 --- a/netbox/extras/management/templates/generated_schema.json.j2 +++ b/netbox/templates/extras/generated_schema.json @@ -90,4 +90,4 @@ } } } -} \ No newline at end of file +} From 71983ad84ebbdb737a3dd9a6038fedf9699c5768 Mon Sep 17 00:00:00 2001 From: "Daniel W. Anner" Date: Fri, 4 Aug 2023 15:48:16 +0000 Subject: [PATCH 04/10] Cleaning up the imports and fixing a few pythonic issues --- contrib/generated_schema.json | 2 +- .../extras/management/commands/buildschema.py | 20 ++++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/contrib/generated_schema.json b/contrib/generated_schema.json index 073654b4008..8dbcb284709 100644 --- a/contrib/generated_schema.json +++ b/contrib/generated_schema.json @@ -558,4 +558,4 @@ } } } -} \ No newline at end of file +} diff --git a/netbox/extras/management/commands/buildschema.py b/netbox/extras/management/commands/buildschema.py index 82edc81b3eb..5e5b1a74470 100644 --- a/netbox/extras/management/commands/buildschema.py +++ b/netbox/extras/management/commands/buildschema.py @@ -1,19 +1,15 @@ -from os import path as os_path from json import dumps as json_dumps from json import loads as json_loads from jinja2 import FileSystemLoader, Environment -from django.core.management.base import BaseCommand from django.conf import settings +from django.core.management.base import BaseCommand -from dcim.choices import DeviceAirflowChoices -from dcim.choices import SubdeviceRoleChoices -from dcim.choices import ConsolePortTypeChoices -from dcim.choices import PowerPortTypeChoices -from dcim.choices import PowerOutletTypeChoices, PowerOutletFeedLegChoices -from dcim.choices import InterfaceTypeChoices, InterfacePoEModeChoices, InterfacePoETypeChoices -from dcim.choices import PortTypeChoices -from dcim.choices import WeightUnitChoices +from dcim.choices import ( + DeviceAirflowChoices, SubdeviceRoleChoices, ConsolePortTypeChoices, PowerPortTypeChoices, + PowerOutletTypeChoices, PowerOutletFeedLegChoices, InterfaceTypeChoices, InterfacePoEModeChoices, + InterfacePoETypeChoices, PortTypeChoices, WeightUnitChoices +) class Command(BaseCommand): @@ -55,7 +51,7 @@ def handle(self, *args, **kwargs): print(json_dumps(json_loads(outputText), indent=4)) if kwargs['file']: - print() - with open(f'{settings.BASE_DIR}/../contrib/generated_schema.json', 'w') as generated_json_file: + with open(f'{settings.BASE_DIR}/../contrib/generated_schema.json', mode='w', encoding='UTF-8') as generated_json_file: generated_json_file.write(json_dumps(json_loads(outputText), indent=4)) + generated_json_file.write('\n') generated_json_file.close() From ebca49f8e3924f7931999dd4dcc3f27bf2a6a54e Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 7 Aug 2023 15:51:22 -0400 Subject: [PATCH 05/10] Tweak command flags --- .../extras/management/commands/buildschema.py | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/netbox/extras/management/commands/buildschema.py b/netbox/extras/management/commands/buildschema.py index 5e5b1a74470..ba0260eab94 100644 --- a/netbox/extras/management/commands/buildschema.py +++ b/netbox/extras/management/commands/buildschema.py @@ -1,9 +1,10 @@ +import os from json import dumps as json_dumps from json import loads as json_loads -from jinja2 import FileSystemLoader, Environment from django.conf import settings from django.core.management.base import BaseCommand +from jinja2 import FileSystemLoader, Environment from dcim.choices import ( DeviceAirflowChoices, SubdeviceRoleChoices, ConsolePortTypeChoices, PowerPortTypeChoices, @@ -11,20 +12,18 @@ InterfacePoETypeChoices, PortTypeChoices, WeightUnitChoices ) +TEMPLATE_FILENAME = 'generated_schema.json' +OUTPUT_FILENAME = 'contrib/generated_schema.json' + class Command(BaseCommand): help = "Generate the NetBox validation schemas." def add_arguments(self, parser): parser.add_argument( - '--console', + '--write', action='store_true', - help="Print the generated schema to stdout" - ) - parser.add_argument( - '--file', - action='store_true', - help="Print the generated schema to the generated file" + help="Write the generated schema to file" ) def handle(self, *args, **kwargs): @@ -43,15 +42,16 @@ def handle(self, *args, **kwargs): template_loader = FileSystemLoader(searchpath=f'{settings.TEMPLATES_DIR}/extras/') template_env = Environment(loader=template_loader) - TEMPLATE_FILE = 'generated_schema.json' - template = template_env.get_template(TEMPLATE_FILE) + template = template_env.get_template(TEMPLATE_FILENAME) outputText = template.render(schemas=schemas) - if kwargs['console']: - print(json_dumps(json_loads(outputText), indent=4)) - - if kwargs['file']: - with open(f'{settings.BASE_DIR}/../contrib/generated_schema.json', mode='w', encoding='UTF-8') as generated_json_file: - generated_json_file.write(json_dumps(json_loads(outputText), indent=4)) - generated_json_file.write('\n') - generated_json_file.close() + if kwargs['write']: + # $root/contrib/generated_schema.json + filename = os.path.join(os.path.split(settings.BASE_DIR)[0], OUTPUT_FILENAME) + with open(filename, mode='w', encoding='UTF-8') as f: + f.write(json_dumps(json_loads(outputText), indent=4)) + f.write('\n') + f.close() + self.stdout.write(self.style.SUCCESS(f"Schema written to {filename}.")) + else: + self.stdout.write(outputText) From 22ecc96de20318727fb92d6223d4a92770e1fdba Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 7 Aug 2023 16:08:19 -0400 Subject: [PATCH 06/10] Clean up choices mapping --- .../extras/management/commands/buildschema.py | 39 +++++++++++-------- netbox/templates/extras/generated_schema.json | 26 ++++++------- 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/netbox/extras/management/commands/buildschema.py b/netbox/extras/management/commands/buildschema.py index ba0260eab94..102be1d6405 100644 --- a/netbox/extras/management/commands/buildschema.py +++ b/netbox/extras/management/commands/buildschema.py @@ -15,6 +15,22 @@ TEMPLATE_FILENAME = 'generated_schema.json' OUTPUT_FILENAME = 'contrib/generated_schema.json' +CHOICES_MAP = { + 'airflow_choices': DeviceAirflowChoices, + 'weight_unit_choices': WeightUnitChoices, + 'subdevice_role_choices': SubdeviceRoleChoices, + 'console_port_type_choices': ConsolePortTypeChoices, + 'console_server_port_type_choices': ConsolePortTypeChoices, # Reusing ConsolePortTypeChoices + 'power_port_type_choices': PowerPortTypeChoices, + 'power_outlet_type_choices': PowerOutletTypeChoices, + 'power_outlet_feedleg_choices': PowerOutletFeedLegChoices, + 'interface_type_choices': InterfaceTypeChoices, + 'interface_poe_mode_choices': InterfacePoEModeChoices, + 'interface_poe_type_choices': InterfacePoETypeChoices, + 'front_port_type_choices': PortTypeChoices, + 'rear_port_type_choices': PortTypeChoices, # Reusing PortTypeChoices +} + class Command(BaseCommand): help = "Generate the NetBox validation schemas." @@ -27,31 +43,22 @@ def add_arguments(self, parser): ) def handle(self, *args, **kwargs): - schemas = {} - schemas["airflow_choices"] = json_dumps(DeviceAirflowChoices.values()) - schemas["weight_unit_choices"] = json_dumps(WeightUnitChoices.values()) - schemas["subdevice_role_choices"] = json_dumps(SubdeviceRoleChoices.values()) - schemas["console_port_type_choices"] = json_dumps(ConsolePortTypeChoices.values()) # console-ports and console-server-ports - schemas["power_port_type_choices"] = json_dumps(PowerPortTypeChoices.values()) - schemas["power_outlet_type_choices"] = json_dumps(PowerOutletTypeChoices.values()) - schemas["power_outlet_feedleg_choices"] = json_dumps(PowerOutletFeedLegChoices.values()) - schemas["interface_type_choices"] = json_dumps(InterfaceTypeChoices.values()) - schemas["interface_poe_mode_choices"] = json_dumps(InterfacePoEModeChoices.values()) - schemas["interface_poe_type_choices"] = json_dumps(InterfacePoETypeChoices.values()) - schemas["port_type_choices"] = json_dumps(PortTypeChoices.values()) # front-ports and rear-ports - template_loader = FileSystemLoader(searchpath=f'{settings.TEMPLATES_DIR}/extras/') template_env = Environment(loader=template_loader) template = template_env.get_template(TEMPLATE_FILENAME) - outputText = template.render(schemas=schemas) + context = { + key: json_dumps(choices.values()) + for key, choices in CHOICES_MAP.items() + } + rendered = template.render(**context) if kwargs['write']: # $root/contrib/generated_schema.json filename = os.path.join(os.path.split(settings.BASE_DIR)[0], OUTPUT_FILENAME) with open(filename, mode='w', encoding='UTF-8') as f: - f.write(json_dumps(json_loads(outputText), indent=4)) + f.write(json_dumps(json_loads(rendered), indent=4)) f.write('\n') f.close() self.stdout.write(self.style.SUCCESS(f"Schema written to {filename}.")) else: - self.stdout.write(outputText) + self.stdout.write(rendered) diff --git a/netbox/templates/extras/generated_schema.json b/netbox/templates/extras/generated_schema.json index 978cfc755b4..b08ab24dec8 100644 --- a/netbox/templates/extras/generated_schema.json +++ b/netbox/templates/extras/generated_schema.json @@ -4,22 +4,22 @@ "definitions": { "airflow": { "type": "string", - "enum": {{ schemas["airflow_choices"] }} + "enum": {{ airflow_choices }} }, "weight-unit": { "type": "string", - "enum": {{ schemas["weight_unit_choices"] }} + "enum": {{ weight_unit_choices }} }, "subdevice-role": { "type": "string", - "enum": {{ schemas["subdevice_role_choices"] }} + "enum": {{ subdevice_role_choices }} }, "console-port": { "type": "object", "properties": { "type": { "type": "string", - "enum": {{ schemas["console_port_type_choices"] }} + "enum": {{ console_port_type_choices }} } } }, @@ -28,7 +28,7 @@ "properties": { "type": { "type": "string", - "enum": {{ schemas["console_port_type_choices"] }} + "enum": {{ console_server_port_type_choices }} } } }, @@ -37,7 +37,7 @@ "properties": { "type": { "type": "string", - "enum": {{ schemas["power_port_type_choices"] }} + "enum": {{ power_port_type_choices }} } } }, @@ -46,11 +46,11 @@ "properties": { "type": { "type": "string", - "enum": {{ schemas["power_outlet_type_choices"] }} + "enum": {{ power_outlet_type_choices }} }, "feed-leg": { "type": "string", - "enum": {{ schemas["power_outlet_feedleg_choices"] }} + "enum": {{ power_outlet_feedleg_choices }} } } }, @@ -59,15 +59,15 @@ "properties": { "type": { "type": "string", - "enum": {{ schemas["interface_type_choices"] }} + "enum": {{ interface_type_choices }} }, "poe_mode": { "type": "string", - "enum": {{ schemas["interface_poe_mode_choices"] }} + "enum": {{ interface_poe_mode_choices }} }, "poe_type": { "type": "string", - "enum": {{ schemas["interface_poe_type_choices"] }} + "enum": {{ interface_poe_type_choices }} } } }, @@ -76,7 +76,7 @@ "properties": { "type": { "type": "string", - "enum": {{ schemas["port_type_choices"] }} + "enum": {{ front_port_type_choices }} } } }, @@ -85,7 +85,7 @@ "properties": { "type": { "type": "string", - "enum": {{ schemas["port_type_choices"]}} + "enum": {{ rear_port_type_choices}} } } } From b699c7bd9862a77d1081feaffbbc931d8d5b9a42 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 7 Aug 2023 16:19:00 -0400 Subject: [PATCH 07/10] Misc cleanup --- .../extras/management/commands/buildschema.py | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/netbox/extras/management/commands/buildschema.py b/netbox/extras/management/commands/buildschema.py index 102be1d6405..a9512a5e0aa 100644 --- a/netbox/extras/management/commands/buildschema.py +++ b/netbox/extras/management/commands/buildschema.py @@ -1,16 +1,11 @@ +import json import os -from json import dumps as json_dumps -from json import loads as json_loads from django.conf import settings from django.core.management.base import BaseCommand from jinja2 import FileSystemLoader, Environment -from dcim.choices import ( - DeviceAirflowChoices, SubdeviceRoleChoices, ConsolePortTypeChoices, PowerPortTypeChoices, - PowerOutletTypeChoices, PowerOutletFeedLegChoices, InterfaceTypeChoices, InterfacePoEModeChoices, - InterfacePoETypeChoices, PortTypeChoices, WeightUnitChoices -) +from dcim.choices import * TEMPLATE_FILENAME = 'generated_schema.json' OUTPUT_FILENAME = 'contrib/generated_schema.json' @@ -20,7 +15,7 @@ 'weight_unit_choices': WeightUnitChoices, 'subdevice_role_choices': SubdeviceRoleChoices, 'console_port_type_choices': ConsolePortTypeChoices, - 'console_server_port_type_choices': ConsolePortTypeChoices, # Reusing ConsolePortTypeChoices + 'console_server_port_type_choices': ConsolePortTypeChoices, 'power_port_type_choices': PowerPortTypeChoices, 'power_outlet_type_choices': PowerOutletTypeChoices, 'power_outlet_feedleg_choices': PowerOutletFeedLegChoices, @@ -28,12 +23,12 @@ 'interface_poe_mode_choices': InterfacePoEModeChoices, 'interface_poe_type_choices': InterfacePoETypeChoices, 'front_port_type_choices': PortTypeChoices, - 'rear_port_type_choices': PortTypeChoices, # Reusing PortTypeChoices + 'rear_port_type_choices': PortTypeChoices, } class Command(BaseCommand): - help = "Generate the NetBox validation schemas." + help = "Generate JSON schema for the NetBox device type library" def add_arguments(self, parser): parser.add_argument( @@ -43,11 +38,14 @@ def add_arguments(self, parser): ) def handle(self, *args, **kwargs): + # Initialize template template_loader = FileSystemLoader(searchpath=f'{settings.TEMPLATES_DIR}/extras/') template_env = Environment(loader=template_loader) template = template_env.get_template(TEMPLATE_FILENAME) + + # Render template context = { - key: json_dumps(choices.values()) + key: json.dumps(choices.values()) for key, choices in CHOICES_MAP.items() } rendered = template.render(**context) @@ -56,7 +54,7 @@ def handle(self, *args, **kwargs): # $root/contrib/generated_schema.json filename = os.path.join(os.path.split(settings.BASE_DIR)[0], OUTPUT_FILENAME) with open(filename, mode='w', encoding='UTF-8') as f: - f.write(json_dumps(json_loads(rendered), indent=4)) + f.write(json.dumps(json.loads(rendered), indent=4)) f.write('\n') f.close() self.stdout.write(self.style.SUCCESS(f"Schema written to {filename}.")) From c75211d10064e96b3cf25db092c75fc7e8f977d0 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 7 Aug 2023 16:20:48 -0400 Subject: [PATCH 08/10] Rename & move template file --- netbox/extras/management/commands/buildschema.py | 4 ++-- .../devicetype_schema.jinja2} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename netbox/templates/extras/{generated_schema.json => schema/devicetype_schema.jinja2} (100%) diff --git a/netbox/extras/management/commands/buildschema.py b/netbox/extras/management/commands/buildschema.py index a9512a5e0aa..e7e0776edaf 100644 --- a/netbox/extras/management/commands/buildschema.py +++ b/netbox/extras/management/commands/buildschema.py @@ -7,7 +7,7 @@ from dcim.choices import * -TEMPLATE_FILENAME = 'generated_schema.json' +TEMPLATE_FILENAME = 'devicetype_schema.jinja2' OUTPUT_FILENAME = 'contrib/generated_schema.json' CHOICES_MAP = { @@ -39,7 +39,7 @@ def add_arguments(self, parser): def handle(self, *args, **kwargs): # Initialize template - template_loader = FileSystemLoader(searchpath=f'{settings.TEMPLATES_DIR}/extras/') + template_loader = FileSystemLoader(searchpath=f'{settings.TEMPLATES_DIR}/extras/schema/') template_env = Environment(loader=template_loader) template = template_env.get_template(TEMPLATE_FILENAME) diff --git a/netbox/templates/extras/generated_schema.json b/netbox/templates/extras/schema/devicetype_schema.jinja2 similarity index 100% rename from netbox/templates/extras/generated_schema.json rename to netbox/templates/extras/schema/devicetype_schema.jinja2 From b556e9c40eaffe3e7dca5029e3671bf02f78e26a Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 11 Aug 2023 10:12:14 -0400 Subject: [PATCH 09/10] Move management command from extras to dcim --- netbox/{extras => dcim}/management/commands/buildschema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename netbox/{extras => dcim}/management/commands/buildschema.py (96%) diff --git a/netbox/extras/management/commands/buildschema.py b/netbox/dcim/management/commands/buildschema.py similarity index 96% rename from netbox/extras/management/commands/buildschema.py rename to netbox/dcim/management/commands/buildschema.py index e7e0776edaf..44a0e95f274 100644 --- a/netbox/extras/management/commands/buildschema.py +++ b/netbox/dcim/management/commands/buildschema.py @@ -28,7 +28,7 @@ class Command(BaseCommand): - help = "Generate JSON schema for the NetBox device type library" + help = "Generate JSON schema for validating NetBox device type definitions" def add_arguments(self, parser): parser.add_argument( From c4bf3ca3f4a725ef849e4fabe2369a3d5cd91e21 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Fri, 11 Aug 2023 10:15:46 -0400 Subject: [PATCH 10/10] Update release checklist --- docs/development/release-checklist.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/development/release-checklist.md b/docs/development/release-checklist.md index efb0f44b964..a5c847a409a 100644 --- a/docs/development/release-checklist.md +++ b/docs/development/release-checklist.md @@ -58,6 +58,16 @@ Before each release, update each of NetBox's Python dependencies to its most rec In cases where upgrading a dependency to its most recent release is breaking, it should be constrained to its current minor version in `base_requirements.txt` with an explanatory comment and revisited for the next major NetBox release (see the [Address Constrained Dependencies](#address-constrained-dependencies) section above). +### Rebuild the Device Type Definition Schema + +Run the following command to update the device type definition validation schema: + +```nohighlight +./manage.py buildschema --write +``` + +This will automatically update the schema file at `contrib/generated_schema.json`. + ### Update Version and Changelog * Update the `VERSION` constant in `settings.py` to the new release version.