Merge pull request #839 from netbox-community/develop

Version 2.2.0
This commit is contained in:
Tobias Genannt 2022-09-01 12:12:06 +02:00 committed by GitHub
commit 3f1e45f636
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
106 changed files with 19 additions and 2989 deletions

View File

@ -41,6 +41,7 @@ RUN export DEBIAN_FRONTEND=noninteractive \
--yes -qq --no-install-recommends \
&& apt-get install \
--yes -qq --no-install-recommends \
bzip2 \
ca-certificates \
curl \
libldap-common \
@ -70,8 +71,6 @@ COPY docker/ldap_config.docker.py /opt/netbox/netbox/netbox/ldap_config.py
COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh
COPY docker/housekeeping.sh /opt/netbox/housekeeping.sh
COPY docker/launch-netbox.sh /opt/netbox/launch-netbox.sh
COPY startup_scripts/ /opt/netbox/startup_scripts/
COPY initializers/ /opt/netbox/initializers/
COPY configuration/ /etc/netbox/config/
COPY docker/nginx-unit.json /etc/unit/

View File

@ -7,7 +7,7 @@
![Docker Pulls](https://img.shields.io/docker/pulls/netboxcommunity/netbox)
[![GitHub license](https://img.shields.io/github/license/netbox-community/netbox-docker)][netbox-docker-license]
[The GitHub repository](netbox-docker-github) houses the components needed to build NetBox as a container.
[The GitHub repository][netbox-docker-github] houses the components needed to build NetBox as a container.
Images are built regularly using the code in that repository and are pushed to [Docker Hub][netbox-dockerhub], [Quay.io][netbox-quayio] and [GitHub Container Registry][netbox-ghcr].
Do you have any questions?
@ -16,7 +16,6 @@ please join [our Slack][netbox-docker-slack] and ask for help in the [`#netbox-d
[github-stargazers]: https://github.com/netbox-community/netbox-docker/stargazers
[github-release]: https://github.com/netbox-community/netbox-docker/releases
[netbox-docker-microbadger]: https://microbadger.com/images/netboxcommunity/netbox
[netbox-dockerhub]: https://hub.docker.com/r/netboxcommunity/netbox/
[netbox-quayio]: https://quay.io/repository/netboxcommunity/netbox
[netbox-ghcr]: https://github.com/netbox-community/netbox-docker/pkgs/container/netbox
@ -56,7 +55,6 @@ The default credentials are:
* API Token: **0123456789abcdef0123456789abcdef01234567**
[wiki-getting-started]: https://github.com/netbox-community/netbox-docker/wiki/Getting-Started
[docker-reception]: https://github.com/nxt-engineering/reception
## Container Image Tags
@ -92,17 +90,9 @@ For each of the above tag, there is an extra tag:
This is the same version as `snapshot-a.b.c`.
It always points to the latest version of _NetBox Docker_.
Then there is currently one extra tags for each of the above tags:
* `-ldap`:
These container images contain additional dependencies and configuration files for connecting NetBox to an LDAP directory.
[Learn more about that in our wiki][netbox-docker-ldap].
[netbox-releases]: https://github.com/netbox-community/netbox/releases
[netbox-master]: https://github.com/netbox-community/netbox/tree/master
[netbox-develop]: https://github.com/netbox-community/netbox/tree/develop
[netbox-branches]: https://github.com/netbox-community/netbox/branches
[netbox-docker-ldap]: https://github.com/netbox-community/netbox-docker/wiki/LDAP
## Documentation
@ -127,10 +117,11 @@ you may find [the `#netbox` channel][netbox-slack-channel] on the same Slack ins
## Dependencies
This project relies only on *Docker* and *docker-compose* meeting these requirements:
This project relies only on _Docker_ and _docker-compose_ meeting these requirements:
* The *Docker version* must be at least `19.03`.
* The *docker-compose version* must be at least `1.28.0`.
* The _Docker version_ must be at least `20.10.10`.
* The _containerd version_ must be at least `1.5.6`.
* The _docker-compose version_ must be at least `1.28.0`.
To check the version installed on your system run `docker --version` and `docker-compose --version`.

View File

@ -1 +1 @@
2.1.0
2.2.0

View File

@ -11,9 +11,8 @@ services:
SKIP_STARTUP_SCRIPTS: ${SKIP_STARTUP_SCRIPTS-false}
user: 'unit:root'
volumes:
- ./startup_scripts:/opt/netbox/startup_scripts:z,ro
- ./${INITIALIZERS_DIR-initializers}:/opt/netbox/initializers:z,ro
- ./configuration:/etc/netbox/config:z,ro
- ./test-configuration/logging.py:/etc/netbox/config/logging.py:z,ro
- ./reports:/etc/netbox/reports:z,ro
- ./scripts:/etc/netbox/scripts:z,ro
- netbox-media-files:/opt/netbox/netbox/media:z

View File

@ -1,7 +1,7 @@
version: '3.4'
services:
netbox: &netbox
image: netboxcommunity/netbox:${VERSION-v3.2-2.1.0}
image: netboxcommunity/netbox:${VERSION-v3.3-2.2.0}
depends_on:
- postgres
- redis
@ -10,8 +10,6 @@ services:
env_file: env/netbox.env
user: 'unit:root'
volumes:
- ./startup_scripts:/opt/netbox/startup_scripts:z,ro
- ./initializers:/opt/netbox/initializers:z,ro
- ./configuration:/etc/netbox/config:z,ro
- ./reports:/etc/netbox/reports:z,ro
- ./scripts:/etc/netbox/scripts:z,ro

View File

@ -80,11 +80,13 @@ END
echo "💡 Superuser Username: ${SUPERUSER_NAME}, E-Mail: ${SUPERUSER_EMAIL}"
fi
# Run the startup scripts (and initializers)
# Print warning if startup scripts (and initializers) would've been run # Remove for next release
if [ "$SKIP_STARTUP_SCRIPTS" == "true" ]; then
echo "↩️ Skipping startup scripts"
# Nothing to do
echo "" # Empty block not allowed
else
echo "import runpy; runpy.run_path('../startup_scripts')" | ./manage.py shell --interface python
echo "⚠️⚠️⚠️ WARNING: The initializers have been moved to a plugin. See release notes."
echo "⚠️⚠️⚠️ Set environment variable 'SKIP_STARTUP_SCRIPTS' to 'true' to remove this warning."
fi
echo "✅ Initialisation is done."

1
env/netbox.env vendored
View File

@ -34,7 +34,6 @@ REDIS_PASSWORD=H733Kdjndks81
REDIS_SSL=false
RELEASE_CHECK_URL=https://api.github.com/repos/netbox-community/netbox/releases
SECRET_KEY=r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj
SKIP_STARTUP_SCRIPTS=false
SKIP_SUPERUSER=false
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
SUPERUSER_EMAIL=admin@example.com

View File

@ -1,7 +0,0 @@
# - prefix: 10.0.0.0/16
# rir: RFC1918
# tenant: tenant1
# - prefix: fd00:ccdd::/32
# rir: RFC4193 ULA
# - prefix: 2001:db8::/32
# rir: RFC3849

View File

@ -1,7 +0,0 @@
# - asn: 1
# rir: RFC1918
# tenant: tenant1
# - asn: 2
# rir: RFC4193 ULA
# - asn: 3
# rir: RFC3849

View File

@ -1,71 +0,0 @@
# # Required parameters for termination X ('a' or 'b'):
# #
# # ```
# # termination_x_name -> name of interface
# # termination_x_device -> name of the device interface belongs to
# # termination_x_class -> required if different than 'Interface' which is the default
# # ```
# #
# # Supported termination classes: Interface, ConsolePort, ConsoleServerPort, FrontPort, RearPort, PowerPort, PowerOutlet
# #
# #
# # If a termination is a circuit then the required parameter is termination_x_circuit.
# # Required parameters for a circuit termination:
# #
# # ```
# # termination_x_circuit:
# # term_side -> termination side of a circuit. Must be A or B
# # cid -> circuit ID value
# # site OR provider_network -> name of Site or ProviderNetwork respectively. If both provided, Site takes precedence
# # ```
# #
# # If a termination is a power feed then the required parameter is termination_x_feed.
# #
# # ```
# # termination_x_feed:
# # name -> name of the PowerFeed object
# # power_panel:
# # name -> name of the PowerPanel the PowerFeed is attached to
# # site -> name of the Site in which the PowerPanel is present
# # ```
# #
# # Any other Cable parameters supported by Netbox are supported as the top level keys, e.g. 'type', 'status', etc.
# #
# # - termination_a_name: console
# # termination_a_device: spine
# # termination_a_class: ConsolePort
# # termination_b_name: tty9
# # termination_b_device: console-server
# # termination_b_class: ConsoleServerPort
# # type: cat6
# #
# - termination_a_name: to-server02
# termination_a_device: server01
# termination_b_name: to-server01
# termination_b_device: server02
# status: planned
# type: mmf
# - termination_a_name: eth0
# termination_a_device: server02
# termination_b_circuit:
# term_side: A
# cid: Circuit_ID-1
# site: AMS 1
# type: cat6
# - termination_a_name: psu0
# termination_a_device: server04
# termination_a_class: PowerPort
# termination_b_feed:
# name: power feed 1
# power_panel:
# name: power panel AMS 1
# site: AMS 1
# - termination_a_name: outlet1
# termination_a_device: server04
# termination_a_class: PowerOutlet
# termination_b_name: psu1
# termination_b_device: server04
# termination_b_class: PowerPort

View File

@ -1,6 +0,0 @@
# - name: VPLS
# slug: vpls
# - name: MPLS
# slug: mpls
# - name: Internet
# slug: internet

View File

@ -1,7 +0,0 @@
# - cid: Circuit_ID-1
# provider: Provider1
# type: Internet
# tenant: tenant1
# - cid: Circuit_ID-2
# provider: Provider2
# type: MPLS

View File

@ -1,4 +0,0 @@
# - name: Group 1
# slug: group-1
# - name: Group 2
# slug: group-2

View File

@ -1,2 +0,0 @@
# - name: Hyper-V
# slug: hyper-v

View File

@ -1,7 +0,0 @@
# - name: cluster1
# type: Hyper-V
# group: Group 1
# tenant: tenant1
# - name: cluster2
# type: Hyper-V
# site: SING 1

View File

@ -1,7 +0,0 @@
# - name: Network-Team
# slug: network-team
# description: This is a new contact group for the Network-Team
# - name: New Contact Group
# slug: new-contact-group
# description: This is a new contact group sub under of Network-Team
# parent: Network-Team

View File

@ -1,3 +0,0 @@
# - name: New Contact Role
# slug: new-contact-role
# description: This is a new contact role description

View File

@ -1,20 +0,0 @@
# - name: Lee Widget
# title: CEO of Widget Corp
# phone: 221-555-1212
# email: widgetCEO@widgetcorp.com
# address: 1200 Nowhere Blvd, Scranton NJ, 555111
# comments: This is a very important contact
# - name: Ali Gator
# group: Network-Team
# title: Consultant for Widget Corp
# phone: 221-555-1213
# email: Consultant@widgetcorp.com
# address: 1200 Nowhere Blvd, Scranton NJ, 555111
# comments: This is a very important contact
# - name: Karlchen Maier
# group: New Contact Group
# title: COO of Widget Corp
# phone: 221-555-1214
# email: Karlchen@widgetcorp.com
# address: 1200 Nowhere Blvd, Scranton NJ, 555111
# comments: This is a very important contact

View File

@ -1,93 +0,0 @@
## Possible Choices:
## type:
## - text
## - integer
## - boolean
## - date
## - url
## - select
## filter_logic:
## - disabled
## - loose
## - exact
##
## Examples:
# text_field:
# type: text
# label: Custom Text
# description: Enter text in a text field.
# required: false
# weight: 0
# on_objects:
# - dcim.models.Device
# - dcim.models.Rack
# - dcim.models.Site
# - dcim.models.DeviceType
# - ipam.models.IPAddress
# - ipam.models.Prefix
# - tenancy.models.Tenant
# - virtualization.models.VirtualMachine
# integer_field:
# type: integer
# label: Custom Number
# description: Enter numbers into an integer field.
# required: true
# filter_logic: loose
# weight: 10
# on_objects:
# - tenancy.models.Tenant
# select_field:
# type: select
# label: Choose between items
# required: false
# filter_logic: exact
# weight: 30
# default: First Item
# on_objects:
# - dcim.models.Device
# choices:
# - First Item
# - Second Item
# - Third Item
# - Fifth Item
# - Fourth Item
# select_field_legacy_format:
# type: select
# label: Choose between items
# required: false
# filter_logic: loose
# weight: 30
# on_objects:
# - dcim.models.Device
# choices:
# - value: A # this is the deprecated format.
# - value: B # we only use it for the tests.
# - value: C # please see above for the new format.
# - value: "D like deprecated"
# weight: 999
# - value: E
# boolean_field:
# type: boolean
# label: Yes Or No?
# required: true
# filter_logic: loose
# default: "false" # important: put "false" in quotes!
# weight: 90
# on_objects:
# - dcim.models.Device
# url_field:
# type: url
# label: Hyperlink
# description: Link to something nice.
# required: true
# filter_logic: disabled
# on_objects:
# - tenancy.models.Tenant
# date_field:
# type: date
# label: Important Date
# required: false
# filter_logic: disabled
# on_objects:
# - dcim.models.Device

View File

@ -1,21 +0,0 @@
## Possible Choices:
## new_window:
## - True
## - False
## content_type:
## - device
## - site
## - any-other-content-type
##
## Examples:
# - name: link_to_repo
# link_text: 'Link to Netbox Docker'
# link_url: 'https://github.com/netbox-community/netbox-docker'
# new_window: False
# content_type: device
# - name: link_to_localhost
# link_text: 'Link to localhost'
# link_url: 'http://localhost'
# new_window: True
# content_type: device

View File

@ -1,35 +0,0 @@
## Possible Choices:
## type:
## - virtual
## - lag
## - 1000base-t
## - ... and many more. See for yourself:
## https://github.com/netbox-community/netbox/blob/295d4f0394b431351c0cb2c3ecc791df68c6c2fb/netbox/dcim/choices.py#L510
##
## Examples:
# - device: server01
# name: ath0
# type: 1000base-t
# lag: ae0
# bridge: br0
# - device: server01
# name: ath1
# type: 1000base-t
# parent: ath0
# - device: server01
# enabled: true
# type: 1000base-x-sfp
# name: to-server02
# - device: server02
# enabled: true
# type: 1000base-x-sfp
# name: to-server01
# - device: server02
# enabled: true
# type: 1000base-t
# name: eth0
# - device: server02
# enabled: true
# type: virtual
# name: loopback

View File

@ -1,15 +0,0 @@
# - 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

@ -1,57 +0,0 @@
# - model: Model 1
# manufacturer: Manufacturer 1
# slug: model-1
# u_height: 2
# custom_field_data:
# text_field: Description
# - model: Model 2
# manufacturer: Manufacturer 1
# slug: model-2
# custom_field_data:
# text_field: Description
# - model: Model 3
# manufacturer: Manufacturer 1
# slug: model-3
# is_full_depth: false
# u_height: 0
# custom_field_data:
# text_field: Description
# - model: Other
# manufacturer: No Name
# slug: other
# custom_field_data:
# text_field: Description
# interfaces:
# - name: eth0
# type: 1000base-t
# mgmt_only: True
# - name: eth1
# type: 1000base-t
# console_server_ports:
# - name_template: ttyS[1-48]
# type: rj-45
# power_ports:
# - name_template: psu[0,1]
# type: iec-60320-c14
# maximum_draw: 35
# allocated_draw: 35
# front_ports:
# - name_template: front[1,2]
# type: 8p8c
# rear_port_template: rear[0,1]
# rear_port_position_template: "[1,2]"
# rear_ports:
# - name_template: rear[0,1]
# type: 8p8c
# positions_template: "[3,2]"
# device_bays:
# - name: bay0 # both non-template and template field specified; non-template field takes precedence
# name_template: bay[0-9]
# label: test0
# label_template: test[0-5,9,6-8]
# description: Test description
# power_outlets:
# - name_template: outlet[0,1]
# type: iec-60320-c5
# power_port: psu0
# feed_leg: B

View File

@ -1,53 +0,0 @@
## Possible Choices:
## face:
## - front
## - rear
## status:
## - offline
## - active
## - planned
## - staged
## - failed
## - inventory
## - decommissioning
##
## Examples:
# - name: server01
# device_role: server
# device_type: Other
# site: AMS 1
# rack: rack-01
# face: front
# position: 1
# custom_field_data:
# text_field: Description
# - name: server02
# device_role: server
# device_type: Other
# site: AMS 2
# rack: rack-02
# face: front
# position: 2
# primary_ip4: 10.1.1.2/24
# primary_ip6: 2001:db8:a000:1::2/64
# custom_field_data:
# text_field: Description
# - name: server03
# device_role: server
# device_type: Other
# site: SING 1
# rack: rack-03
# face: front
# position: 3
# custom_field_data:
# text_field: Description
# - name: server04
# device_role: server
# device_type: Other
# site: SING 1
# location: cage 101
# face: front
# position: 3
# custom_field_data:
# text_field: Description

View File

@ -1,9 +0,0 @@
# applications:
# users:
# - technical_user
# readers:
# users:
# - reader
# writers:
# users:
# - writer

View File

@ -1,44 +0,0 @@
## Possible Choices:
## status:
## - active
## - reserved
## - deprecated
## - dhcp
## role:
## - loopback
## - secondary
## - anycast
## - vip
## - vrrp
## - hsrp
## - glbp
## - carp
##
## Examples:
# - address: 10.1.1.1/24
# device: server01
# interface: to-server02
# status: active
# vrf: vrf1
# - address: 2001:db8:a000:1::1/64
# device: server01
# interface: to-server02
# status: active
# vrf: vrf1
# - address: 10.1.1.2/24
# device: server02
# interface: to-server01
# status: active
# - address: 2001:db8:a000:1::2/64
# device: server02
# interface: to-server01
# status: active
# - address: 10.1.1.10/24
# description: reserved IP
# status: reserved
# tenant: tenant1
# - address: 2001:db8:a000:1::10/64
# description: reserved IP
# status: reserved
# tenant: tenant1

View File

@ -1,3 +0,0 @@
# - name: cage 101
# slug: cage-101
# site: SING 1

View File

@ -1,6 +0,0 @@
# - name: Manufacturer 1
# slug: manufacturer-1
# - name: Manufacturer 2
# slug: manufacturer-2
# - name: No Name
# slug: no-name

View File

@ -1,48 +0,0 @@
# all.ro:
# actions:
# - view
# description: 'Read Only for All Objects'
# enabled: true
# groups:
# - applications
# - readers
# object_types: all
# users:
# - jdoe
# all.rw:
# actions:
# - add
# - change
# - delete
# - view
# description: 'Read/Write for All Objects'
# enabled: true
# groups:
# - writers
# object_types: all
# network_team.rw:
# actions:
# - add
# - change
# - delete
# - view
# description: "Network Team Permissions"
# enabled: true
# object_types:
# circuits:
# - circuit
# - circuittermination
# - circuittype
# - provider
# dcim: all
# ipam:
# - aggregate
# - ipaddress
# - prefix
# - rir
# - role
# - routetarget
# - service
# - vlan
# - vlangroup
# - vrf

View File

@ -1,15 +0,0 @@
# - name: Platform 1
# slug: platform-1
# manufacturer: Manufacturer 1
# napalm_driver: driver1
# napalm_args: "{'arg1': 'value1', 'arg2': 'value2'}"
# - name: Platform 2
# slug: platform-2
# manufacturer: Manufacturer 2
# napalm_driver: driver2
# napalm_args: "{'arg1': 'value1', 'arg2': 'value2'}"
# - name: Platform 3
# slug: platform-3
# manufacturer: No Name
# napalm_driver: driver3
# napalm_args: "{'arg1': 'value1', 'arg2': 'value2'}"

View File

@ -1,14 +0,0 @@
# - name: power feed 1
# power_panel: power panel AMS 1
# voltage: 208
# amperage: 50
# max_utilization: 80
# phase: Single phase
# rack: rack-01
# - name: power feed 2
# power_panel: power panel SING 1
# voltage: 208
# amperage: 50
# max_utilization: 80
# phase: Three-phase
# rack: rack-03

View File

@ -1,5 +0,0 @@
# - name: power panel AMS 1
# site: AMS 1
# - name: power panel SING 1
# site: SING 1
# location: cage 101

View File

@ -1,2 +0,0 @@
# - name: Main Management
# slug: main-management

View File

@ -1,29 +0,0 @@
## Possible Choices:
## status:
## - container
## - active
## - reserved
## - deprecated
##
## Examples:
# - description: prefix1
# prefix: 10.1.1.0/24
# site: AMS 1
# status: active
# tenant: tenant1
# vlan: vlan1
# - description: prefix2
# prefix: 10.1.2.0/24
# site: AMS 2
# status: active
# tenant: tenant2
# vlan: vlan2
# is_pool: true
# vrf: vrf2
# - description: ipv6 prefix1
# prefix: 2001:db8:a000:1::/64
# site: AMS 2
# status: active
# tenant: tenant2
# vlan: vlan2

View File

@ -1,6 +0,0 @@
# - name: Provider1
# slug: provider1
# asn: 121
# - name: Provider2
# slug: provider2
# asn: 122

View File

@ -1,12 +0,0 @@
# - 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

View File

@ -1,41 +0,0 @@
## Possible Choices:
## width:
## - 19
## - 23
## types:
## - 2-post-frame
## - 4-post-frame
## - 4-post-cabinet
## - wall-frame
## - wall-cabinet
## outer_unit:
## - mm
## - in
##
## Examples:
# - site: AMS 1
# name: rack-01
# role: Role 1
# type: 4-post-cabinet
# width: 19
# u_height: 47
# custom_field_data:
# text_field: Description
# - site: AMS 2
# name: rack-02
# role: Role 2
# type: 4-post-cabinet
# width: 19
# u_height: 47
# custom_field_data:
# text_field: Description
# - site: SING 1
# name: rack-03
# location: cage 101
# role: Role 3
# type: 4-post-cabinet
# width: 19
# u_height: 47
# custom_field_data:
# text_field: Description

View File

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

View File

@ -1,9 +0,0 @@
# - is_private: true
# name: RFC1918
# slug: rfc1918
# - is_private: true
# name: RFC4193 ULA
# slug: rfc4193-ula
# - is_private: true
# name: RFC3849
# slug: rfc3849

View File

@ -1,3 +0,0 @@
# - name: 65000:1001
# tenant: tenant1
# - name: 65000:1002

View File

@ -1,15 +0,0 @@
# - name: DNS
# protocol: TCP
# ports:
# - 53
# virtual_machine: virtual machine 1
# - name: DNS
# protocol: UDP
# ports:
# - 53
# virtual_machine: virtual machine 1
# - name: MISC
# protocol: UDP
# ports:
# - 4000
# device: server01

View File

@ -1,30 +0,0 @@
# - name: AMS 1
# slug: ams1
# region: Downtown
# status: active
# facility: Amsterdam 1
# custom_field_data:
# text_field: Description for AMS1
# - name: AMS 2
# slug: ams2
# region: Downtown
# status: active
# facility: Amsterdam 2
# custom_field_data:
# text_field: Description for AMS2
# - name: AMS 3
# slug: ams3
# region: Suburbs
# status: active
# facility: Amsterdam 3
# tenant: tenant1
# custom_field_data:
# text_field: Description for AMS3
# - name: SING 1
# slug: sing1
# region: Singapore
# status: active
# facility: Singapore 1
# tenant: tenant2
# custom_field_data:
# text_field: Description for SING1

View File

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

View File

@ -1,4 +0,0 @@
# - name: Tenant Group 1
# slug: tenant-group-1
# - name: Tenant Group 2
# slug: tenant-group-2

View File

@ -1,5 +0,0 @@
# - name: tenant1
# slug: tenant1
# - name: tenant2
# slug: tenant2
# group: Tenant Group 2

View File

@ -1,15 +0,0 @@
# technical_user:
# api_token: 0123456789technicaluser789abcdef01234567 # must be looooong!
# reader:
# password: reader
# writer:
# password: writer
# api_token: "" # a token is generated automatically unless the value is explicity set to empty
# jdoe:
# first_name: John
# last_name: Doe
# api_token: 0123456789jdoe789abcdef01234567jdoe
# is_active: True
# is_superuser: False
# is_staff: False
# email: john.doe@example.com

View File

@ -1,28 +0,0 @@
## Possible Choices:
## status:
## - active
## - offline
## - staged
##
## Examples:
# - cluster: cluster1
# comments: VM1
# disk: 200
# memory: 4096
# name: virtual machine 1
# platform: Platform 2
# status: active
# tenant: tenant1
# vcpus: 8
# - cluster: cluster1
# comments: VM2
# disk: 100
# memory: 2048
# name: virtual machine 2
# platform: Platform 2
# primary_ip4: 10.1.1.10/24
# primary_ip6: 2001:db8:a000:1::10/64
# status: active
# tenant: tenant1
# vcpus: 8

View File

@ -1,12 +0,0 @@
# - description: Network Interface 1
# enabled: true
# mac_address: 00:77:77:77:77:77
# mtu: 1500
# name: Network Interface 1
# virtual_machine: virtual machine 1
# - description: Network Interface 2
# enabled: true
# mac_address: 00:55:55:55:55:55
# mtu: 1500
# name: Network Interface 2
# virtual_machine: virtual machine 1

View File

@ -1,24 +0,0 @@
# - name: VLAN group 1
# scope_type: dcim.region
# scope: Amsterdam
# slug: vlan-group-1
# - name: VLAN group 2
# scope_type: dcim.site
# scope: AMS 1
# slug: vlan-group-2
# - name: VLAN group 3
# scope_type: dcim.location
# scope: cage 101
# slug: vlan-group-3
# - name: VLAN group 4
# scope_type: dcim.rack
# scope: rack-01
# slug: vlan-group-4
# - name: VLAN group 5
# scope_type: virtualization.cluster
# scope: cluster1
# slug: vlan-group-5
# - name: VLAN group 6
# scope_type: virtualization.clustergroup
# scope: Group 1
# slug: vlan-group-6

View File

@ -1,19 +0,0 @@
## Possible Choices:
## status:
## - active
## - reserved
## - deprecated
##
## Examples:
# - name: vlan1
# site: AMS 1
# status: active
# vid: 5
# role: Main Management
# description: VLAN 5 for MGMT
# - group: VLAN group 2
# name: vlan2
# site: AMS 1
# status: active
# vid: 1300

View File

@ -1,8 +0,0 @@
# - enforce_unique: true
# name: vrf1
# tenant: tenant1
# description: main VRF
# - enforce_unique: true
# name: vrf2
# rd: "6500:6500"
# tenant: tenant2

View File

@ -1,27 +0,0 @@
## Possible Choices:
## object_types:
## - device
## - site
## - any-other-content-type
## types:
## - type_create
## - type_update
## - type_delete
## Examples:
# - name: device_creation
# payload_url: 'http://localhost:8080'
# object_types:
# - device
# - cable
# type_create: True
# - name: device_update
# payload_url: 'http://localhost:8080'
# object_types:
# - device
# type_update: True
# - name: device_delete
# payload_url: 'http://localhost:8080'
# object_types:
# - device
# type_delete: True

View File

@ -1,6 +1,5 @@
django-auth-ldap==4.1.0
django-storages[azure,boto3,dropbox,google,libcloud,sftp]==1.12.3
django-storages[azure,boto3,dropbox,google,libcloud,sftp]==1.13.1
napalm==4.0.0
psycopg2==2.9.3
social-auth-core[openidconnect]==4.3.0
ruamel.yaml==0.17.21

View File

@ -1,25 +0,0 @@
import sys
from django.contrib.auth.models import User
from startup_script_utils import load_yaml
from users.models import Token
users = load_yaml("/opt/netbox/initializers/users.yml")
if users is None:
sys.exit()
for username, user_details in users.items():
api_token = user_details.pop("api_token", Token.generate_key())
password = user_details.pop("password", User.objects.make_random_password())
user, created = User.objects.get_or_create(username=username, defaults=user_details)
if created:
user.set_password(password)
user.save()
if api_token:
Token.objects.get_or_create(user=user, key=api_token)
print("👤 Created user", username)

View File

@ -1,23 +0,0 @@
import sys
from startup_script_utils import load_yaml
from users.models import AdminGroup, AdminUser
groups = load_yaml("/opt/netbox/initializers/groups.yml")
if groups is None:
sys.exit()
for groupname, group_details in groups.items():
group, created = AdminGroup.objects.get_or_create(name=groupname)
if created:
print("👥 Created group", groupname)
for username in group_details.get("users", []):
user = AdminUser.objects.get(username=username)
if user:
group.user_set.add(user)
print(" 👤 Assigned user %s to group %s" % (username, group.name))
group.save()

View File

@ -1,68 +0,0 @@
import sys
from django.contrib.contenttypes.models import ContentType
from startup_script_utils import load_yaml
from users.models import AdminGroup, AdminUser, ObjectPermission
object_permissions = load_yaml("/opt/netbox/initializers/object_permissions.yml")
if object_permissions is None:
sys.exit()
for permission_name, permission_details in object_permissions.items():
object_permission, created = ObjectPermission.objects.get_or_create(
name=permission_name,
defaults={
"description": permission_details["description"],
"enabled": permission_details["enabled"],
"actions": permission_details["actions"],
},
)
if permission_details.get("object_types", 0):
object_types = permission_details["object_types"]
if object_types == "all":
object_permission.object_types.set(ContentType.objects.all())
else:
for app_label, models in object_types.items():
if models == "all":
app_models = ContentType.objects.filter(app_label=app_label)
for app_model in app_models:
object_permission.object_types.add(app_model.id)
else:
# There is
for model in models:
object_permission.object_types.add(
ContentType.objects.get(app_label=app_label, model=model)
)
print("🔓 Created object permission", object_permission.name)
if permission_details.get("groups", 0):
for groupname in permission_details["groups"]:
group = AdminGroup.objects.filter(name=groupname).first()
if group:
object_permission.groups.add(group)
print(
" 👥 Assigned group %s object permission of %s"
% (groupname, object_permission.name)
)
if permission_details.get("users", 0):
for username in permission_details["users"]:
user = AdminUser.objects.filter(username=username).first()
if user:
object_permission.users.add(user)
print(
" 👤 Assigned user %s object permission of %s"
% (username, object_permission.name)
)
object_permission.save()

View File

@ -1,67 +0,0 @@
import sys
from extras.models import CustomField
from startup_script_utils import load_yaml
def get_class_for_class_path(class_path):
import importlib
from django.contrib.contenttypes.models import ContentType
module_name, class_name = class_path.rsplit(".", 1)
module = importlib.import_module(module_name)
clazz = getattr(module, class_name)
return ContentType.objects.get_for_model(clazz)
customfields = load_yaml("/opt/netbox/initializers/custom_fields.yml")
if customfields is None:
sys.exit()
for cf_name, cf_details in customfields.items():
custom_field, created = CustomField.objects.get_or_create(name=cf_name)
if created:
if cf_details.get("default", False):
custom_field.default = cf_details["default"]
if cf_details.get("description", False):
custom_field.description = cf_details["description"]
if cf_details.get("label", False):
custom_field.label = cf_details["label"]
for object_type in cf_details.get("on_objects", []):
custom_field.content_types.add(get_class_for_class_path(object_type))
if cf_details.get("required", False):
custom_field.required = cf_details["required"]
if cf_details.get("type", False):
custom_field.type = cf_details["type"]
if cf_details.get("filter_logic", False):
custom_field.filter_logic = cf_details["filter_logic"]
if cf_details.get("weight", -1) >= 0:
custom_field.weight = cf_details["weight"]
if cf_details.get("choices", False):
custom_field.choices = []
for choice_detail in cf_details.get("choices", []):
if isinstance(choice_detail, dict) and "value" in choice_detail:
# legacy mode
print(
f"⚠️ Please migrate the choice '{choice_detail['value']}' of '{cf_name}'"
+ " to the new format, as 'weight' is no longer supported!"
)
custom_field.choices.append(choice_detail["value"])
else:
custom_field.choices.append(choice_detail)
custom_field.save()
print("🔧 Created custom field", cf_name)

View File

@ -1,35 +0,0 @@
import sys
from django.contrib.contenttypes.models import ContentType
from extras.models import CustomLink
from startup_script_utils import load_yaml, split_params
custom_links = load_yaml("/opt/netbox/initializers/custom_links.yml")
if custom_links is None:
sys.exit()
def get_content_type_id(content_type):
try:
return ContentType.objects.get(model=content_type).id
except ContentType.DoesNotExist:
pass
for link in custom_links:
content_type = link.pop("content_type")
link["content_type_id"] = get_content_type_id(content_type)
if link["content_type_id"] is None:
print(
"⚠️ Unable to create Custom Link '{0}': The content_type '{1}' is unknown".format(
link.get("name"), content_type
)
)
continue
matching_params, defaults = split_params(link)
custom_link, created = CustomLink.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🔗 Created Custom Link '{0}'".format(custom_link.name))

View File

@ -1,24 +0,0 @@
import sys
from extras.models import Tag
from startup_script_utils import load_yaml, split_params
from utilities.choices import ColorChoices
tags = load_yaml("/opt/netbox/initializers/tags.yml")
if tags is None:
sys.exit()
for params in tags:
if "color" in params:
color = params.pop("color")
for color_tpl in ColorChoices:
if color in color_tpl:
params["color"] = color_tpl[0]
matching_params, defaults = split_params(params)
tag, created = Tag.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🎨 Created Tag", tag.name)

View File

@ -1,36 +0,0 @@
import sys
from django.contrib.contenttypes.models import ContentType
from extras.models import Webhook
from startup_script_utils import load_yaml, split_params
webhooks = load_yaml("/opt/netbox/initializers/webhooks.yml")
if webhooks is None:
sys.exit()
def get_content_type_id(hook_name, content_type):
try:
return ContentType.objects.get(model=content_type).id
except ContentType.DoesNotExist as ex:
print("⚠️ Webhook '{0}': The object_type '{1}' is unknown.".format(hook_name, content_type))
raise ex
for hook in webhooks:
obj_types = hook.pop("object_types")
try:
obj_type_ids = [get_content_type_id(hook["name"], obj) for obj in obj_types]
except ContentType.DoesNotExist:
continue
matching_params, defaults = split_params(hook)
webhook, created = Webhook.objects.get_or_create(**matching_params, defaults=defaults)
if created:
webhook.content_types.set(obj_type_ids)
webhook.save()
print("🪝 Created Webhook {0}".format(webhook.name))

View File

@ -1,16 +0,0 @@
import sys
from startup_script_utils import load_yaml, split_params
from tenancy.models import TenantGroup
tenant_groups = load_yaml("/opt/netbox/initializers/tenant_groups.yml")
if tenant_groups is None:
sys.exit()
for params in tenant_groups:
matching_params, defaults = split_params(params)
tenant_group, created = TenantGroup.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🔳 Created Tenant Group", tenant_group.name)

View File

@ -1,34 +0,0 @@
import sys
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant, TenantGroup
tenants = load_yaml("/opt/netbox/initializers/tenants.yml")
if tenants is None:
sys.exit()
optional_assocs = {"group": (TenantGroup, "name")}
for params in tenants:
custom_field_data = pop_custom_fields(params)
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)
matching_params, defaults = split_params(params)
tenant, created = Tenant.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("👩‍💻 Created Tenant", tenant.name)
set_custom_fields_values(tenant, custom_field_data)

View File

@ -1,26 +0,0 @@
import sys
from dcim.models import Region
from startup_script_utils import load_yaml, split_params
regions = load_yaml("/opt/netbox/initializers/regions.yml")
if regions is None:
sys.exit()
optional_assocs = {"parent": (Region, "name")}
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)
matching_params, defaults = split_params(params)
region, created = Region.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🌐 Created region", region.name)

View File

@ -1,35 +0,0 @@
import sys
from dcim.models import Region, Site
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
sites = load_yaml("/opt/netbox/initializers/sites.yml")
if sites is None:
sys.exit()
optional_assocs = {"region": (Region, "name"), "tenant": (Tenant, "name")}
for params in sites:
custom_field_data = pop_custom_fields(params)
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)
matching_params, defaults = split_params(params)
site, created = Site.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("📍 Created site", site.name)
set_custom_fields_values(site, custom_field_data)

View File

@ -1,25 +0,0 @@
import sys
from dcim.models import Location, Site
from startup_script_utils import load_yaml, split_params
rack_groups = load_yaml("/opt/netbox/initializers/locations.yml")
if rack_groups is None:
sys.exit()
match_params = ["name", "slug", "site"]
required_assocs = {"site": (Site, "name")}
for params in rack_groups:
for assoc, details in required_assocs.items():
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
matching_params, defaults = split_params(params, match_params)
location, created = Location.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🎨 Created location", location.name)

View File

@ -1,24 +0,0 @@
import sys
from dcim.models import RackRole
from startup_script_utils import load_yaml, split_params
from utilities.choices import ColorChoices
rack_roles = load_yaml("/opt/netbox/initializers/rack_roles.yml")
if rack_roles is None:
sys.exit()
for params in rack_roles:
if "color" in params:
color = params.pop("color")
for color_tpl in ColorChoices:
if color in color_tpl:
params["color"] = color_tpl[0]
matching_params, defaults = split_params(params)
rack_role, created = RackRole.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🎨 Created rack role", rack_role.name)

View File

@ -1,47 +0,0 @@
import sys
from dcim.models import Location, Rack, RackRole, Site
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
racks = load_yaml("/opt/netbox/initializers/racks.yml")
if racks is None:
sys.exit()
match_params = ["name", "site"]
required_assocs = {"site": (Site, "name")}
optional_assocs = {
"role": (RackRole, "name"),
"tenant": (Tenant, "name"),
"location": (Location, "name"),
}
for params in racks:
custom_field_data = pop_custom_fields(params)
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)
matching_params, defaults = split_params(params, match_params)
rack, created = Rack.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🔳 Created rack", rack.site, rack.name)
set_custom_fields_values(rack, custom_field_data)

