Merge pull request #371 from netbox-community/develop

Version 0.27.0
This commit is contained in:
Christian Mäder 2020-12-15 09:53:01 +01:00 committed by GitHub
commit aa4d630a0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 232 additions and 296 deletions

View File

@ -19,8 +19,7 @@ jobs:
- ./build.sh develop - ./build.sh develop
docker_from: docker_from:
- '' # use the default of the build script - '' # use the default of the build script
- python:3.8-alpine # - python:3.10-rc-alpine # disable until dependencies work
- python:3.9-alpine
fail-fast: false fail-fast: false
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Builds new Netbox Docker Images name: Builds new Netbox Docker Images

View File

@ -50,3 +50,34 @@ jobs:
name: Logout of the Docker Registry name: Logout of the Docker Registry
run: docker logout "${DOCKER_REGISTRY}" run: docker logout "${DOCKER_REGISTRY}"
if: steps.docker-build.outputs.skipped != 'true' if: steps.docker-build.outputs.skipped != 'true'
# Quay.io
- id: quayio-docker-build
name: Build the image with '${{ matrix.build_cmd }}'
run: ${{ matrix.build_cmd }}
env:
DOCKER_REGISTRY: quay.io
GH_ACTION: enable
- id: quayio-registry-login
name: Login to the Quay.io Registry
run: |
echo "::add-mask::$QUAYIO_USERNAME"
echo "::add-mask::$QUAYIO_PASSWORD"
docker login -u "$QUAYIO_USERNAME" --password "${QUAYIO_PASSWORD}" "${DOCKER_REGISTRY}"
env:
DOCKER_REGISTRY: quay.io
QUAYIO_USERNAME: ${{ secrets.quayio_username }}
QUAYIO_PASSWORD: ${{ secrets.quayio_password }}
if: steps.docker-build.outputs.skipped != 'true'
- id: quayio-registry-push
name: Push the image
run: ${{ matrix.build_cmd }} --push-only
env:
DOCKER_REGISTRY: quay.io
if: steps.docker-build.outputs.skipped != 'true'
- id: quayio-registry-logout
name: Logout of the Docker Registry
run: docker logout "${DOCKER_REGISTRY}"
env:
DOCKER_REGISTRY: quay.io
if: steps.docker-build.outputs.skipped != 'true'

View File

