Compare commits

...

35 Commits
0.5.1 ... 0.7.0

Author SHA1 Message Date
1fa07c6dc1 Bump VERSION file 2019-01-07 02:12:44 -08:00
c866bfff16 add note about breaking changes 2019-01-07 11:11:48 +01:00
44cdb73cc4 Merge pull request #109 from TakeMeNL/feature/105
#105 Change default api page size to 1000
2019-01-07 11:09:28 +01:00
20a86f0e98 Merge branch 'master' into feature/105 2019-01-07 10:47:15 +01:00
1f9ea8db53 Merge branch 'bdlamprecht-master' 2019-01-07 10:44:20 +01:00
db7daee86e formatted 2019-01-07 10:43:41 +01:00
0bae952410 Documenting reporting usage in README.md
Updated `README.md` on how to use the reporting feature of NetBox within this container.
2019-01-02 17:12:46 -07:00
34cd2cc577 Renamed devices.py to devices.py.example to set the example report to be disabled by default 2019-01-02 16:45:02 +00:00
e55580e3ae #105 Change default api page size to 1000 2018-12-27 22:12:17 +01:00
aab28d03ba Changing 'reports' from a volume to a mapped directory
This will allow eaiser interaction with the report functionality of the 'netbox' container.
2018-12-19 16:26:24 -07:00
eb09bf5364 Merge pull request #106 from ninech/ignore_missing_initializers
Ignore missing initializers
2018-12-19 17:03:56 +01:00
e46a7d2f7f Ignore missing initializers
Previously, the startup_scripts would fail if an initializer file was
not present. Now they just ignore missing files.
2018-12-19 14:25:58 +01:00
03eb153da4 Mention the #netbox-docker Slack channel 2018-11-12 21:54:14 +01:00
7675e8fc03 Update Breaking Changes list 2018-10-31 14:37:07 +01:00
d92f991cbe Bump VERSION file 2018-10-31 05:30:43 -07:00
c16f72a2ac Merge pull request #104 from ninech/add-more-seeds
Add seed/initialization scripts for additional resources
2018-10-31 13:27:54 +01:00
2fe139cb3c Reorder device types data 2018-10-31 10:58:42 +01:00
aa68548f41 Add Plaform seeds 2018-10-30 14:22:04 +01:00
a10cd805ae Prefix output messages with appropriate emoji 2018-10-30 10:51:43 +01:00
8b8620864c Replace trademarked names with generic ones 2018-10-30 10:11:05 +01:00
d145e9c719 Comply to README style 2018-10-30 10:07:33 +01:00
a120a95184 Fix initializers 2018-10-16 13:26:23 +02:00
97477556e0 Increase order prefix to 3 digits 2018-10-16 13:26:13 +02:00
7e6edd1bf5 Merge branch 'master' into add-more-seeds 2018-10-16 11:32:41 +02:00
b53e886f8f Handle all associations 2018-10-16 11:05:28 +02:00
6cc4c67387 Remove default pop value 2018-10-16 09:12:43 +02:00
ab0ce20971 Update README 2018-10-15 15:15:56 +02:00
90ae5cf01d Add device seeds 2018-10-15 15:15:41 +02:00
ebb7779b5f Add device role seeds 2018-10-15 15:15:23 +02:00
60f7de1898 Add rack seeds 2018-10-15 15:15:09 +02:00
89fddbe0ab Add rack role seeds 2018-10-15 15:14:42 +02:00
a2b08a6ca5 Add device type seeds 2018-10-15 15:14:27 +02:00
86675278ab Add manufacturer seeds 2018-10-15 15:14:11 +02:00
791027f77b Add site seeds 2018-10-15 15:13:51 +02:00
819f325bd5 Add region seeds 2018-10-15 15:13:26 +02:00
28 changed files with 654 additions and 19 deletions

View File