View File

@ -1,42 +0,0 @@
import sys
from dcim.models import Location, PowerPanel, Site
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
power_panels = load_yaml("/opt/netbox/initializers/power_panels.yml")
if power_panels is None:
sys.exit()
match_params = ["name", "site"]
required_assocs = {"site": (Site, "name")}
optional_assocs = {"location": (Location, "name")}
for params in power_panels:
custom_field_data = pop_custom_fields(params)
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)
matching_params, defaults = split_params(params, match_params)
power_panel, created = PowerPanel.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("⚡ Created Power Panel", power_panel.site, power_panel.name)
set_custom_fields_values(power_panel, custom_field_data)

View File

@ -1,42 +0,0 @@
import sys
from dcim.models import PowerFeed, PowerPanel, Rack
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
power_feeds = load_yaml("/opt/netbox/initializers/power_feeds.yml")
if power_feeds is None:
sys.exit()
match_params = ["name", "power_panel"]
required_assocs = {"power_panel": (PowerPanel, "name")}
optional_assocs = {"rack": (Rack, "name")}
for params in power_feeds:
custom_field_data = pop_custom_fields(params)
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)
matching_params, defaults = split_params(params, match_params)
power_feed, created = PowerFeed.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("⚡ Created Power Feed", power_feed.name)
set_custom_fields_values(power_feed, custom_field_data)