@ -10,7 +10,7 @@
[![GitHub license](https://img.shields.io/github/license/netbox-community/netbox-docker)][netbox-docker-license] [![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 Docker container. [The Github repository](netbox-docker-github) houses the components needed to build Netbox as a Docker container.
Images are built using this code and are released to [Docker Hub][netbox-dockerhub] once a day. Images are built using this code and are released to [Docker Hub][netbox-dockerhub] and [Quay.io][netbox-quayio] once a day.
Do you have any questions? Do you have any questions?
Before opening an issue on Github, please join the [Network To Code][ntc-slack] Slack and ask for help in our [`#netbox-docker`][netbox-docker-slack] channel. Before opening an issue on Github, please join the [Network To Code][ntc-slack] Slack and ask for help in our [`#netbox-docker`][netbox-docker-slack] channel.
@ -18,11 +18,12 @@ Before opening an issue on Github, please join the [Network To Code][ntc-slack]
[github-stargazers]: https://github.com/netbox-community/netbox-docker/stargazers [github-stargazers]: https://github.com/netbox-community/netbox-docker/stargazers
[github-release]: https://github.com/netbox-community/netbox-docker/releases [github-release]: https://github.com/netbox-community/netbox-docker/releases
[netbox-docker-microbadger]: https://microbadger.com/images/netboxcommunity/netbox [netbox-docker-microbadger]: https://microbadger.com/images/netboxcommunity/netbox
[netbox-dockerhub]: https://hub.docker.com/r/netboxcommunity/netbox/tags/ [netbox-dockerhub]: https://hub.docker.com/r/netboxcommunity/netbox/
[netbox-docker-github]: https://github.com/netbox-community/netbox-docker/ [netbox-docker-github]: https://github.com/netbox-community/netbox-docker/
[ntc-slack]: http://slack.networktocode.com/ [ntc-slack]: http://slack.networktocode.com/
[netbox-docker-slack]: https://slack.com/app_redirect?channel=netbox-docker&team=T09LQ7E9E [netbox-docker-slack]: https://slack.com/app_redirect?channel=netbox-docker&team=T09LQ7E9E
[netbox-docker-license]: https://github.com/netbox-community/netbox-docker/blob/release/LICENSE [netbox-docker-license]: https://github.com/netbox-community/netbox-docker/blob/release/LICENSE
[netbox-quayio]: https://quay.io/repository/netboxcommunity/netbox
## Docker Tags ## Docker Tags
@ -85,11 +86,13 @@ It covers advanced topics such as using files for secrets, deployment to Kuberne
## Getting Help ## Getting Help
Please join [our Slack channel `#netbox-docker`][netbox-docker-slack] on the [Network To Code Slack][ntc-slack]. Feel free to ask questions in our [Github Community][netbox-community] or join [our Slack channel `#netbox-docker`][netbox-docker-slack] on the [Network To Code Slack][ntc-slack],
It's free to use and there are almost always people online that can help. which is free to use and where there are almost always people online that can help you in the Slack channel.
If you need help with using Netbox or developing for it or against it's API you may find the `#netbox` channel on the same Slack instance very helpful. If you need help with using Netbox or developing for it or against it's API you may find the `#netbox` channel on the same Slack instance very helpful.
[netbox-community]: https://github.com/netbox-community/netbox-docker/discussions
## Dependencies ## 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:
@ -104,7 +107,7 @@ To check the version installed on your system run `docker --version` and `docker
The `docker-compose.yml` file is prepared to run a specific version of Netbox, instead of `latest`. The `docker-compose.yml` file is prepared to run a specific version of Netbox, instead of `latest`.
To use this feature, set and export the environment-variable `VERSION` before launching `docker-compose`, as shown below. To use this feature, set and export the environment-variable `VERSION` before launching `docker-compose`, as shown below.
`VERSION` may be set to the name of `VERSION` may be set to the name of
[any tag of the `netboxcommunity/netbox` Docker image on Docker Hub][netbox-dockerhub]. [any tag of the `netboxcommunity/netbox` Docker image on Docker Hub][netbox-dockerhub] or [Quay.io][netbox-quayio].
```bash ```bash
export VERSION=v2.7.1 export VERSION=v2.7.1

View File

@ -1 +1 @@
0.26.2 0.27.0

View File

@ -49,7 +49,7 @@ if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then
echo " DOCKERFILE The name of Dockerfile to use." echo " DOCKERFILE The name of Dockerfile to use."
echo " Default: Dockerfile" echo " Default: Dockerfile"
echo " DOCKER_FROM The base image to use." echo " DOCKER_FROM The base image to use."
echo " Default: 'python:3.8-alpine'" echo " Default: 'python:3.9-alpine'"
echo " DOCKER_TARGET A specific target to build." echo " DOCKER_TARGET A specific target to build."
echo " It's currently not possible to pass multiple targets." echo " It's currently not possible to pass multiple targets."
echo " Default: main ldap" echo " Default: main ldap"
@ -157,7 +157,7 @@ fi
# Determining the value for DOCKER_FROM # Determining the value for DOCKER_FROM
### ###
if [ -z "$DOCKER_FROM" ]; then if [ -z "$DOCKER_FROM" ]; then
DOCKER_FROM="python:3.8-alpine" DOCKER_FROM="python:3.9-alpine"
fi fi
### ###

View File

@ -157,7 +157,7 @@ LOGIN_REQUIRED = environ.get('LOGIN_REQUIRED', 'False').lower() == 'true'
# The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to # The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to
# re-authenticate. (Default: 1209600 [14 days]) # re-authenticate. (Default: 1209600 [14 days])
LOGIN_TIMEOUT = environ.get('LOGIN_TIMEOUT', None) LOGIN_TIMEOUT = int(environ.get('LOGIN_TIMEOUT', 1209600))
# Setting this to True will display a "maintenance mode" banner at the top of every page. # Setting this to True will display a "maintenance mode" banner at the top of every page.
MAINTENANCE_MODE = environ.get('MAINTENANCE_MODE', 'False').lower() == 'true' MAINTENANCE_MODE = environ.get('MAINTENANCE_MODE', 'False').lower() == 'true'
@ -233,7 +233,7 @@ SCRIPTS_ROOT = environ.get('SCRIPTS_ROOT', '/etc/netbox/scripts')
# By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use # By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use
# local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only # local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only
# database access.) Note that the user as which NetBox runs must have read and write permissions to this path. # database access.) Note that the user as which NetBox runs must have read and write permissions to this path.
SESSION_FILE_PATH = environ.get('REPORTS_ROOT', None) SESSION_FILE_PATH = environ.get('SESSIONS_ROOT', None)
# Time zone (default: UTC) # Time zone (default: UTC)
TIME_ZONE = environ.get('TIME_ZONE', 'UTC') TIME_ZONE = environ.get('TIME_ZONE', 'UTC')

View File

@ -60,14 +60,17 @@ AUTH_LDAP_GROUP_SEARCH = LDAPSearch(AUTH_LDAP_GROUP_SEARCH_BASEDN, ldap.SCOPE_SU
AUTH_LDAP_GROUP_TYPE = _import_group_type(environ.get('AUTH_LDAP_GROUP_TYPE', 'GroupOfNamesType')) AUTH_LDAP_GROUP_TYPE = _import_group_type(environ.get('AUTH_LDAP_GROUP_TYPE', 'GroupOfNamesType'))
# Define a group required to login. # Define a group required to login.
AUTH_LDAP_REQUIRE_GROUP = environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', '') AUTH_LDAP_REQUIRE_GROUP = environ.get('AUTH_LDAP_REQUIRE_GROUP_DN')
# Define special user types using groups. Exercise great caution when assigning superuser status. # Define special user types using groups. Exercise great caution when assigning superuser status.
AUTH_LDAP_USER_FLAGS_BY_GROUP = { AUTH_LDAP_USER_FLAGS_BY_GROUP = {}
"is_active": environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', ''),
"is_staff": environ.get('AUTH_LDAP_IS_ADMIN_DN', ''), if AUTH_LDAP_REQUIRE_GROUP is not None:
"is_superuser": environ.get('AUTH_LDAP_IS_SUPERUSER_DN', '') AUTH_LDAP_USER_FLAGS_BY_GROUP = {
} "is_active": environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', ''),
"is_staff": environ.get('AUTH_LDAP_IS_ADMIN_DN', ''),
"is_superuser": environ.get('AUTH_LDAP_IS_SUPERUSER_DN', '')
}
# For more granular permissions, we can map LDAP groups to Django groups. # For more granular permissions, we can map LDAP groups to Django groups.
AUTH_LDAP_FIND_GROUP_PERMS = environ.get('AUTH_LDAP_FIND_GROUP_PERMS', 'True').lower() == 'true' AUTH_LDAP_FIND_GROUP_PERMS = environ.get('AUTH_LDAP_FIND_GROUP_PERMS', 'True').lower() == 'true'

View File

@ -43,20 +43,16 @@
# required: false # required: false
# filter_logic: exact # filter_logic: exact
# weight: 30 # weight: 30
# default: First Item
# on_objects: # on_objects:
# - dcim.models.Device # - dcim.models.Device
# choices: # choices:
# - value: First Item # - First Item
# weight: 10 # - Second Item
# - value: Second Item # - Third Item
# weight: 20 # - Fifth Item
# - value: Third Item # - Fourth Item
# weight: 30 # select_field_legacy_format:
# - value: Fifth Item
# weight: 50
# - value: Fourth Item
# weight: 40
# select_field_auto_weight:
# type: select # type: select
# label: Choose between items # label: Choose between items
# required: false # required: false
@ -65,9 +61,9 @@
# on_objects: # on_objects:
# - dcim.models.Device # - dcim.models.Device
# choices: # choices:
# - value: A # - value: A # this is the deprecated format.
# - value: B # - value: B # we only use it for the tests.
# - value: C # - value: C # please see above for the new format.
# - value: "D like deprecated" # - value: "D like deprecated"
# weight: 999 # weight: 999
# - value: E # - value: E
@ -76,7 +72,7 @@
# label: Yes Or No? # label: Yes Or No?
# required: true # required: true
# filter_logic: loose # filter_logic: loose
# default: "false" # important: but "false" in quotes! # default: "false" # important: put "false" in quotes!
# weight: 90 # weight: 90
# on_objects: # on_objects:
# - dcim.models.Device # - dcim.models.Device

View File

@ -2,22 +2,22 @@
# manufacturer: Manufacturer 1 # manufacturer: Manufacturer 1
# slug: model-1 # slug: model-1
# u_height: 2 # u_height: 2
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description
# - model: Model 2 # - model: Model 2
# manufacturer: Manufacturer 1 # manufacturer: Manufacturer 1
# slug: model-2 # slug: model-2
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description
# - model: Model 3 # - model: Model 3
# manufacturer: Manufacturer 1 # manufacturer: Manufacturer 1
# slug: model-3 # slug: model-3
# is_full_depth: false # is_full_depth: false
# u_height: 0 # u_height: 0
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description
# - model: Other # - model: Other
# manufacturer: No Name # manufacturer: No Name
# slug: other # slug: other
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description

View File

@ -20,7 +20,7 @@
# rack: rack-01 # rack: rack-01
# face: front # face: front
# position: 1 # position: 1
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description
# - name: server02 # - name: server02
# device_role: server # device_role: server
@ -31,7 +31,7 @@
# position: 2 # position: 2
# primary_ip4: 10.1.1.2/24 # primary_ip4: 10.1.1.2/24
# primary_ip6: 2001:db8:a000:1::2/64 # primary_ip6: 2001:db8:a000:1::2/64
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description
# - name: server03 # - name: server03
# device_role: server # device_role: server
@ -40,5 +40,5 @@
# rack: rack-03 # rack: rack-03
# face: front # face: front
# position: 3 # position: 3
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description

View File

@ -20,7 +20,7 @@
# type: 4-post-cabinet # type: 4-post-cabinet
# width: 19 # width: 19
# u_height: 47 # u_height: 47
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description
# - site: AMS 2 # - site: AMS 2
# name: rack-02 # name: rack-02
@ -28,7 +28,7 @@
# type: 4-post-cabinet # type: 4-post-cabinet
# width: 19 # width: 19
# u_height: 47 # u_height: 47
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description
# - site: SING 1 # - site: SING 1
# name: rack-03 # name: rack-03
@ -37,5 +37,5 @@
# type: 4-post-cabinet # type: 4-post-cabinet
# width: 19 # width: 19
# u_height: 47 # u_height: 47
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description

View File

@ -4,29 +4,29 @@
# status: active # status: active
# facility: Amsterdam 1 # facility: Amsterdam 1
# asn: 12345 # asn: 12345
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description for AMS1
# - name: AMS 2 # - name: AMS 2
# slug: ams2 # slug: ams2
# region: Downtown # region: Downtown
# status: active # status: active
# facility: Amsterdam 2 # facility: Amsterdam 2
# asn: 54321 # asn: 54321
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description for AMS2
# - name: AMS 3 # - name: AMS 3
# slug: ams3 # slug: ams3
# region: Suburbs # region: Suburbs
# status: active # status: active
# facility: Amsterdam 3 # facility: Amsterdam 3
# asn: 67890 # asn: 67890
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description for AMS3
# - name: SING 1 # - name: SING 1
# slug: sing1 # slug: sing1
# region: Singapore # region: Singapore
# status: active # status: active
# facility: Singapore 1 # facility: Singapore 1
# asn: 09876 # asn: 09876
# custom_fields: # custom_field_data:
# text_field: Description # text_field: Description for SING1

View File

@ -1,6 +1,6 @@
import sys import sys
from django.contrib.auth.models import Group, User from django.contrib.auth.models import User
from startup_script_utils import load_yaml, set_permissions from startup_script_utils import load_yaml, set_permissions
from users.models import Token from users.models import Token

View File

@ -1,8 +1,8 @@
from extras.models import CustomField, CustomFieldChoice
from startup_script_utils import load_yaml
import sys import sys
from extras.models import CustomField
from startup_script_utils import load_yaml
def get_class_for_class_path(class_path): def get_class_for_class_path(class_path):
import importlib import importlib
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
@ -21,34 +21,38 @@ for cf_name, cf_details in customfields.items():
custom_field, created = CustomField.objects.get_or_create(name = cf_name) custom_field, created = CustomField.objects.get_or_create(name = cf_name)
if created: if created:
if cf_details.get('default', 0): if cf_details.get('default', False):
custom_field.default = cf_details['default'] custom_field.default = cf_details['default']
if cf_details.get('description', 0): if cf_details.get('description', False):
custom_field.description = cf_details['description'] custom_field.description = cf_details['description']
if cf_details.get('label', 0): if cf_details.get('label', False):
custom_field.label = cf_details['label'] custom_field.label = cf_details['label']
for object_type in cf_details.get('on_objects', []): for object_type in cf_details.get('on_objects', []):
custom_field.obj_type.add(get_class_for_class_path(object_type)) custom_field.content_types.add(get_class_for_class_path(object_type))
if cf_details.get('required', 0): if cf_details.get('required', False):
custom_field.required = cf_details['required'] custom_field.required = cf_details['required']
if cf_details.get('type', 0): if cf_details.get('type', False):
custom_field.type = cf_details['type'] custom_field.type = cf_details['type']
if cf_details.get('weight', 0): if cf_details.get('weight', -1) >= 0:
custom_field.weight = cf_details['weight'] 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() custom_field.save()
for idx, choice_details in enumerate(cf_details.get('choices', [])):
choice, _ = CustomFieldChoice.objects.get_or_create(
field=custom_field,
value=choice_details['value'],
defaults={'weight': idx * 10}
)
print("🔧 Created custom field", cf_name) print("🔧 Created custom field", cf_name)

View File

@ -1,9 +1,9 @@
from dcim.models import Region, Site
from extras.models import CustomField, CustomFieldValue
from tenancy.models import Tenant
from startup_script_utils import load_yaml
import sys import sys
from dcim.models import Region, Site
from startup_script_utils import *
from tenancy.models import Tenant
sites = load_yaml('/opt/netbox/initializers/sites.yml') sites = load_yaml('/opt/netbox/initializers/sites.yml')
if sites is None: if sites is None:
@ -15,7 +15,7 @@ optional_assocs = {
} }
for params in sites: for params in sites:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
for assoc, details in optional_assocs.items(): for assoc, details in optional_assocs.items():
if assoc in params: if assoc in params:
@ -27,15 +27,6 @@ for params in sites:
site, created = Site.objects.get_or_create(**params) site, created = Site.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(site, custom_field_data)
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) print("📍 Created site", site.name)

View File

@ -1,9 +1,9 @@
from dcim.models import DeviceType, Manufacturer, Region
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
from startup_script_utils import load_yaml
import sys import sys
from dcim.models import DeviceType, Manufacturer, Region
from startup_script_utils import *
from tenancy.models import Tenant
device_types = load_yaml('/opt/netbox/initializers/device_types.yml') device_types = load_yaml('/opt/netbox/initializers/device_types.yml')
if device_types is None: if device_types is None:
@ -19,7 +19,7 @@ optional_assocs = {
} }
for params in device_types: for params in device_types:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
for assoc, details in required_assocs.items(): for assoc, details in required_assocs.items():
model, field = details model, field = details
@ -37,15 +37,6 @@ for params in device_types:
device_type, created = DeviceType.objects.get_or_create(**params) device_type, created = DeviceType.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(device_type, custom_field_data)
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) print("🔡 Created device type", device_type.manufacturer, device_type.model)

View File

@ -1,9 +1,9 @@
from dcim.models import Site, RackRole, Rack, RackGroup
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
from startup_script_utils import load_yaml
import sys import sys
from dcim.models import Site, RackRole, Rack, RackGroup
from startup_script_utils import *
from tenancy.models import Tenant
racks = load_yaml('/opt/netbox/initializers/racks.yml') racks = load_yaml('/opt/netbox/initializers/racks.yml')
if racks is None: if racks is None:
@ -20,7 +20,7 @@ optional_assocs = {
} }
for params in racks: for params in racks:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
for assoc, details in required_assocs.items(): for assoc, details in required_assocs.items():
model, field = details model, field = details
@ -38,15 +38,6 @@ for params in racks:
rack, created = Rack.objects.get_or_create(**params) rack, created = Rack.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(rack, custom_field_data)
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) print("🔳 Created rack", rack.site, rack.name)

View File

@ -1,8 +1,8 @@
from tenancy.models import Tenant, TenantGroup
from extras.models import CustomField, CustomFieldValue
from startup_script_utils import load_yaml
import sys import sys
from startup_script_utils import *
from tenancy.models import Tenant, TenantGroup
tenants = load_yaml('/opt/netbox/initializers/tenants.yml') tenants = load_yaml('/opt/netbox/initializers/tenants.yml')
if tenants is None: if tenants is None:
@ -13,7 +13,7 @@ optional_assocs = {
} }
for params in tenants: for params in tenants:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
for assoc, details in optional_assocs.items(): for assoc, details in optional_assocs.items():
if assoc in params: if assoc in params:
@ -25,15 +25,6 @@ for params in tenants:
tenant, created = Tenant.objects.get_or_create(**params) tenant, created = Tenant.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(tenant, custom_field_data)
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=tenant,
value=cf_value
)
tenant.custom_field_values.add(custom_field_value)
print("👩‍💻 Created Tenant", tenant.name) print("👩‍💻 Created Tenant", tenant.name)

View File

@ -1,10 +1,10 @@
from dcim.models import Site, Rack, DeviceRole, DeviceType, Device, Platform
from virtualization.models import Cluster
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
from startup_script_utils import load_yaml
import sys import sys
from dcim.models import Site, Rack, DeviceRole, DeviceType, Device, Platform
from startup_script_utils import *
from tenancy.models import Tenant
from virtualization.models import Cluster
devices = load_yaml('/opt/netbox/initializers/devices.yml') devices = load_yaml('/opt/netbox/initializers/devices.yml')
if devices is None: if devices is None:
@ -24,7 +24,8 @@ optional_assocs = {
} }
for params in devices: for params in devices:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
# primary ips are handled later in `270_primary_ips.py` # primary ips are handled later in `270_primary_ips.py`
params.pop('primary_ip4', None) params.pop('primary_ip4', None)
params.pop('primary_ip6', None) params.pop('primary_ip6', None)
@ -45,15 +46,6 @@ for params in devices:
device, created = Device.objects.get_or_create(**params) device, created = Device.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(device, custom_field_data)
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) print("🖥️ Created device", device.name)

View File

@ -1,11 +1,9 @@
from ipam.models import Aggregate, RIR
from extras.models import CustomField, CustomFieldValue
from netaddr import IPNetwork
from startup_script_utils import load_yaml
import sys import sys
from ipam.models import Aggregate, RIR
from netaddr import IPNetwork
from startup_script_utils import *
aggregates = load_yaml('/opt/netbox/initializers/aggregates.yml') aggregates = load_yaml('/opt/netbox/initializers/aggregates.yml')
if aggregates is None: if aggregates is None:
@ -16,7 +14,8 @@ required_assocs = {
} }
for params in aggregates: for params in aggregates:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
params['prefix'] = IPNetwork(params['prefix']) params['prefix'] = IPNetwork(params['prefix'])
for assoc, details in required_assocs.items(): for assoc, details in required_assocs.items():
@ -28,15 +27,6 @@ for params in aggregates:
aggregate, created = Aggregate.objects.get_or_create(**params) aggregate, created = Aggregate.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(aggregate, custom_field_data)
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=aggregate,
value=cf_value
)
aggregate.custom_field_values.add(custom_field_value)
print("🗞️ Created Aggregate", aggregate.prefix) print("🗞️ Created Aggregate", aggregate.prefix)

View File

@ -1,9 +1,9 @@
from dcim.models import Site
from virtualization.models import Cluster, ClusterType, ClusterGroup
from extras.models import CustomField, CustomFieldValue
from startup_script_utils import load_yaml
import sys import sys
from dcim.models import Site
from startup_script_utils import *
from virtualization.models import Cluster, ClusterType, ClusterGroup
clusters = load_yaml('/opt/netbox/initializers/clusters.yml') clusters = load_yaml('/opt/netbox/initializers/clusters.yml')
if clusters is None: if clusters is None:
@ -19,7 +19,7 @@ optional_assocs = {
} }
for params in clusters: for params in clusters:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
for assoc, details in required_assocs.items(): for assoc, details in required_assocs.items():
model, field = details model, field = details
@ -37,15 +37,6 @@ for params in clusters:
cluster, created = Cluster.objects.get_or_create(**params) cluster, created = Cluster.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(cluster, custom_field_data)
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=cluster,
value=cf_value
)
cluster.custom_field_values.add(custom_field_value)
print("🗄️ Created cluster", cluster.name) print("🗄️ Created cluster", cluster.name)

View File

@ -1,11 +1,9 @@
from ipam.models import VRF
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
from startup_script_utils import load_yaml
import sys import sys
from ipam.models import VRF
from startup_script_utils import *
from tenancy.models import Tenant
vrfs = load_yaml('/opt/netbox/initializers/vrfs.yml') vrfs = load_yaml('/opt/netbox/initializers/vrfs.yml')
if vrfs is None: if vrfs is None:
@ -16,7 +14,7 @@ optional_assocs = {
} }
for params in vrfs: for params in vrfs:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
for assoc, details in optional_assocs.items(): for assoc, details in optional_assocs.items():
if assoc in params: if assoc in params:
@ -28,15 +26,6 @@ for params in vrfs:
vrf, created = VRF.objects.get_or_create(**params) vrf, created = VRF.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(vrf, custom_field_data)
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=vrf,
value=cf_value
)
vrf.custom_field_values.add(custom_field_value)
print("📦 Created VRF", vrf.name) print("📦 Created VRF", vrf.name)

View File

@ -1,8 +1,8 @@
import sys
from dcim.models import Site from dcim.models import Site
from ipam.models import VLANGroup from ipam.models import VLANGroup
from extras.models import CustomField, CustomFieldValue from startup_script_utils import *
from startup_script_utils import load_yaml
import sys
vlan_groups = load_yaml('/opt/netbox/initializers/vlan_groups.yml') vlan_groups = load_yaml('/opt/netbox/initializers/vlan_groups.yml')
@ -14,7 +14,7 @@ optional_assocs = {
} }
for params in vlan_groups: for params in vlan_groups:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
for assoc, details in optional_assocs.items(): for assoc, details in optional_assocs.items():
if assoc in params: if assoc in params:
@ -26,15 +26,6 @@ for params in vlan_groups:
vlan_group, created = VLANGroup.objects.get_or_create(**params) vlan_group, created = VLANGroup.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(vlan_group, custom_field_data)
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=vlan_group,
value=cf_value
)
vlan_group.custom_field_values.add(custom_field_value)
print("🏘️ Created VLAN Group", vlan_group.name) print("🏘️ Created VLAN Group", vlan_group.name)

View File

@ -1,9 +1,9 @@
import sys
from dcim.models import Site from dcim.models import Site
from ipam.models import VLAN, VLANGroup, Role from ipam.models import VLAN, VLANGroup, Role
from startup_script_utils import *
from tenancy.models import Tenant, TenantGroup from tenancy.models import Tenant, TenantGroup
from extras.models import CustomField, CustomFieldValue
from startup_script_utils import load_yaml
import sys
vlans = load_yaml('/opt/netbox/initializers/vlans.yml') vlans = load_yaml('/opt/netbox/initializers/vlans.yml')
@ -19,7 +19,7 @@ optional_assocs = {
} }
for params in vlans: for params in vlans:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
for assoc, details in optional_assocs.items(): for assoc, details in optional_assocs.items():
if assoc in params: if assoc in params:
@ -31,15 +31,6 @@ for params in vlans:
vlan, created = VLAN.objects.get_or_create(**params) vlan, created = VLAN.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(vlan, custom_field_data)
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=vlan,
value=cf_value
)
vlan.custom_field_values.add(custom_field_value)
print("🏠 Created VLAN", vlan.name) print("🏠 Created VLAN", vlan.name)

View File

@ -1,10 +1,10 @@
import sys
from dcim.models import Site from dcim.models import Site
from ipam.models import Prefix, VLAN, Role, VRF from ipam.models import Prefix, VLAN, Role, VRF
from tenancy.models import Tenant, TenantGroup
from extras.models import CustomField, CustomFieldValue
from netaddr import IPNetwork from netaddr import IPNetwork
from startup_script_utils import load_yaml from startup_script_utils import *
import sys from tenancy.models import Tenant, TenantGroup
prefixes = load_yaml('/opt/netbox/initializers/prefixes.yml') prefixes = load_yaml('/opt/netbox/initializers/prefixes.yml')
@ -21,7 +21,8 @@ optional_assocs = {
} }
for params in prefixes: for params in prefixes:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
params['prefix'] = IPNetwork(params['prefix']) params['prefix'] = IPNetwork(params['prefix'])
for assoc, details in optional_assocs.items(): for assoc, details in optional_assocs.items():
@ -33,14 +34,6 @@ for params in prefixes:
prefix, created = Prefix.objects.get_or_create(**params) prefix, created = Prefix.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(prefix, custom_field_data)
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=prefix,
value=cf_value
)
prefix.custom_field_values.add(custom_field_value)
print("📌 Created Prefix", prefix.prefix) print("📌 Created Prefix", prefix.prefix)

View File

@ -1,10 +1,10 @@
from dcim.models import Site, Platform, DeviceRole
from virtualization.models import Cluster, VirtualMachine
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
from startup_script_utils import load_yaml
import sys import sys
from dcim.models import Platform, DeviceRole
from startup_script_utils import *
from tenancy.models import Tenant
from virtualization.models import Cluster, VirtualMachine
virtual_machines = load_yaml('/opt/netbox/initializers/virtual_machines.yml') virtual_machines = load_yaml('/opt/netbox/initializers/virtual_machines.yml')
if virtual_machines is None: if virtual_machines is None:
@ -21,7 +21,8 @@ optional_assocs = {
} }
for params in virtual_machines: for params in virtual_machines:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
# primary ips are handled later in `270_primary_ips.py` # primary ips are handled later in `270_primary_ips.py`
params.pop('primary_ip4', None) params.pop('primary_ip4', None)
params.pop('primary_ip6', None) params.pop('primary_ip6', None)
@ -42,15 +43,6 @@ for params in virtual_machines:
virtual_machine, created = VirtualMachine.objects.get_or_create(**params) virtual_machine, created = VirtualMachine.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(virtual_machine, custom_field_data)
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=virtual_machine,
value=cf_value
)
virtual_machine.custom_field_values.add(custom_field_value)
print("🖥️ Created virtual machine", virtual_machine.name) print("🖥️ Created virtual machine", virtual_machine.name)

View File

@ -1,8 +1,8 @@
from virtualization.models import VirtualMachine, VMInterface
from extras.models import CustomField, CustomFieldValue
from startup_script_utils import load_yaml
import sys import sys
from startup_script_utils import *
from virtualization.models import VirtualMachine, VMInterface
interfaces = load_yaml('/opt/netbox/initializers/virtualization_interfaces.yml') interfaces = load_yaml('/opt/netbox/initializers/virtualization_interfaces.yml')
if interfaces is None: if interfaces is None:
@ -13,7 +13,7 @@ required_assocs = {
} }
for params in interfaces: for params in interfaces:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
for assoc, details in required_assocs.items(): for assoc, details in required_assocs.items():
model, field = details model, field = details
@ -24,15 +24,6 @@ for params in interfaces:
interface, created = VMInterface.objects.get_or_create(**params) interface, created = VMInterface.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(interface, custom_field_data)
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=interface,
value=cf_value
)
interface.custom_field_values.add(custom_field_value)
print("🧷 Created interface", interface.name, interface.virtual_machine.name) print("🧷 Created interface", interface.name, interface.virtual_machine.name)

View File

@ -1,9 +1,9 @@
from dcim.models import Interface, Device
from extras.models import CustomField, CustomFieldValue
from startup_script_utils import load_yaml
import sys import sys
interfaces= load_yaml('/opt/netbox/initializers/dcim_interfaces.yml') from dcim.models import Interface, Device
from startup_script_utils import *
interfaces = load_yaml('/opt/netbox/initializers/dcim_interfaces.yml')
if interfaces is None: if interfaces is None:
sys.exit() sys.exit()
@ -13,7 +13,7 @@ required_assocs = {
} }
for params in interfaces: for params in interfaces:
custom_fields = params.pop('custom_fields', None) custom_field_data = pop_custom_fields(params)
for assoc, details in required_assocs.items(): for assoc, details in required_assocs.items():
model, field = details model, field = details
@ -24,15 +24,6 @@ for params in interfaces:
interface, created = Interface.objects.get_or_create(**params) interface, created = Interface.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(interface, custom_field_data)
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=interface,
value=cf_value
)
interface.custom_field_values.add(custom_field_value)
print("🧷 Created interface", interface.name, interface.device.name) print("🧷 Created interface", interface.name, interface.device.name)

View File

@ -3,10 +3,9 @@ import sys
from dcim.models import Device, Interface from dcim.models import Device, Interface
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db.models import Q from django.db.models import Q
from extras.models import CustomField, CustomFieldValue
from ipam.models import VRF, IPAddress from ipam.models import VRF, IPAddress
from netaddr import IPNetwork from netaddr import IPNetwork
from startup_script_utils import load_yaml from startup_script_utils import *
from tenancy.models import Tenant from tenancy.models import Tenant
from virtualization.models import VirtualMachine, VMInterface from virtualization.models import VirtualMachine, VMInterface
@ -25,9 +24,10 @@ vm_interface_ct = ContentType.objects.filter(Q(app_label='virtualization', model
interface_ct = ContentType.objects.filter(Q(app_label='dcim', model='interface')).first() interface_ct = ContentType.objects.filter(Q(app_label='dcim', model='interface')).first()
for params in ip_addresses: for params in ip_addresses:
custom_field_data = pop_custom_fields(params)
vm = params.pop('virtual_machine', None) vm = params.pop('virtual_machine', None)
device = params.pop('device', None) device = params.pop('device', None)
custom_fields = params.pop('custom_fields', None)
params['address'] = IPNetwork(params['address']) params['address'] = IPNetwork(params['address'])
if vm and device: if vm and device:
@ -55,15 +55,6 @@ for params in ip_addresses:
ip_address, created = IPAddress.objects.get_or_create(**params) ip_address, created = IPAddress.objects.get_or_create(**params)
if created: if created:
if custom_fields is not None: set_custom_fields_values(ip_address, custom_field_data)
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=ip_address,
value=cf_value
)
ip_address.custom_field_values.add(custom_field_value)
print("🧬 Created IP Address", ip_address.address) print("🧬 Created IP Address", ip_address.address)

View File

@ -1,2 +1,3 @@
from .load_yaml import load_yaml from .load_yaml import load_yaml
from .permissions import set_permissions from .permissions import set_permissions
from .custom_fields import set_custom_fields_values, pop_custom_fields

View File

@ -0,0 +1,15 @@
def set_custom_fields_values(entity, custom_field_data):
if not custom_field_data:
return
entity.custom_field_data = custom_field_data
return entity.save()
def pop_custom_fields(params):
if 'custom_field_data' in params:
return params.pop('custom_field_data')
elif 'custom_fields' in params:
print("⚠️ Please rename 'custom_fields' to 'custom_field_data'!")
return params.pop('custom_fields')
return None

View File

@ -1,5 +1,6 @@
from ruamel.yaml import YAML
from pathlib import Path from pathlib import Path
from ruamel.yaml import YAML
def load_yaml(yaml_file: str): def load_yaml(yaml_file: str):
yf = Path(yaml_file) yf = Path(yaml_file)

21
test.sh
View File

@ -1,12 +1,29 @@
#!/bin/bash #!/bin/bash
# Runs the original Netbox unit tests and tests whether all initializers work.
# Usage:
# ./test.sh latest
# ./test.sh v2.9.7
# ./test.sh develop-2.10
# IMAGE='netboxcommunity/netbox:latest' ./test.sh
# IMAGE='netboxcommunity/netbox:v2.9.7' ./test.sh
# IMAGE='netboxcommunity/netbox:develop-2.10' ./test.sh
# export IMAGE='netboxcommunity/netbox:latest'; ./test.sh
# export IMAGE='netboxcommunity/netbox:v2.9.7'; ./test.sh
# export IMAGE='netboxcommunity/netbox:develop-2.10'; ./test.sh
# exit when a command exits with an exit code != 0 # exit when a command exits with an exit code != 0
set -e set -e
# version is used by `docker-compose.yml` do determine the tag # IMAGE is used by `docker-compose.yml` do determine the tag
# of the Docker Image that is to be used # of the Docker Image that is to be used
export IMAGE="${IMAGE-netboxcommunity/netbox:latest}" if [ "${1}x" != "x" ]; then
# Use the command line argument
export IMAGE="netboxcommunity/netbox:${1}"
else
export IMAGE="${IMAGE-netboxcommunity/netbox:latest}"
fi
# Ensure that an IMAGE is defined
if [ -z "${IMAGE}" ]; then if [ -z "${IMAGE}" ]; then
echo "⚠️ No image defined" echo "⚠️ No image defined"