@ -9,6 +9,12 @@ Before raising an issue here, answer the following questions for yourself, pleas
(Otherwise ask on the Netbox mailing list, please: https://groups.google.com/d/forum/netbox-discuss)
* Have you looked through the issues already resolved?
Please try this means to get help before opening an issue here:
* On the networktocode Slack in the #netbox-docker channel: http://slack.networktocode.com/
* On the networktocode Slack in the #netbox channel: http://slack.networktocode.com/
* On the Netbox mailing list: https://groups.google.com/d/forum/netbox-discuss
-->
## Current Behavior
@ -30,17 +36,19 @@ The output of `git rev-parse HEAD`: `XXXXX`
The command you used to start the project: `XXXXX`
The output of `docker-compose logs netbox`:
<!-- if your log is very long, create a Gist instead: https://gist.github.com -->
```
LOG LOG LOG
```
<!--
If you have get any 5xx http error, else delete this section.
If your log is very long, create a Gist instead: https://gist.github.com
If your log is very long, create a Gist instead (and post the link to it): https://gist.github.com
-->
```
LOG LOG LOG
```
The output of `docker-compose logs nginx`:
<!--
Only if you have gotten a 5xx http error, else delete this section.
If your log is very long, create a Gist instead (and post the link to it): https://gist.github.com
-->
The output of `docker-compose logs nginx`:
```
LOG LOG LOG

View File

@ -131,6 +131,18 @@ However, if you don't need this functionality, leave these blank.
[napalm-doc]: http://napalm.readthedocs.io/en/latest/index.html
[netbox-napalm-doc]: https://netbox.readthedocs.io/en/latest/configuration/optional-settings/#napalm_username
### Customizable Reporting
NetBox includes [customized reporting][netbox-reports-doc] that allows the user to write Python code and determine the validity of the data within NetBox.
The `REPORTS_ROOT` variable is setup as a mapped directory within this Docker container to `/reports/` and includes the example directly from the documentation for `devices.py`.
However, it has been renamed to `devices.py.example` which prevents NetBox from recognizing it as a valid report.
This was done to avoid unnessary issues from being opened when the default does not work for someone's expectations.
To re-enable this default report, simply rename `devices.py.example` to `devices.py` and browse within the WebUI to `/extras/reports/`.
You can also dynamically add any other report to this same directory and NetBox will be able to see it without restarting the container.
[netbox-reports-doc]: https://netbox.readthedocs.io/en/stable/additional-features/reports/
### Custom Initialization Code (e.g. Automatically Setting Up Custom Fields)
When using `docker-compose`, all the python scripts present in `/opt/netbox/startup_scripts` will automatically be executed after the application boots in the context of `./manage.py`.
@ -161,7 +173,7 @@ if created:
#### Initializers
Initializers are built-in startup scripts for defining NetBox custom fields, groups and users.
Initializers are built-in startup scripts for defining NetBox custom fields, groups, users and many other resources.
All you need to do is to mount you own `initializers` folder ([see `docker-compose.yml`][netbox-docker-compose]).
Look at the [`initializers` folder][netbox-docker-initializers] to learn how the files must look like.
@ -325,10 +337,10 @@ Then make sure that the `redis` container and at least one `netbox-worker` are r
# check the container status
$ docker-compose ps
Name Command State Ports
Name Command State Ports
--------------------------------------------------------------------------------------------------------
netbox-docker_netbox-worker_1 /opt/netbox/docker-entrypo ... Up
netbox-docker_netbox_1 /opt/netbox/docker-entrypo ... Up
netbox-docker_netbox-worker_1 /opt/netbox/docker-entrypo ... Up
netbox-docker_netbox_1 /opt/netbox/docker-entrypo ... Up
netbox-docker_nginx_1 nginx -c /etc/netbox-nginx ... Up 80/tcp, 0.0.0.0:32776->8080/tcp
netbox-docker_postgres_1 docker-entrypoint.sh postgres Up 5432/tcp
netbox-docker_redis_1 docker-entrypoint.sh redis ... Up 6379/tcp
@ -368,6 +380,9 @@ Compare the version with the list below to check whether a breaking change was i
The following is a list of breaking changes of the `netbox-docker` project:
* 0.7.0: The value of the `MAX_PAGE_SIZE` environment variable was changed to `1000`, which is the default of Netbox.
* 0.6.0: The naming of the default startup_scripts were changed.
If you overwrite them, you may need to adjust these scripts.
* 0.5.0: Alpine was updated to 3.8, `*.env` moved to `/env` folder
* 0.4.0: In order to use Netbox webhooks you need to add Redis and a netbox-worker to your docker-compose.yml.
* 0.3.0: Field `filterable: <boolean` was replaced with field `filter_logic: loose/exact/disabled`. It will default to `CF_FILTER_LOOSE=loose` when not defined.

View File

@ -1 +1 @@
0.5.1
0.7.0

View File

@ -15,10 +15,10 @@ services:
- ./startup_scripts:/opt/netbox/startup_scripts:ro
- ./initializers:/opt/netbox/initializers:ro
- ./configuration:/etc/netbox/config:ro
- ./reports:/etc/netbox/reports:ro
- netbox-nginx-config:/etc/netbox-nginx/
- netbox-static-files:/opt/netbox/netbox/static
- netbox-media-files:/opt/netbox/netbox/media
- netbox-report-files:/etc/netbox/reports:ro
netbox-worker:
<<: *netbox
depends_on:

2
env/netbox.env vendored
View File

@ -12,7 +12,7 @@ MEDIA_ROOT=/opt/netbox/netbox/media
NAPALM_USERNAME=
NAPALM_PASSWORD=
NAPALM_TIMEOUT=10
MAX_PAGE_SIZE=0
MAX_PAGE_SIZE=1000
REDIS_HOST=redis
REDIS_PASSWORD=H733Kdjndks81
SECRET_KEY=r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj

View File

@ -7,6 +7,8 @@
# on_objects:
# - dcim.models.Device
# - dcim.models.Rack
# - dcim.models.Site
# - dcim.models.DeviceType
# - ipam.models.IPAddress
# - ipam.models.Prefix
# - tenancy.models.Tenant

View File

@ -0,0 +1,15 @@
# - name: switch
# slug: switch
# color: Grey
# - name: router
# slug: router
# color: Cyan
# - name: load-balancer
# slug: load-balancer
# color: Red
# - name: server
# slug: server
# color: Blue
# - name: patchpanel
# slug: patchpanel
# color: Black

View File

@ -0,0 +1,23 @@
# - model: Model 1
# manufacturer: Manufacturer 1
# slug: model-1
# u_height: 2
# custom_fields:
# text_field: Description
# - model: Model 2
# manufacturer: Manufacturer 1
# slug: model-2
# custom_fields:
# text_field: Description
# - model: Model 3
# manufacturer: Manufacturer 1
# slug: model-3
# is_full_depth: false
# u_height: 0
# custom_fields:
# text_field: Description
# - model: Other
# manufacturer: NoName
# slug: other
# custom_fields:
# text_field: Description

27
initializers/devices.yml Normal file
View File

@ -0,0 +1,27 @@
# - name: server01
# device_role: server
# device_type: Other
# site: AMS 1
# rack: rack-01
# face: Front
# position: 1
# custom_fields:
# text_field: Description
# - name: server02
# device_role: server
# device_type: Other
# site: AMS 2
# rack: rack-02
# face: Front
# position: 2
# custom_fields:
# text_field: Description
# - name: server03
# device_role: server
# device_type: Other
# site: SING 1
# rack: rack-03
# face: Front
# position: 3
# custom_fields:
# text_field: Description

View File

@ -0,0 +1,6 @@
# - name: Manufacturer 1
# slug: manufacturer-1
# - name: Manufacturer 2
# slug: manufacturer-2
# - name: NoName
# slug: noname

View File

@ -0,0 +1,19 @@
# # Allowed rpc clients are: juniper-junos, cisco-ios, opengear
# - name: Platform 1
# slug: platform-1
# manufacturer: Manufacturer 1
# napalm_driver: driver1
# napalm_args: "{'arg1': 'value1', 'arg2': 'value2'}"
# rpc_client: juniper-junos
# - name: Platform 2
# slug: platform-2
# manufacturer: Manufacturer 2
# napalm_driver: driver2
# napalm_args: "{'arg1': 'value1', 'arg2': 'value2'}"
# rpc_client: opengear
# - name: Platform 3
# slug: platform-3
# manufacturer: NoName
# napalm_driver: driver3
# napalm_args: "{'arg1': 'value1', 'arg2': 'value2'}"
# rpc_client: juniper-junos

View File

@ -0,0 +1,12 @@
# - name: Role 1
# slug: role-1
# color: Pink
# - name: Role 2
# slug: role-2
# color: Cyan
# - name: Role 3
# slug: role-3
# color: Grey
# - name: Role 4
# slug: role-4
# color: Teal

24
initializers/racks.yml Normal file
View File

@ -0,0 +1,24 @@
# - site: AMS 1
# name: rack-01
# role: Role 1
# type: 4-post cabinet
# width: 19 inches
# u_height: 47
# custom_fields:
# text_field: Description
# - site: AMS 2
# name: rack-02
# role: Role 2
# type: 4-post cabinet
# width: 19 inches
# u_height: 47
# custom_fields:
# text_field: Description
# - site: SING 1
# name: rack-03
# role: Role 3
# type: 4-post cabinet
# width: 19 inches
# u_height: 47
# custom_fields:
# text_field: Description

10
initializers/regions.yml Normal file
View File

@ -0,0 +1,10 @@
# - name: Singapore
# slug: singapore
# - name: Amsterdam
# slug: amsterdam
# - name: Downtown
# slug: downtown
# parent: Amsterdam
# - name: Suburbs
# slug: suburbs
# parent: Amsterdam

32
initializers/sites.yml Normal file
View File

@ -0,0 +1,32 @@
# - name: AMS 1
# slug: ams1
# region: Downtown
# status: 1
# facility: Amsterdam 1
# asn: 12345
# custom_fields:
# text_field: Description
# - name: AMS 2
# slug: ams2
# region: Downtown
# status: 1
# facility: Amsterdam 2
# asn: 54321
# custom_fields:
# text_field: Description
# - name: AMS 3
# slug: ams3
# region: Suburbs
# status: 1
# facility: Amsterdam 3
# asn: 67890
# custom_fields:
# text_field: Description
# - name: SING 1
# slug: sing1
# region: Singapore
# status: 1
# facility: Singapore 1
# asn: 09876
# custom_fields:
# text_field: Description

View File

@ -0,0 +1,46 @@
from dcim.constants import CONNECTION_STATUS_PLANNED, DEVICE_STATUS_ACTIVE
from dcim.models import ConsolePort, Device, PowerPort
from extras.reports import Report
class DeviceConnectionsReport(Report):
description = "Validate the minimum physical connections for each device"
def test_console_connection(self):
# Check that every console port for every active device has a connection defined.
for console_port in ConsolePort.objects.select_related('device').filter(device__status=DEVICE_STATUS_ACTIVE):
if console_port.connected_endpoint is None:
self.log_failure(
console_port.device,
"No console connection defined for {}".format(console_port.name)
)
elif console_port.connection_status == CONNECTION_STATUS_PLANNED:
self.log_warning(
console_port.device,
"Console connection for {} marked as planned".format(console_port.name)
)
else:
self.log_success(console_port.device)
def test_power_connections(self):
# Check that every active device has at least two connected power supplies.
for device in Device.objects.filter(status=DEVICE_STATUS_ACTIVE):
connected_ports = 0
for power_port in PowerPort.objects.filter(device=device):
if power_port.connected_endpoint is not None:
connected_ports += 1
if power_port.connection_status == CONNECTION_STATUS_PLANNED:
self.log_warning(
device,
"Power connection for {} marked as planned".format(power_port.name)
)
if connected_ports < 2:
self.log_failure(
device,
"{} connected power supplies found (2 needed)".format(connected_ports)
)
else:
self.log_success(device)

View File

@ -2,8 +2,14 @@ from django.contrib.auth.models import Permission, Group, User
from users.models import Token
from ruamel.yaml import YAML
from pathlib import Path
import sys
with open('/opt/netbox/initializers/users.yml', 'r') as stream:
file = Path('/opt/netbox/initializers/users.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml=YAML(typ='safe')
users = yaml.load(stream)

View File

@ -1,7 +1,13 @@
from django.contrib.auth.models import Permission, Group, User
from ruamel.yaml import YAML
from pathlib import Path
import sys
with open('/opt/netbox/initializers/groups.yml', 'r') as stream:
file = Path('/opt/netbox/initializers/groups.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml=YAML(typ='safe')
groups = yaml.load(stream)

View File

@ -2,6 +2,8 @@ from extras.constants import CF_TYPE_TEXT, CF_TYPE_INTEGER, CF_TYPE_BOOLEAN, CF_
from extras.models import CustomField, CustomFieldChoice
from ruamel.yaml import YAML
from pathlib import Path
import sys
text_to_fields = {
'boolean': CF_TYPE_BOOLEAN,
@ -21,7 +23,11 @@ def get_class_for_class_path(class_path):
clazz = getattr(module, class_name)
return ContentType.objects.get_for_model(clazz)
with open('/opt/netbox/initializers/custom_fields.yml', 'r') as stream:
file = Path('/opt/netbox/initializers/custom_fields.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
customfields = yaml.load(stream)

View File

@ -0,0 +1,31 @@
from dcim.models import Region
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/regions.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml=YAML(typ='safe')
regions = yaml.load(stream)
optional_assocs = {
'parent': (Region, 'name')
}
if regions is not None:
for params in regions:
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
region, created = Region.objects.get_or_create(**params)
if created:
print("🌐 Created region", region.name)

View File

@ -0,0 +1,46 @@
from dcim.models import Region, Site
from extras.models import CustomField, CustomFieldValue
from tenancy.models import Tenant
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/sites.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
sites = yaml.load(stream)
optional_assocs = {
'region': (Region, 'name'),
'tenant': (Tenant, 'name')
}
if sites is not None:
for params in sites:
custom_fields = params.pop('custom_fields', None)
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
site, created = Site.objects.get_or_create(**params)
if created:
if custom_fields is not None:
for cf_name, cf_value in custom_fields.items():
custom_field = CustomField.objects.get(name=cf_name)
custom_field_value = CustomFieldValue.objects.create(
field=custom_field,
obj=site,
value=cf_value
)
site.custom_field_values.add(custom_field_value)
print("📍 Created site", site.name)

View File

@ -0,0 +1,19 @@
from dcim.models import Manufacturer
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/manufacturers.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
manufacturers = yaml.load(stream)
if manufacturers is not None:
for params in manufacturers:
manufacturer, created = Manufacturer.objects.get_or_create(**params)
if created:
print("🏭 Created Manufacturer", manufacturer.name)

View File

@ -0,0 +1,56 @@
from dcim.models import DeviceType, Manufacturer, Region
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/device_types.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
device_types = yaml.load(stream)
required_assocs = {
'manufacturer': (Manufacturer, 'name')
}
optional_assocs = {
'region': (Region, 'name'),
'tenant': (Tenant, 'name')
}
if device_types is not None:
for params in device_types:
custom_fields = params.pop('custom_fields', None)
for assoc, details in required_assocs.items():
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
device_type, created = DeviceType.objects.get_or_create(**params)
if created:
if custom_fields is not None:
for cf_name, cf_value in custom_fields.items():
custom_field = CustomField.objects.get(name=cf_name)
custom_field_value = CustomFieldValue.objects.create(
field=custom_field,
obj=device_type,
value=cf_value
)
device_type.custom_field_values.add(custom_field_value)
print("🔡 Created device type", device_type.manufacturer, device_type.model)

View File

@ -0,0 +1,28 @@
from dcim.models import RackRole
from ruamel.yaml import YAML
from utilities.forms import COLOR_CHOICES
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/rack_roles.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml=YAML(typ='safe')
rack_roles = yaml.load(stream)
if rack_roles is not None:
for params in rack_roles:
if 'color' in params:
color = params.pop('color')
for color_tpl in COLOR_CHOICES:
if color in color_tpl:
params['color'] = color_tpl[0]
rack_role, created = RackRole.objects.get_or_create(**params)
if created:
print("🎨 Created rack role", rack_role.name)

View File

@ -0,0 +1,66 @@
from dcim.models import Site, RackRole, Rack, RackGroup
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
from dcim.constants import RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/racks.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
racks = yaml.load(stream)
required_assocs = {
'site': (Site, 'name')
}
optional_assocs = {
'role': (RackRole, 'name'),
'tenant': (Tenant, 'name'),
'group': (RackGroup, 'name')
}
if racks is not None:
for params in racks:
custom_fields = params.pop('custom_fields', None)
for assoc, details in required_assocs.items():
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
for rack_type in RACK_TYPE_CHOICES:
if params['type'] in rack_type:
params['type'] = rack_type[0]
for rack_width in RACK_WIDTH_CHOICES:
if params['width'] in rack_width:
params['width'] = rack_width[0]
rack, created = Rack.objects.get_or_create(**params)
if created:
if custom_fields is not None:
for cf_name, cf_value in custom_fields.items():
custom_field = CustomField.objects.get(name=cf_name)
custom_field_value = CustomFieldValue.objects.create(
field=custom_field,
obj=rack,
value=cf_value
)
rack.custom_field_values.add(custom_field_value)
print("🔳 Created rack", rack.site, rack.name)

View File

@ -0,0 +1,29 @@
from dcim.models import DeviceRole
from ruamel.yaml import YAML
from utilities.forms import COLOR_CHOICES
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/device_roles.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml=YAML(typ='safe')
device_roles = yaml.load(stream)
if device_roles is not None:
for params in device_roles:
if 'color' in params:
color = params.pop('color')
for color_tpl in COLOR_CHOICES:
if color in color_tpl:
params['color'] = color_tpl[0]
device_role, created = DeviceRole.objects.get_or_create(**params)
if created:
print("🎨 Created device role", device_role.name)

View File

@ -0,0 +1,32 @@
from dcim.models import Manufacturer, Platform
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/platforms.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
platforms = yaml.load(stream)
optional_assocs = {
'manufacturer': (Manufacturer, 'name'),
}
if platforms is not None:
for params in platforms:
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
platform, created = Platform.objects.get_or_create(**params)
if created:
print("💾 Created platform", platform.name)

View File

@ -0,0 +1,71 @@
from dcim.models import Site, Rack, DeviceRole, DeviceType, Device, Platform
from dcim.constants import RACK_FACE_CHOICES
from ipam.models import IPAddress
from virtualization.models import Cluster
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/devices.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
devices = yaml.load(stream)
required_assocs = {
'device_role': (DeviceRole, 'name'),
'device_type': (DeviceType, 'model'),
'site': (Site, 'name')
}
optional_assocs = {
'tenant': (Tenant, 'name'),
'platform': (Platform, 'name'),
'rack': (Rack, 'name'),
'cluster': (Cluster, 'name'),
'primary_ip4': (IPAddress, 'address'),
'primary_ip6': (IPAddress, 'address')
}
if devices is not None:
for params in devices:
custom_fields = params.pop('custom_fields', None)
for assoc, details in required_assocs.items():
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
if 'face' in params:
for rack_face in RACK_FACE_CHOICES:
if params['face'] in rack_face:
params['face'] = rack_face[0]
device, created = Device.objects.get_or_create(**params)
if created:
if custom_fields is not None:
for cf_name, cf_value in custom_fields.items():
custom_field = CustomField.objects.get(name=cf_name)
custom_field_value = CustomFieldValue.objects.create(
field=custom_field,
obj=device,
value=cf_value
)
device.custom_field_values.add(custom_field_value)
print("🖥️ Created device", device.name)