View File

@ -1,16 +0,0 @@
import sys
from dcim.models import Manufacturer
from startup_script_utils import load_yaml, split_params
manufacturers = load_yaml("/opt/netbox/initializers/manufacturers.yml")
if manufacturers is None:
sys.exit()
for params in manufacturers:
matching_params, defaults = split_params(params)
manufacturer, created = Manufacturer.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🏭 Created Manufacturer", manufacturer.name)

View File

@ -1,25 +0,0 @@
import sys
from dcim.models import DeviceRole
from startup_script_utils import load_yaml, split_params
from utilities.choices import ColorChoices
device_roles = load_yaml("/opt/netbox/initializers/device_roles.yml")
if device_roles is None:
sys.exit()
for params in device_roles:
if "color" in params:
color = params.pop("color")
for color_tpl in ColorChoices:
if color in color_tpl:
params["color"] = color_tpl[0]
matching_params, defaults = split_params(params)
device_role, created = DeviceRole.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🎨 Created device role", device_role.name)

View File

@ -1,139 +0,0 @@
import sys
from typing import List
from dcim.models import DeviceType, Manufacturer, Region
from dcim.models.device_component_templates import (
ConsolePortTemplate,
ConsoleServerPortTemplate,
DeviceBayTemplate,
FrontPortTemplate,
InterfaceTemplate,
PowerOutletTemplate,
PowerPortTemplate,
RearPortTemplate,
)
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
from utilities.forms.utils import expand_alphanumeric_pattern
def expand_templates(params: List[dict], device_type: DeviceType) -> List[dict]:
templateable_fields = ["name", "label", "positions", "rear_port", "rear_port_position"]
expanded = []
for param in params:
param["device_type"] = device_type
expanded_fields = {}
has_plain_fields = False
for field in templateable_fields:
template_value = param.pop(f"{field}_template", None)
if field in param:
has_plain_fields = True
elif template_value:
expanded_fields[field] = list(expand_alphanumeric_pattern(template_value))
if expanded_fields and has_plain_fields:
raise ValueError(f"Mix of plain and template keys provided for {templateable_fields}")
elif not expanded_fields:
expanded.append(param)
continue
elements = list(expanded_fields.values())
master_len = len(elements[0])
if not all([len(elem) == master_len for elem in elements]):
raise ValueError(
f"Number of elements in template fields "
f"{list(expanded_fields.keys())} must be equal"
)
for idx in range(master_len):
tmp = param.copy()
for field, value in expanded_fields.items():
if field in nested_assocs:
model, match_key = nested_assocs[field]
query = {match_key: value[idx], "device_type": device_type}
tmp[field] = model.objects.get(**query)
else:
tmp[field] = value[idx]
expanded.append(tmp)
return expanded
device_types = load_yaml("/opt/netbox/initializers/device_types.yml")
if device_types is None:
sys.exit()
match_params = ["manufacturer", "model", "slug"]
required_assocs = {"manufacturer": (Manufacturer, "name")}
optional_assocs = {"region": (Region, "name"), "tenant": (Tenant, "name")}
nested_assocs = {"rear_port": (RearPortTemplate, "name"), "power_port": (PowerPortTemplate, "name")}
supported_components = {
"interfaces": (InterfaceTemplate, ["name"]),
"console_ports": (ConsolePortTemplate, ["name"]),
"console_server_ports": (ConsoleServerPortTemplate, ["name"]),
"power_ports": (PowerPortTemplate, ["name"]),
"power_outlets": (PowerOutletTemplate, ["name"]),
"rear_ports": (RearPortTemplate, ["name"]),
"front_ports": (FrontPortTemplate, ["name"]),
"device_bays": (DeviceBayTemplate, ["name"]),
}
for params in device_types:
custom_field_data = pop_custom_fields(params)
components = [(v[0], v[1], params.pop(k, [])) for k, v in supported_components.items()]
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)
matching_params, defaults = split_params(params, match_params)
device_type, created = DeviceType.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🔡 Created device type", device_type.manufacturer, device_type.model)
set_custom_fields_values(device_type, custom_field_data)
for component in components:
c_model, c_match_params, c_params = component
c_match_params.append("device_type")
if not c_params:
continue
expanded_c_params = expand_templates(c_params, device_type)
for n_assoc, n_details in nested_assocs.items():
n_model, n_field = n_details
for c_param in expanded_c_params:
if n_assoc in c_param:
n_query = {n_field: c_param[n_assoc], "device_type": device_type}
c_param[n_assoc] = n_model.objects.get(**n_query)
for new_param in expanded_c_params:
new_matching_params, new_defaults = split_params(new_param, c_match_params)
new_obj, new_obj_created = c_model.objects.get_or_create(
**new_matching_params, defaults=new_defaults
)
if new_obj_created:
print(
f"🧷 Created {c_model._meta} {new_obj} component for device type {device_type}"
)

View File

@ -1,58 +0,0 @@
import sys
from dcim.models import Device, DeviceRole, DeviceType, Location, Platform, Rack, Site
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
from virtualization.models import Cluster
devices = load_yaml("/opt/netbox/initializers/devices.yml")
if devices is None:
sys.exit()
match_params = ["device_type", "name", "site"]
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"),
"location": (Location, "name"),
}
for params in devices:
custom_field_data = pop_custom_fields(params)
# primary ips are handled later in `380_primary_ips.py`
params.pop("primary_ip4", None)
params.pop("primary_ip6", 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)
matching_params, defaults = split_params(params, match_params)
device, created = Device.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🖥️ Created device", device.name)
set_custom_fields_values(device, custom_field_data)

View File

@ -1,70 +0,0 @@
import sys
from dcim.models import Device, Interface
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
interfaces = load_yaml("/opt/netbox/initializers/dcim_interfaces.yml")
if interfaces is None:
sys.exit()
match_params = ["device", "name"]
required_assocs = {"device": (Device, "name")}
related_assocs = {
"bridge": (Interface, "name"),
"lag": (Interface, "name"),
"parent": (Interface, "name"),
}
for params in interfaces:
custom_field_data = pop_custom_fields(params)
related_interfaces = {k: params.pop(k, None) for k in related_assocs}
for assoc, details in required_assocs.items():
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
matching_params, defaults = split_params(params, match_params)
interface, created = Interface.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print(f"🧷 Created interface {interface} on {interface.device}")
set_custom_fields_values(interface, custom_field_data)
for related_field, related_value in related_interfaces.items():
if not related_value:
continue
r_model, r_field = related_assocs[related_field]
if related_field == "parent" and not interface.parent_id:
query = {r_field: related_value, "device": interface.device}
try:
related_obj = r_model.objects.get(**query)
except Interface.DoesNotExist:
print(f"⚠️ Could not find parent interface with: {query} for interface {interface}")
raise
interface.parent_id = related_obj.id
interface.save()
print(
f"🧷 Attached interface {interface} on {interface.device} "
f"to parent {related_obj}"
)
else:
query = {r_field: related_value, "device": interface.device, "type": related_field}
related_obj, rel_obj_created = r_model.objects.get_or_create(**query)
if rel_obj_created:
setattr(interface, f"{related_field}_id", related_obj.id)
interface.save()
print(f"🧷 Created {related_field} interface {interface} on {interface.device}")

View File

@ -1,28 +0,0 @@
import sys
from dcim.models import Manufacturer, Platform
from startup_script_utils import load_yaml, split_params
platforms = load_yaml("/opt/netbox/initializers/platforms.yml")
if platforms is None:
sys.exit()
optional_assocs = {
"manufacturer": (Manufacturer, "name"),
}
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)
matching_params, defaults = split_params(params)
platform, created = Platform.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("💾 Created platform", platform.name)

View File

@ -1,35 +0,0 @@
import sys
from ipam.models import RouteTarget
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
route_targets = load_yaml("/opt/netbox/initializers/route_targets.yml")
if route_targets is None:
sys.exit()
optional_assocs = {"tenant": (Tenant, "name")}
for params in route_targets:
custom_field_data = pop_custom_fields(params)
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)
matching_params, defaults = split_params(params)
route_target, created = RouteTarget.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🎯 Created Route Target", route_target.name)
set_custom_fields_values(route_target, custom_field_data)

View File

@ -1,36 +0,0 @@
import sys
from ipam.models import VRF
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
vrfs = load_yaml("/opt/netbox/initializers/vrfs.yml")
if vrfs is None:
sys.exit()
match_params = ["name", "rd"]
optional_assocs = {"tenant": (Tenant, "name")}
for params in vrfs:
custom_field_data = pop_custom_fields(params)
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)
matching_params, defaults = split_params(params)
vrf, created = VRF.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("📦 Created VRF", vrf.name)
set_custom_fields_values(vrf, custom_field_data)

View File

@ -1,16 +0,0 @@
import sys
from ipam.models import RIR
from startup_script_utils import load_yaml, split_params
rirs = load_yaml("/opt/netbox/initializers/rirs.yml")
if rirs is None:
sys.exit()
for params in rirs:
matching_params, defaults = split_params(params)
rir, created = RIR.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🗺️ Created RIR", rir.name)

View File

@ -1,34 +0,0 @@
import sys
from ipam.models import ASN, RIR
from startup_script_utils import load_yaml, split_params
from tenancy.models import Tenant
asns = load_yaml("/opt/netbox/initializers/asns.yml")
if asns is None:
sys.exit()
match_params = ["asn", "rir"]
required_assocs = {"rir": (RIR, "name")}
optional_assocs = {"tenant": (Tenant, "name")}
for params in asns:
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)
matching_params, defaults = split_params(params, match_params)
asn, created = ASN.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print(f"🔡 Created ASN {asn.asn}")

View File

@ -1,48 +0,0 @@
import sys
from ipam.models import RIR, Aggregate
from netaddr import IPNetwork
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
aggregates = load_yaml("/opt/netbox/initializers/aggregates.yml")
if aggregates is None:
sys.exit()
match_params = ["prefix", "rir"]
required_assocs = {"rir": (RIR, "name")}
optional_assocs = {
"tenant": (Tenant, "name"),
}
for params in aggregates:
custom_field_data = pop_custom_fields(params)
params["prefix"] = IPNetwork(params["prefix"])
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)
matching_params, defaults = split_params(params, match_params)
aggregate, created = Aggregate.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🗞️ Created Aggregate", aggregate.prefix)
set_custom_fields_values(aggregate, custom_field_data)

View File

@ -1,16 +0,0 @@
import sys
from ipam.models import Role
from startup_script_utils import load_yaml, split_params
roles = load_yaml("/opt/netbox/initializers/prefix_vlan_roles.yml")
if roles is None:
sys.exit()
for params in roles:
matching_params, defaults = split_params(params)
role, created = Role.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("⛹️‍ Created Prefix/VLAN Role", role.name)

View File

@ -1,16 +0,0 @@
import sys
from startup_script_utils import load_yaml, split_params
from virtualization.models import ClusterType
cluster_types = load_yaml("/opt/netbox/initializers/cluster_types.yml")
if cluster_types is None:
sys.exit()
for params in cluster_types:
matching_params, defaults = split_params(params)
cluster_type, created = ClusterType.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🧰 Created Cluster Type", cluster_type.name)

View File

@ -1,18 +0,0 @@
import sys
from startup_script_utils import load_yaml, split_params
from virtualization.models import ClusterGroup
cluster_groups = load_yaml("/opt/netbox/initializers/cluster_groups.yml")
if cluster_groups is None:
sys.exit()
for params in cluster_groups:
matching_params, defaults = split_params(params)
cluster_group, created = ClusterGroup.objects.get_or_create(
**matching_params, defaults=defaults
)
if created:
print("🗄️ Created Cluster Group", cluster_group.name)

View File

@ -1,48 +0,0 @@
import sys
from dcim.models import Site
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
from virtualization.models import Cluster, ClusterGroup, ClusterType
clusters = load_yaml("/opt/netbox/initializers/clusters.yml")
if clusters is None:
sys.exit()
match_params = ["name", "type"]
required_assocs = {"type": (ClusterType, "name")}
optional_assocs = {
"site": (Site, "name"),
"group": (ClusterGroup, "name"),
"tenant": (Tenant, "name"),
}
for params in clusters:
custom_field_data = pop_custom_fields(params)
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)
matching_params, defaults = split_params(params, match_params)
cluster, created = Cluster.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🗄️ Created cluster", cluster.name)
set_custom_fields_values(cluster, custom_field_data)

View File

@ -1,47 +0,0 @@
import sys
from django.contrib.contenttypes.models import ContentType
from ipam.models import VLANGroup
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
vlan_groups = load_yaml("/opt/netbox/initializers/vlan_groups.yml")
if vlan_groups is None:
sys.exit()
optional_assocs = {"scope": (None, "name")}
for params in vlan_groups:
custom_field_data = pop_custom_fields(params)
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = {field: params.pop(assoc)}
# Get model from Contenttype
scope_type = params.pop("scope_type", None)
if not scope_type:
print(f"VLAN Group '{params['name']}': scope_type is missing from VLAN Group")
continue
app_label, model = str(scope_type).split(".")
ct = ContentType.objects.filter(app_label=app_label, model=model).first()
if not ct:
print(
f"VLAN Group '{params['name']}': ContentType for "
+ f"app_label = '{app_label}' and model = '{model}' not found"
)
continue
params["scope_id"] = ct.model_class().objects.get(**query).id
matching_params, defaults = split_params(params)
vlan_group, created = VLANGroup.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🏘️ Created VLAN Group", vlan_group.name)
set_custom_fields_values(vlan_group, custom_field_data)

View File

@ -1,43 +0,0 @@
import sys
from dcim.models import Site
from ipam.models import VLAN, Role, VLANGroup
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant, TenantGroup
vlans = load_yaml("/opt/netbox/initializers/vlans.yml")
if vlans is None:
sys.exit()
match_params = ["name", "vid"]
optional_assocs = {
"site": (Site, "name"),
"tenant": (Tenant, "name"),
"tenant_group": (TenantGroup, "name"),
"group": (VLANGroup, "name"),
"role": (Role, "name"),
}
for params in vlans:
custom_field_data = pop_custom_fields(params)
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)
matching_params, defaults = split_params(params, match_params)
vlan, created = VLAN.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🏠 Created VLAN", vlan.name)
set_custom_fields_values(vlan, custom_field_data)

View File

@ -1,54 +0,0 @@
import sys
from dcim.models import DeviceRole, Platform
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
from virtualization.models import Cluster, VirtualMachine
virtual_machines = load_yaml("/opt/netbox/initializers/virtual_machines.yml")
if virtual_machines is None:
sys.exit()
match_params = ["cluster", "name"]
required_assocs = {"cluster": (Cluster, "name")}
optional_assocs = {
"tenant": (Tenant, "name"),
"platform": (Platform, "name"),
"role": (DeviceRole, "name"),
}
for params in virtual_machines:
custom_field_data = pop_custom_fields(params)
# primary ips are handled later in `270_primary_ips.py`
params.pop("primary_ip4", None)
params.pop("primary_ip6", 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)
matching_params, defaults = split_params(params, match_params)
virtual_machine, created = VirtualMachine.objects.get_or_create(
**matching_params, defaults=defaults
)
if created:
print("🖥️ Created virtual machine", virtual_machine.name)
set_custom_fields_values(virtual_machine, custom_field_data)

View File

@ -1,34 +0,0 @@
import sys
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from virtualization.models import VirtualMachine, VMInterface
interfaces = load_yaml("/opt/netbox/initializers/virtualization_interfaces.yml")
if interfaces is None:
sys.exit()
match_params = ["name", "virtual_machine"]
required_assocs = {"virtual_machine": (VirtualMachine, "name")}
for params in interfaces:
custom_field_data = pop_custom_fields(params)
for assoc, details in required_assocs.items():
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
matching_params, defaults = split_params(params, match_params)
interface, created = VMInterface.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🧷 Created interface", interface.name, interface.virtual_machine.name)
set_custom_fields_values(interface, custom_field_data)

View File

@ -1,46 +0,0 @@
import sys
from dcim.models import Site
from ipam.models import VLAN, VRF, Prefix, Role
from netaddr import IPNetwork
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant, TenantGroup
prefixes = load_yaml("/opt/netbox/initializers/prefixes.yml")
if prefixes is None:
sys.exit()
match_params = ["prefix", "site", "vrf", "vlan"]
optional_assocs = {
"site": (Site, "name"),
"tenant": (Tenant, "name"),
"tenant_group": (TenantGroup, "name"),
"vlan": (VLAN, "name"),
"role": (Role, "name"),
"vrf": (VRF, "name"),
}
for params in prefixes:
custom_field_data = pop_custom_fields(params)
params["prefix"] = IPNetwork(params["prefix"])
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)
matching_params, defaults = split_params(params, match_params)
prefix, created = Prefix.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("📌 Created Prefix", prefix.prefix)
set_custom_fields_values(prefix, custom_field_data)

View File

@ -1,70 +0,0 @@
import sys
from dcim.models import Device, Interface
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from ipam.models import VRF, IPAddress
from netaddr import IPNetwork
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
from virtualization.models import VirtualMachine, VMInterface
ip_addresses = load_yaml("/opt/netbox/initializers/ip_addresses.yml")
if ip_addresses is None:
sys.exit()
match_params = ["address", "vrf"]
optional_assocs = {
"tenant": (Tenant, "name"),
"vrf": (VRF, "name"),
"interface": (Interface, "name"),
}
vm_interface_ct = ContentType.objects.filter(
Q(app_label="virtualization", model="vminterface")
).first()
interface_ct = ContentType.objects.filter(Q(app_label="dcim", model="interface")).first()
for params in ip_addresses:
custom_field_data = pop_custom_fields(params)
vm = params.pop("virtual_machine", None)
device = params.pop("device", None)
params["address"] = IPNetwork(params["address"])
if vm and device:
print("IP Address can only specify one of the following: virtual_machine or device.")
sys.exit()
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
if assoc == "interface":
if vm:
vm_id = VirtualMachine.objects.get(name=vm).id
query = {"name": params.pop(assoc), "virtual_machine_id": vm_id}
params["assigned_object_type"] = vm_interface_ct
params["assigned_object_id"] = VMInterface.objects.get(**query).id
elif device:
dev_id = Device.objects.get(name=device).id
query = {"name": params.pop(assoc), "device_id": dev_id}
params["assigned_object_type"] = interface_ct
params["assigned_object_id"] = Interface.objects.get(**query).id
else:
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
matching_params, defaults = split_params(params, match_params)
ip_address, created = IPAddress.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🧬 Created IP Address", ip_address.address)
set_custom_fields_values(ip_address, custom_field_data)

View File

@ -1,47 +0,0 @@
import sys
from dcim.models import Device
from ipam.models import IPAddress
from startup_script_utils import load_yaml
from virtualization.models import VirtualMachine
def link_primary_ip(assets, asset_model):
for params in assets:
primary_ip_fields = set(params) & {"primary_ip4", "primary_ip6"}
if not primary_ip_fields:
continue
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = {field: params.pop(assoc)}
try:
params[assoc] = model.objects.get(**query)
except model.DoesNotExist:
primary_ip_fields -= {assoc}
print(f"⚠️ IP Address '{query[field]}' not found")
asset = asset_model.objects.get(name=params["name"])
for field in primary_ip_fields:
if getattr(asset, field) != params[field]:
setattr(asset, field, params[field])
print(f"🔗 Define primary IP '{params[field].address}' on '{asset.name}'")
asset.save()
devices = load_yaml("/opt/netbox/initializers/devices.yml")
virtual_machines = load_yaml("/opt/netbox/initializers/virtual_machines.yml")
optional_assocs = {
"primary_ip4": (IPAddress, "address"),
"primary_ip6": (IPAddress, "address"),
}
if devices is None and virtual_machines is None:
sys.exit()
if devices is not None:
link_primary_ip(devices, Device)
if virtual_machines is not None:
link_primary_ip(virtual_machines, VirtualMachine)

View File

@ -1,32 +0,0 @@
import sys
from dcim.models import Device
from ipam.models import Service
from startup_script_utils import load_yaml, split_params
from virtualization.models import VirtualMachine
services = load_yaml("/opt/netbox/initializers/services.yml")
if services is None:
sys.exit()
match_params = ["name", "device", "virtual_machine"]
optional_assocs = {
"device": (Device, "name"),
"virtual_machine": (VirtualMachine, "name"),
}
for params in services:
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)
matching_params, defaults = split_params(params, match_params)
service, created = Service.objects.get_or_create(**params)
if created:
print("🧰 Created Service", service.name)

View File

@ -1,25 +0,0 @@
import sys
from circuits.models import Provider
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
providers = load_yaml("/opt/netbox/initializers/providers.yml")
if providers is None:
sys.exit()
for params in providers:
custom_field_data = pop_custom_fields(params)
matching_params, defaults = split_params(params)
provider, created = Provider.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("📡 Created provider", provider.name)
set_custom_fields_values(provider, custom_field_data)

View File

@ -1,25 +0,0 @@
import sys
from circuits.models import CircuitType
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
circuit_types = load_yaml("/opt/netbox/initializers/circuit_types.yml")
if circuit_types is None:
sys.exit()
for params in circuit_types:
custom_field_data = pop_custom_fields(params)
matching_params, defaults = split_params(params)
circuit_type, created = CircuitType.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("⚡ Created Circuit Type", circuit_type.name)
set_custom_fields_values(circuit_type, custom_field_data)

View File

@ -1,43 +0,0 @@
import sys
from circuits.models import Circuit, CircuitType, Provider
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Tenant
circuits = load_yaml("/opt/netbox/initializers/circuits.yml")
if circuits is None:
sys.exit()
match_params = ["cid", "provider", "type"]
required_assocs = {"provider": (Provider, "name"), "type": (CircuitType, "name")}
optional_assocs = {"tenant": (Tenant, "name")}
for params in circuits:
custom_field_data = pop_custom_fields(params)
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)
matching_params, defaults = split_params(params, match_params)
circuit, created = Circuit.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("⚡ Created Circuit", circuit.cid)
set_custom_fields_values(circuit, custom_field_data)

View File

@ -1,227 +0,0 @@
import sys
from typing import Tuple
from circuits.models import Circuit, CircuitTermination, ProviderNetwork
from dcim.models import (
Cable,
ConsolePort,
ConsoleServerPort,
FrontPort,
Interface,
PowerFeed,
PowerOutlet,
PowerPanel,
PowerPort,
RearPort,
Site,
)
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from startup_script_utils import load_yaml
CONSOLE_PORT_TERMINATION = ContentType.objects.get_for_model(ConsolePort)
CONSOLE_SERVER_PORT_TERMINATION = ContentType.objects.get_for_model(ConsoleServerPort)
FRONT_PORT_TERMINATION = ContentType.objects.get_for_model(FrontPort)
REAR_PORT_TERMINATION = ContentType.objects.get_for_model(RearPort)
FRONT_AND_REAR = [FRONT_PORT_TERMINATION, REAR_PORT_TERMINATION]
POWER_PORT_TERMINATION = ContentType.objects.get_for_model(PowerPort)
POWER_OUTLET_TERMINATION = ContentType.objects.get_for_model(PowerOutlet)
POWER_FEED_TERMINATION = ContentType.objects.get_for_model(PowerFeed)
POWER_TERMINATIONS = [POWER_PORT_TERMINATION, POWER_OUTLET_TERMINATION, POWER_FEED_TERMINATION]
VIRTUAL_INTERFACES = ["bridge", "lag", "virtual"]
def get_termination_object(params: dict, side: str):
klass = params.pop(f"termination_{side}_class")
name = params.pop(f"termination_{side}_name", None)
device = params.pop(f"termination_{side}_device", None)
feed_params = params.pop(f"termination_{side}_feed", None)
circuit_params = params.pop(f"termination_{side}_circuit", {})
if name and device:
termination = klass.objects.get(name=name, device__name=device)
return termination
elif feed_params:
q = {
"name": feed_params["power_panel"]["name"],
"site__name": feed_params["power_panel"]["site"],
}
power_panel = PowerPanel.objects.get(**q)
termination = PowerFeed.objects.get(name=feed_params["name"], power_panel=power_panel)
return termination
elif circuit_params:
circuit = Circuit.objects.get(cid=circuit_params.pop("cid"))
term_side = circuit_params.pop("term_side").upper()
site_name = circuit_params.pop("site", None)
provider_network = circuit_params.pop("provider_network", None)
if site_name:
circuit_params["site"] = Site.objects.get(name=site_name)
elif provider_network:
circuit_params["provider_network"] = ProviderNetwork.objects.get(name=provider_network)
else:
raise ValueError(
f"⚠️ Missing one of required parameters: 'site' or 'provider_network' "
f"for side {term_side} of circuit {circuit}"
)
termination, created = CircuitTermination.objects.get_or_create(
circuit=circuit, term_side=term_side, defaults=circuit_params
)
if created:
print(f"⚡ Created new CircuitTermination {termination}")
return termination
raise ValueError(
f"⚠️ Missing parameters for termination_{side}. "
"Need termination_{side}_name AND termination_{side}_device OR termination_{side}_circuit"
)
def get_termination_class_by_name(port_class: str):
if not port_class:
return Interface
return globals()[port_class]
def cable_in_cables(term_a: tuple, term_b: tuple) -> bool:
"""Check if cable exist for given terminations.
Each tuple should consist termination object and termination type
"""
cable = Cable.objects.filter(
Q(
termination_a_id=term_a[0].id,
termination_a_type=term_a[1],
termination_b_id=term_b[0].id,
termination_b_type=term_b[1],
)
| Q(
termination_a_id=term_b[0].id,
termination_a_type=term_b[1],
termination_b_id=term_a[0].id,
termination_b_type=term_a[1],
)
)
return cable.exists()
def check_termination_types(type_a, type_b) -> Tuple[bool, str]:
if type_a in POWER_TERMINATIONS and type_b in POWER_TERMINATIONS:
if type_a == type_b:
return False, "Can't connect the same power terminations together"
elif (
type_a == POWER_OUTLET_TERMINATION
and type_b == POWER_FEED_TERMINATION
or type_a == POWER_FEED_TERMINATION
and type_b == POWER_OUTLET_TERMINATION
):
return False, "PowerOutlet can't be connected with PowerFeed"
elif type_a in POWER_TERMINATIONS or type_b in POWER_TERMINATIONS:
return False, "Can't mix power terminations with port terminations"
elif type_a in FRONT_AND_REAR or type_b in FRONT_AND_REAR:
return True, ""
elif (
type_a == CONSOLE_PORT_TERMINATION
and type_b != CONSOLE_SERVER_PORT_TERMINATION
or type_b == CONSOLE_PORT_TERMINATION
and type_a != CONSOLE_SERVER_PORT_TERMINATION
):
return False, "ConsolePorts can only be connected to ConsoleServerPorts or Front/Rear ports"
return True, ""
def get_cable_name(termination_a: tuple, termination_b: tuple) -> str:
"""Returns name of a cable in format:
device_a interface_a <---> interface_b device_b
or for circuits:
circuit_a termination_a <---> termination_b circuit_b
"""
cable_name = []
for is_side_b, termination in enumerate([termination_a, termination_b]):
try:
power_panel_id = getattr(termination[0], "power_panel_id", None)
if power_panel_id:
power_feed = PowerPanel.objects.get(id=power_panel_id)
segment = [f"{power_feed}", f"{termination[0]}"]
else:
segment = [f"{termination[0].device}", f"{termination[0]}"]
except AttributeError:
segment = [f"{termination[0].circuit.cid}", f"{termination[0]}"]
if is_side_b:
segment.reverse()
cable_name.append(" ".join(segment))
return " <---> ".join(cable_name)
def check_interface_types(*args):
for termination in args:
try:
if termination.type in VIRTUAL_INTERFACES:
raise Exception(
f"⚠️ Virtual interfaces are not supported for cabling. "
f"Termination {termination.device} {termination} {termination.type}"
)
except AttributeError:
# CircuitTermination doesn't have a type field
pass
def check_terminations_are_free(*args):
any_failed = False
for termination in args:
if termination.cable_id:
any_failed = True
print(
f"⚠️ Termination {termination} is already occupied "
f"with cable #{termination.cable_id}"
)
if any_failed:
raise Exception("⚠️ At least one end of the cable is already occupied.")
cables = load_yaml("/opt/netbox/initializers/cables.yml")
if cables is None:
sys.exit()
for params in cables:
params["termination_a_class"] = get_termination_class_by_name(params.get("termination_a_class"))
params["termination_b_class"] = get_termination_class_by_name(params.get("termination_b_class"))
term_a = get_termination_object(params, side="a")
term_b = get_termination_object(params, side="b")
check_interface_types(term_a, term_b)
term_a_ct = ContentType.objects.get_for_model(term_a)
term_b_ct = ContentType.objects.get_for_model(term_b)
types_ok, msg = check_termination_types(term_a_ct, term_b_ct)
cable_name = get_cable_name((term_a, term_a_ct), (term_b, term_b_ct))
if not types_ok:
print(f"⚠️ Invalid termination types for {cable_name}. {msg}")
continue
if cable_in_cables((term_a, term_a_ct), (term_b, term_b_ct)):
continue
check_terminations_are_free(term_a, term_b)
params["termination_a_id"] = term_a.id
params["termination_b_id"] = term_b.id
params["termination_a_type"] = term_a_ct
params["termination_b_type"] = term_b_ct
cable = Cable.objects.create(**params)
print(f"🧷 Created cable {cable} {cable_name}")

View File

@ -1,36 +0,0 @@
import sys
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import ContactGroup
contact_groups = load_yaml("/opt/netbox/initializers/contact_groups.yml")
if contact_groups is None:
sys.exit()
optional_assocs = {"parent": (ContactGroup, "name")}
for params in contact_groups:
custom_field_data = pop_custom_fields(params)
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)
matching_params, defaults = split_params(params)
contact_group, created = ContactGroup.objects.get_or_create(
**matching_params, defaults=defaults
)
if created:
print("🔳 Created Contact Group", contact_group.name)
set_custom_fields_values(contact_group, custom_field_data)

View File

@ -1,26 +0,0 @@
import sys
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import ContactRole
contact_roles = load_yaml("/opt/netbox/initializers/contact_roles.yml")
if contact_roles is None:
sys.exit()
for params in contact_roles:
custom_field_data = pop_custom_fields(params)
matching_params, defaults = split_params(params)
contact_role, created = ContactRole.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("🔳 Created Contact Role", contact_role.name)
set_custom_fields_values(contact_role, custom_field_data)

View File

@ -1,34 +0,0 @@
import sys
from startup_script_utils import (
load_yaml,
pop_custom_fields,
set_custom_fields_values,
split_params,
)
from tenancy.models import Contact, ContactGroup
contacts = load_yaml("/opt/netbox/initializers/contacts.yml")
if contacts is None:
sys.exit()
optional_assocs = {"group": (ContactGroup, "name")}
for params in contacts:
custom_field_data = pop_custom_fields(params)
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)
matching_params, defaults = split_params(params)
contact, created = Contact.objects.get_or_create(**matching_params, defaults=defaults)
if created:
print("👩‍💻 Created Contact", contact.name)
set_custom_fields_values(contact, custom_field_data)

View File

@ -1,31 +0,0 @@
#!/usr/bin/env python3
import runpy
from os import scandir
from os.path import abspath, dirname
this_dir = dirname(abspath(__file__))
def filename(f):
return f.name
with scandir(this_dir) as it:
for f in sorted(it, key=filename):
if not f.is_file():
continue
if f.name.startswith("__"):
continue
if not f.name.endswith(".py"):
continue
print(f"▶️ Running the startup script {f.path}")
try:
runpy.run_path(f.path)
except SystemExit as e:
if e.code is not None and e.code != 0:
print(f"‼️ The startup script {f.path} returned with code {e.code}, exiting.")
raise

Some files were not shown because too many files have changed in this diff Show More