From 30a98c500987fe654c1cd96b86b22146e5f04f82 Mon Sep 17 00:00:00 2001 From: Tobias Genannt Date: Mon, 26 Oct 2020 16:25:38 +0100 Subject: [PATCH 01/16] Upgrade Python image to 3.9-alpine Upgrade the default base image to Python 3.9 and start testing on Python 3.10-rc --- .github/workflows/push.yml | 3 +-- build.sh | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 50d17e9..3d2c687 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -19,8 +19,7 @@ jobs: - ./build.sh develop docker_from: - '' # use the default of the build script - - python:3.8-alpine - - python:3.9-alpine + - python:3.10-rc-alpine fail-fast: false runs-on: ubuntu-latest name: Builds new Netbox Docker Images diff --git a/build.sh b/build.sh index 360f08c..19c56c8 100755 --- a/build.sh +++ b/build.sh @@ -49,7 +49,7 @@ if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then echo " DOCKERFILE The name of Dockerfile to use." echo " Default: Dockerfile" 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 " It's currently not possible to pass multiple targets." echo " Default: main ldap" @@ -157,7 +157,7 @@ fi # Determining the value for DOCKER_FROM ### if [ -z "$DOCKER_FROM" ]; then - DOCKER_FROM="python:3.8-alpine" + DOCKER_FROM="python:3.9-alpine" fi ### From bb2ac7bd717a3ee345f3ff4216221a813bb7b9d4 Mon Sep 17 00:00:00 2001 From: Tobias Genannt Date: Mon, 26 Oct 2020 16:47:38 +0100 Subject: [PATCH 02/16] Disable tests for Python 3.10-rc Some of the dependencies of Netbox can't be built with Python 3.10. --- .github/workflows/push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 3d2c687..e1780b7 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -19,7 +19,7 @@ jobs: - ./build.sh develop docker_from: - '' # use the default of the build script - - python:3.10-rc-alpine + # - python:3.10-rc-alpine # disable until dependencies work fail-fast: false runs-on: ubuntu-latest name: Builds new Netbox Docker Images From 60f4e8b2eda4d3f4d3ed8488f2536e27c54c20b4 Mon Sep 17 00:00:00 2001 From: Pablo Ruiz Date: Tue, 25 Aug 2020 22:47:38 +0200 Subject: [PATCH 03/16] Allow disabling LDAP-group related settings when AUTH_LDAP_*_GROUP environment variables are not defined. This is required in order to work with Google's Secure LDAP, due to some limitations on django-auth-ldap plugin (see: https://github.com/django-auth-ldap/django-auth-ldap/issues/201) --- configuration/ldap/ldap_config.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/configuration/ldap/ldap_config.py b/configuration/ldap/ldap_config.py index 4cd5b8b..52d7238 100644 --- a/configuration/ldap/ldap_config.py +++ b/configuration/ldap/ldap_config.py @@ -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')) # Define a group required to login. -AUTH_LDAP_REQUIRE_GROUP = environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', '') +AUTH_LDAP_REQUIRE_GROUP = os.environ.get('AUTH_LDAP_REQUIRE_GROUP_DN') # Define special user types using groups. Exercise great caution when assigning superuser status. -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', '') -} +AUTH_LDAP_USER_FLAGS_BY_GROUP = {} + +if AUTH_LDAP_REQUIRE_GROUP is not None: + AUTH_LDAP_USER_FLAGS_BY_GROUP = { + "is_active": os.environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', ''), + "is_staff": os.environ.get('AUTH_LDAP_IS_ADMIN_DN', ''), + "is_superuser": os.environ.get('AUTH_LDAP_IS_SUPERUSER_DN', '') + } # 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' From dcb3b5495d3d0a437f09f5a5df1e23760cd1c603 Mon Sep 17 00:00:00 2001 From: Pablo Ruiz Date: Thu, 29 Oct 2020 14:57:34 +0100 Subject: [PATCH 04/16] Remove now unneeded os. prefix from environ calls. --- configuration/ldap/ldap_config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/configuration/ldap/ldap_config.py b/configuration/ldap/ldap_config.py index 52d7238..1ed599f 100644 --- a/configuration/ldap/ldap_config.py +++ b/configuration/ldap/ldap_config.py @@ -60,16 +60,16 @@ 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')) # Define a group required to login. -AUTH_LDAP_REQUIRE_GROUP = os.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. AUTH_LDAP_USER_FLAGS_BY_GROUP = {} if AUTH_LDAP_REQUIRE_GROUP is not None: AUTH_LDAP_USER_FLAGS_BY_GROUP = { - "is_active": os.environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', ''), - "is_staff": os.environ.get('AUTH_LDAP_IS_ADMIN_DN', ''), - "is_superuser": os.environ.get('AUTH_LDAP_IS_SUPERUSER_DN', '') + "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. From 255889288ccbe8145cab756f1fc9ca9a150085b5 Mon Sep 17 00:00:00 2001 From: Tim Oliver Rabl Date: Fri, 30 Oct 2020 23:06:42 +0100 Subject: [PATCH 05/16] Rename SESSION_FILE_PATH default environment variable in configration.py --- configuration/configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration/configuration.py b/configuration/configuration.py index 639e1b6..9d2e476 100644 --- a/configuration/configuration.py +++ b/configuration/configuration.py @@ -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 # 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. -SESSION_FILE_PATH = environ.get('REPORTS_ROOT', None) +SESSION_FILE_PATH = environ.get('SESSIONS_ROOT', None) # Time zone (default: UTC) TIME_ZONE = environ.get('TIME_ZONE', 'UTC') From fbfce46ce5742216d334043fd20bf4d9de36d04f Mon Sep 17 00:00:00 2001 From: devon-mar Date: Mon, 2 Nov 2020 22:39:06 -0800 Subject: [PATCH 06/16] Fix TypeError when using LOGIN_TIMEOUT --- configuration/configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration/configuration.py b/configuration/configuration.py index 639e1b6..bd6b8b4 100644 --- a/configuration/configuration.py +++ b/configuration/configuration.py @@ -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 # re-authenticate. (Default: 1209600 [14 days]) -LOGIN_TIMEOUT = environ.get('LOGIN_TIMEOUT', None) +LOGIN_TIMEOUT = int(environ.get('LOGIN_TIMEOUT', None)) # 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' From 4276c941ebd03b91b30f5010873da3795129230a Mon Sep 17 00:00:00 2001 From: devon-mar Date: Tue, 3 Nov 2020 14:57:12 -0800 Subject: [PATCH 07/16] Change LOGIN_TIMEOUT default to int MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Christian MΓ€der --- configuration/configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration/configuration.py b/configuration/configuration.py index bd6b8b4..c49c7e0 100644 --- a/configuration/configuration.py +++ b/configuration/configuration.py @@ -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 # re-authenticate. (Default: 1209600 [14 days]) -LOGIN_TIMEOUT = int(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. MAINTENANCE_MODE = environ.get('MAINTENANCE_MODE', 'False').lower() == 'true' From 036f94a450d8a76eed02980f8a5e43dbd29e481f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=A4der?= Date: Sat, 17 Oct 2020 19:24:35 +0200 Subject: [PATCH 08/16] Simplify test script for manual tests --- test.sh | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/test.sh b/test.sh index 26de625..dc892b1 100755 --- a/test.sh +++ b/test.sh @@ -1,12 +1,29 @@ #!/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 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 -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 echo "⚠️ No image defined" @@ -63,7 +80,7 @@ echo "🐳🐳🐳 Start testing '${IMAGE}'" trap test_cleanup EXIT ERR test_setup -test_netbox_unit_tests +#test_netbox_unit_tests test_initializers echo "🐳🐳🐳 Done testing '${IMAGE}'" From 5c9bea8b50157f3235f0bfdf57fa331d86b72ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=A4der?= Date: Sat, 17 Oct 2020 21:51:38 +0200 Subject: [PATCH 09/16] Update Custom Field logic for Netbox v2.10.x --- initializers/custom_fields.yml | 30 +++++++++++---- initializers/device_types.yml | 8 ++-- initializers/devices.yml | 6 +-- initializers/racks.yml | 6 +-- initializers/sites.yml | 16 ++++---- startup_scripts/020_custom_fields.py | 37 ++++++++++--------- startup_scripts/040_sites.py | 21 +++-------- startup_scripts/060_device_types.py | 21 +++-------- startup_scripts/080_racks.py | 21 +++-------- startup_scripts/120_tenants.py | 19 +++------- startup_scripts/130_devices.py | 24 ++++-------- startup_scripts/160_aggregates.py | 24 ++++-------- startup_scripts/170_clusters.py | 21 +++-------- startup_scripts/180_vrfs.py | 23 +++--------- startup_scripts/200_vlan_groups.py | 19 +++------- startup_scripts/210_vlans.py | 19 +++------- startup_scripts/220_prefixes.py | 21 ++++------- startup_scripts/230_virtual_machines.py | 24 ++++-------- .../240_virtualization_interfaces.py | 19 +++------- startup_scripts/250_dcim_interfaces.py | 21 +++-------- startup_scripts/260_ip_addresses.py | 17 ++------- .../startup_script_utils/__init__.py | 1 + .../startup_script_utils/custom_fields.py | 15 ++++++++ .../startup_script_utils/load_yaml.py | 3 +- 24 files changed, 168 insertions(+), 268 deletions(-) create mode 100644 startup_scripts/startup_script_utils/custom_fields.py diff --git a/initializers/custom_fields.yml b/initializers/custom_fields.yml index 4085ab0..fae780d 100644 --- a/initializers/custom_fields.yml +++ b/initializers/custom_fields.yml @@ -37,7 +37,7 @@ # weight: 10 # on_objects: # - tenancy.models.Tenant -# select_field: +# legacy_select_field: # type: select # label: Choose between items # required: false @@ -56,6 +56,21 @@ # weight: 50 # - value: Fourth Item # weight: 40 +# 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_auto_weight: # type: select # label: Choose between items @@ -65,18 +80,17 @@ # on_objects: # - dcim.models.Device # choices: -# - value: A -# - value: B -# - value: C -# - value: "D like deprecated" -# weight: 999 -# - value: E +# - A +# - B +# - C +# - E +# - D like deprecated # boolean_field: # type: boolean # label: Yes Or No? # required: true # filter_logic: loose -# default: "false" # important: but "false" in quotes! +# default: "false" # important: put "false" in quotes! # weight: 90 # on_objects: # - dcim.models.Device diff --git a/initializers/device_types.yml b/initializers/device_types.yml index 9b27da3..88798b5 100644 --- a/initializers/device_types.yml +++ b/initializers/device_types.yml @@ -2,22 +2,22 @@ # manufacturer: Manufacturer 1 # slug: model-1 # u_height: 2 -# custom_fields: +# custom_field_data: # text_field: Description # - model: Model 2 # manufacturer: Manufacturer 1 # slug: model-2 -# custom_fields: +# custom_field_data: # text_field: Description # - model: Model 3 # manufacturer: Manufacturer 1 # slug: model-3 # is_full_depth: false # u_height: 0 -# custom_fields: +# custom_field_data: # text_field: Description # - model: Other # manufacturer: No Name # slug: other -# custom_fields: +# custom_field_data: # text_field: Description diff --git a/initializers/devices.yml b/initializers/devices.yml index e968503..0de0504 100644 --- a/initializers/devices.yml +++ b/initializers/devices.yml @@ -20,7 +20,7 @@ # rack: rack-01 # face: front # position: 1 -# custom_fields: +# custom_field_data: # text_field: Description # - name: server02 # device_role: server @@ -31,7 +31,7 @@ # position: 2 # primary_ip4: 10.1.1.2/24 # primary_ip6: 2001:db8:a000:1::2/64 -# custom_fields: +# custom_field_data: # text_field: Description # - name: server03 # device_role: server @@ -40,5 +40,5 @@ # rack: rack-03 # face: front # position: 3 -# custom_fields: +# custom_field_data: # text_field: Description diff --git a/initializers/racks.yml b/initializers/racks.yml index 51502de..379553d 100644 --- a/initializers/racks.yml +++ b/initializers/racks.yml @@ -20,7 +20,7 @@ # type: 4-post-cabinet # width: 19 # u_height: 47 -# custom_fields: +# custom_field_data: # text_field: Description # - site: AMS 2 # name: rack-02 @@ -28,7 +28,7 @@ # type: 4-post-cabinet # width: 19 # u_height: 47 -# custom_fields: +# custom_field_data: # text_field: Description # - site: SING 1 # name: rack-03 @@ -37,5 +37,5 @@ # type: 4-post-cabinet # width: 19 # u_height: 47 -# custom_fields: +# custom_field_data: # text_field: Description diff --git a/initializers/sites.yml b/initializers/sites.yml index f3e05ba..0015f4e 100644 --- a/initializers/sites.yml +++ b/initializers/sites.yml @@ -4,29 +4,29 @@ # status: active # facility: Amsterdam 1 # asn: 12345 -# custom_fields: -# text_field: Description +# custom_field_data: +# text_field: Description for AMS1 # - name: AMS 2 # slug: ams2 # region: Downtown # status: active # facility: Amsterdam 2 # asn: 54321 -# custom_fields: -# text_field: Description +# custom_field_data: +# text_field: Description for AMS2 # - name: AMS 3 # slug: ams3 # region: Suburbs # status: active # facility: Amsterdam 3 # asn: 67890 -# custom_fields: -# text_field: Description +# custom_field_data: +# text_field: Description for AMS3 # - name: SING 1 # slug: sing1 # region: Singapore # status: active # facility: Singapore 1 # asn: 09876 -# custom_fields: -# text_field: Description +# custom_field_data: +# text_field: Description for SING1 diff --git a/startup_scripts/020_custom_fields.py b/startup_scripts/020_custom_fields.py index 2cb48a0..3b2f1c1 100644 --- a/startup_scripts/020_custom_fields.py +++ b/startup_scripts/020_custom_fields.py @@ -1,8 +1,8 @@ -from extras.models import CustomField, CustomFieldChoice - -from startup_script_utils import load_yaml 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 @@ -21,34 +21,37 @@ 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', 0): + if cf_details.get('default', False): custom_field.default = cf_details['default'] - if cf_details.get('description', 0): + if cf_details.get('description', False): custom_field.description = cf_details['description'] - if cf_details.get('label', 0): + if cf_details.get('label', False): custom_field.label = cf_details['label'] 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'] - if cf_details.get('type', 0): + if cf_details.get('type', False): 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'] + if cf_details.get('choices', False): + custom_field.choices = [] + + for _, choice_detail in enumerate(cf_details.get('choices', [])): + if isinstance(choice_detail, str): + custom_field.choices.append(choice_detail) + else: # legacy mode + print(f"⚠️ Please migrate the 'choices' of '{cf_name}' to the new format, as 'weight' is no longer supported!") + custom_field.choices.append(choice_detail['value']) + 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) diff --git a/startup_scripts/040_sites.py b/startup_scripts/040_sites.py index 828a86b..a4f7e71 100644 --- a/startup_scripts/040_sites.py +++ b/startup_scripts/040_sites.py @@ -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 +from dcim.models import Region, Site +from startup_script_utils import * +from tenancy.models import Tenant + sites = load_yaml('/opt/netbox/initializers/sites.yml') if sites is None: @@ -15,7 +15,7 @@ optional_assocs = { } 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(): if assoc in params: @@ -27,15 +27,6 @@ for params in sites: site, created = Site.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=site, - value=cf_value - ) - - site.custom_field_values.add(custom_field_value) + set_custom_fields_values(site, custom_field_data) print("πŸ“ Created site", site.name) diff --git a/startup_scripts/060_device_types.py b/startup_scripts/060_device_types.py index e6cea93..e0695b1 100644 --- a/startup_scripts/060_device_types.py +++ b/startup_scripts/060_device_types.py @@ -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 +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') if device_types is None: @@ -19,7 +19,7 @@ optional_assocs = { } 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(): model, field = details @@ -37,15 +37,6 @@ for params in device_types: device_type, created = DeviceType.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=device_type, - value=cf_value - ) - - device_type.custom_field_values.add(custom_field_value) + set_custom_fields_values(device_type, custom_field_data) print("πŸ”‘ Created device type", device_type.manufacturer, device_type.model) diff --git a/startup_scripts/080_racks.py b/startup_scripts/080_racks.py index 279cb2c..4504624 100644 --- a/startup_scripts/080_racks.py +++ b/startup_scripts/080_racks.py @@ -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 +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') if racks is None: @@ -20,7 +20,7 @@ optional_assocs = { } 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(): model, field = details @@ -38,15 +38,6 @@ for params in racks: rack, created = Rack.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=rack, - value=cf_value - ) - - rack.custom_field_values.add(custom_field_value) + set_custom_fields_values(rack, custom_field_data) print("πŸ”³ Created rack", rack.site, rack.name) diff --git a/startup_scripts/120_tenants.py b/startup_scripts/120_tenants.py index 121c83a..3b3b221 100644 --- a/startup_scripts/120_tenants.py +++ b/startup_scripts/120_tenants.py @@ -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 +from startup_script_utils import * +from tenancy.models import Tenant, TenantGroup + tenants = load_yaml('/opt/netbox/initializers/tenants.yml') if tenants is None: @@ -13,7 +13,7 @@ optional_assocs = { } 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(): if assoc in params: @@ -25,15 +25,6 @@ for params in tenants: tenant, created = Tenant.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=tenant, - value=cf_value - ) - - tenant.custom_field_values.add(custom_field_value) + set_custom_fields_values(tenant, custom_field_data) print("πŸ‘©β€πŸ’» Created Tenant", tenant.name) diff --git a/startup_scripts/130_devices.py b/startup_scripts/130_devices.py index 7233dd0..d73fd07 100644 --- a/startup_scripts/130_devices.py +++ b/startup_scripts/130_devices.py @@ -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 +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') if devices is None: @@ -24,7 +24,8 @@ optional_assocs = { } 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` params.pop('primary_ip4', None) params.pop('primary_ip6', None) @@ -45,15 +46,6 @@ for params in devices: device, created = Device.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=device, - value=cf_value - ) - - device.custom_field_values.add(custom_field_value) + set_custom_fields_values(device, custom_field_data) print("πŸ–₯️ Created device", device.name) diff --git a/startup_scripts/160_aggregates.py b/startup_scripts/160_aggregates.py index 0ffe9b0..cc1d220 100644 --- a/startup_scripts/160_aggregates.py +++ b/startup_scripts/160_aggregates.py @@ -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 +from ipam.models import Aggregate, RIR +from netaddr import IPNetwork +from startup_script_utils import * + aggregates = load_yaml('/opt/netbox/initializers/aggregates.yml') if aggregates is None: @@ -16,7 +14,8 @@ required_assocs = { } for params in aggregates: - custom_fields = params.pop('custom_fields', None) + custom_field_data = pop_custom_fields(params) + params['prefix'] = IPNetwork(params['prefix']) for assoc, details in required_assocs.items(): @@ -28,15 +27,6 @@ for params in aggregates: aggregate, created = Aggregate.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=aggregate, - value=cf_value - ) - - aggregate.custom_field_values.add(custom_field_value) + set_custom_fields_values(aggregate, custom_field_data) print("πŸ—žοΈ Created Aggregate", aggregate.prefix) diff --git a/startup_scripts/170_clusters.py b/startup_scripts/170_clusters.py index a7e2065..ffd965e 100644 --- a/startup_scripts/170_clusters.py +++ b/startup_scripts/170_clusters.py @@ -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 +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') if clusters is None: @@ -19,7 +19,7 @@ optional_assocs = { } 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(): model, field = details @@ -37,15 +37,6 @@ for params in clusters: cluster, created = Cluster.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=cluster, - value=cf_value - ) - - cluster.custom_field_values.add(custom_field_value) + set_custom_fields_values(cluster, custom_field_data) print("πŸ—„οΈ Created cluster", cluster.name) diff --git a/startup_scripts/180_vrfs.py b/startup_scripts/180_vrfs.py index 496710d..2f22316 100644 --- a/startup_scripts/180_vrfs.py +++ b/startup_scripts/180_vrfs.py @@ -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 +from ipam.models import VRF +from startup_script_utils import * +from tenancy.models import Tenant + vrfs = load_yaml('/opt/netbox/initializers/vrfs.yml') if vrfs is None: @@ -16,7 +14,7 @@ optional_assocs = { } 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(): if assoc in params: @@ -28,15 +26,6 @@ for params in vrfs: vrf, created = VRF.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=vrf, - value=cf_value - ) - - vrf.custom_field_values.add(custom_field_value) + set_custom_fields_values(vrf, custom_field_data) print("πŸ“¦ Created VRF", vrf.name) diff --git a/startup_scripts/200_vlan_groups.py b/startup_scripts/200_vlan_groups.py index f8dc55d..35c3616 100644 --- a/startup_scripts/200_vlan_groups.py +++ b/startup_scripts/200_vlan_groups.py @@ -1,8 +1,8 @@ +import sys + from dcim.models import Site from ipam.models import VLANGroup -from extras.models import CustomField, CustomFieldValue -from startup_script_utils import load_yaml -import sys +from startup_script_utils import * vlan_groups = load_yaml('/opt/netbox/initializers/vlan_groups.yml') @@ -14,7 +14,7 @@ optional_assocs = { } 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(): if assoc in params: @@ -26,15 +26,6 @@ for params in vlan_groups: vlan_group, created = VLANGroup.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=vlan_group, - value=cf_value - ) - - vlan_group.custom_field_values.add(custom_field_value) + set_custom_fields_values(vlan_group, custom_field_data) print("🏘️ Created VLAN Group", vlan_group.name) diff --git a/startup_scripts/210_vlans.py b/startup_scripts/210_vlans.py index ceab196..7848fc1 100644 --- a/startup_scripts/210_vlans.py +++ b/startup_scripts/210_vlans.py @@ -1,9 +1,9 @@ +import sys + from dcim.models import Site from ipam.models import VLAN, VLANGroup, Role +from startup_script_utils import * 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') @@ -19,7 +19,7 @@ optional_assocs = { } 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(): if assoc in params: @@ -31,15 +31,6 @@ for params in vlans: vlan, created = VLAN.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=vlan, - value=cf_value - ) - - vlan.custom_field_values.add(custom_field_value) + set_custom_fields_values(vlan, custom_field_data) print("🏠 Created VLAN", vlan.name) diff --git a/startup_scripts/220_prefixes.py b/startup_scripts/220_prefixes.py index b047c8c..9175521 100644 --- a/startup_scripts/220_prefixes.py +++ b/startup_scripts/220_prefixes.py @@ -1,10 +1,10 @@ +import sys + from dcim.models import Site 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 startup_script_utils import load_yaml -import sys +from startup_script_utils import * +from tenancy.models import Tenant, TenantGroup prefixes = load_yaml('/opt/netbox/initializers/prefixes.yml') @@ -21,7 +21,8 @@ optional_assocs = { } for params in prefixes: - custom_fields = params.pop('custom_fields', None) + custom_field_data = pop_custom_fields(params) + params['prefix'] = IPNetwork(params['prefix']) for assoc, details in optional_assocs.items(): @@ -33,14 +34,6 @@ for params in prefixes: prefix, created = Prefix.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=prefix, - value=cf_value - ) - prefix.custom_field_values.add(custom_field_value) + set_custom_fields_values(prefix, custom_field_data) print("πŸ“Œ Created Prefix", prefix.prefix) diff --git a/startup_scripts/230_virtual_machines.py b/startup_scripts/230_virtual_machines.py index f138886..328d3ea 100644 --- a/startup_scripts/230_virtual_machines.py +++ b/startup_scripts/230_virtual_machines.py @@ -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 +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') if virtual_machines is None: @@ -21,7 +21,8 @@ optional_assocs = { } 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` params.pop('primary_ip4', None) params.pop('primary_ip6', None) @@ -42,15 +43,6 @@ for params in virtual_machines: virtual_machine, created = VirtualMachine.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=virtual_machine, - value=cf_value - ) - - virtual_machine.custom_field_values.add(custom_field_value) + set_custom_fields_values(virtual_machine, custom_field_data) print("πŸ–₯️ Created virtual machine", virtual_machine.name) diff --git a/startup_scripts/240_virtualization_interfaces.py b/startup_scripts/240_virtualization_interfaces.py index f04f30b..c6f8e50 100644 --- a/startup_scripts/240_virtualization_interfaces.py +++ b/startup_scripts/240_virtualization_interfaces.py @@ -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 +from startup_script_utils import * +from virtualization.models import VirtualMachine, VMInterface + interfaces = load_yaml('/opt/netbox/initializers/virtualization_interfaces.yml') if interfaces is None: @@ -13,7 +13,7 @@ required_assocs = { } 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(): model, field = details @@ -24,15 +24,6 @@ for params in interfaces: interface, created = VMInterface.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=interface, - value=cf_value - ) - - interface.custom_field_values.add(custom_field_value) + set_custom_fields_values(interface, custom_field_data) print("🧷 Created interface", interface.name, interface.virtual_machine.name) diff --git a/startup_scripts/250_dcim_interfaces.py b/startup_scripts/250_dcim_interfaces.py index 51f885b..3b7067d 100644 --- a/startup_scripts/250_dcim_interfaces.py +++ b/startup_scripts/250_dcim_interfaces.py @@ -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 -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: sys.exit() @@ -13,7 +13,7 @@ required_assocs = { } 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(): model, field = details @@ -24,15 +24,6 @@ for params in interfaces: interface, created = Interface.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=interface, - value=cf_value - ) - - interface.custom_field_values.add(custom_field_value) + set_custom_fields_values(interface, custom_field_data) print("🧷 Created interface", interface.name, interface.device.name) diff --git a/startup_scripts/260_ip_addresses.py b/startup_scripts/260_ip_addresses.py index 7d164fd..a0582a2 100644 --- a/startup_scripts/260_ip_addresses.py +++ b/startup_scripts/260_ip_addresses.py @@ -3,10 +3,9 @@ import sys from dcim.models import Device, Interface from django.contrib.contenttypes.models import ContentType from django.db.models import Q -from extras.models import CustomField, CustomFieldValue from ipam.models import VRF, IPAddress from netaddr import IPNetwork -from startup_script_utils import load_yaml +from startup_script_utils import * from tenancy.models import Tenant 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() for params in ip_addresses: + custom_field_data = pop_custom_fields(params) + vm = params.pop('virtual_machine', None) device = params.pop('device', None) - custom_fields = params.pop('custom_fields', None) params['address'] = IPNetwork(params['address']) if vm and device: @@ -55,15 +55,6 @@ for params in ip_addresses: ip_address, created = IPAddress.objects.get_or_create(**params) if created: - if custom_fields is not None: - for cf_name, cf_value in custom_fields.items(): - custom_field = CustomField.objects.get(name=cf_name) - custom_field_value = CustomFieldValue.objects.create( - field=custom_field, - obj=ip_address, - value=cf_value - ) - - ip_address.custom_field_values.add(custom_field_value) + set_custom_fields_values(ip_address, custom_field_data) print("🧬 Created IP Address", ip_address.address) diff --git a/startup_scripts/startup_script_utils/__init__.py b/startup_scripts/startup_script_utils/__init__.py index c3cf28f..1e9f042 100644 --- a/startup_scripts/startup_script_utils/__init__.py +++ b/startup_scripts/startup_script_utils/__init__.py @@ -1,2 +1,3 @@ from .load_yaml import load_yaml from .permissions import set_permissions +from .custom_fields import set_custom_fields_values, pop_custom_fields diff --git a/startup_scripts/startup_script_utils/custom_fields.py b/startup_scripts/startup_script_utils/custom_fields.py new file mode 100644 index 0000000..1be7a94 --- /dev/null +++ b/startup_scripts/startup_script_utils/custom_fields.py @@ -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 diff --git a/startup_scripts/startup_script_utils/load_yaml.py b/startup_scripts/startup_script_utils/load_yaml.py index 4c16816..ecae7af 100644 --- a/startup_scripts/startup_script_utils/load_yaml.py +++ b/startup_scripts/startup_script_utils/load_yaml.py @@ -1,5 +1,6 @@ -from ruamel.yaml import YAML from pathlib import Path +from ruamel.yaml import YAML + def load_yaml(yaml_file: str): yf = Path(yaml_file) From 349e269356866f651311b35ae95bf400461ac17c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=A4der?= Date: Sat, 17 Oct 2020 22:00:22 +0200 Subject: [PATCH 10/16] Remove the legacy select_field from the samples This was only used for testing. --- initializers/custom_fields.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/initializers/custom_fields.yml b/initializers/custom_fields.yml index fae780d..98111fc 100644 --- a/initializers/custom_fields.yml +++ b/initializers/custom_fields.yml @@ -37,25 +37,6 @@ # weight: 10 # on_objects: # - tenancy.models.Tenant -# legacy_select_field: -# type: select -# label: Choose between items -# required: false -# filter_logic: exact -# weight: 30 -# on_objects: -# - dcim.models.Device -# choices: -# - value: First Item -# weight: 10 -# - value: Second Item -# weight: 20 -# - value: Third Item -# weight: 30 -# - value: Fifth Item -# weight: 50 -# - value: Fourth Item -# weight: 40 # select_field: # type: select # label: Choose between items From 77d3dcded020ddd9ea33a9059f1edc1a18062515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=A4der?= Date: Sat, 17 Oct 2020 22:10:33 +0200 Subject: [PATCH 11/16] Fix leftover from testing in test.sh --- test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.sh b/test.sh index dc892b1..fa6fa38 100755 --- a/test.sh +++ b/test.sh @@ -80,7 +80,7 @@ echo "🐳🐳🐳 Start testing '${IMAGE}'" trap test_cleanup EXIT ERR test_setup -#test_netbox_unit_tests +test_netbox_unit_tests test_initializers echo "🐳🐳🐳 Done testing '${IMAGE}'" From 234baa40a5191e0865d52c7ca2a11aeeed0c2ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=A4der?= Date: Sun, 18 Oct 2020 03:01:57 +0200 Subject: [PATCH 12/16] Remove redundant assignment in for loop --- startup_scripts/020_custom_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/startup_scripts/020_custom_fields.py b/startup_scripts/020_custom_fields.py index 3b2f1c1..da93a92 100644 --- a/startup_scripts/020_custom_fields.py +++ b/startup_scripts/020_custom_fields.py @@ -45,7 +45,7 @@ for cf_name, cf_details in customfields.items(): if cf_details.get('choices', False): custom_field.choices = [] - for _, choice_detail in enumerate(cf_details.get('choices', [])): + for choice_detail in enumerate(cf_details.get('choices', [])): if isinstance(choice_detail, str): custom_field.choices.append(choice_detail) else: # legacy mode From e383fd42bdf9826979e3d3a17887ef252cd9f7d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ma=CC=88der?= Date: Mon, 14 Dec 2020 22:09:08 +0100 Subject: [PATCH 13/16] Fix custom fields initializer --- initializers/custom_fields.yml | 13 +++++++------ startup_scripts/000_users.py | 2 +- startup_scripts/020_custom_fields.py | 11 ++++++----- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/initializers/custom_fields.yml b/initializers/custom_fields.yml index 98111fc..654fb69 100644 --- a/initializers/custom_fields.yml +++ b/initializers/custom_fields.yml @@ -52,7 +52,7 @@ # - Third Item # - Fifth Item # - Fourth Item -# select_field_auto_weight: +# select_field_legacy_format: # type: select # label: Choose between items # required: false @@ -61,11 +61,12 @@ # on_objects: # - dcim.models.Device # choices: -# - A -# - B -# - C -# - E -# - D like deprecated +# - 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? diff --git a/startup_scripts/000_users.py b/startup_scripts/000_users.py index a801d85..ffd4bec 100644 --- a/startup_scripts/000_users.py +++ b/startup_scripts/000_users.py @@ -1,6 +1,6 @@ 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 users.models import Token diff --git a/startup_scripts/020_custom_fields.py b/startup_scripts/020_custom_fields.py index da93a92..7479a5e 100644 --- a/startup_scripts/020_custom_fields.py +++ b/startup_scripts/020_custom_fields.py @@ -45,12 +45,13 @@ for cf_name, cf_details in customfields.items(): if cf_details.get('choices', False): custom_field.choices = [] - for choice_detail in enumerate(cf_details.get('choices', [])): - if isinstance(choice_detail, str): - custom_field.choices.append(choice_detail) - else: # legacy mode - print(f"⚠️ Please migrate the 'choices' of '{cf_name}' to the new format, as 'weight' is no longer supported!") + 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() From b92c652d99e2ad6e9db2fe24350f1562714f4109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ma=CC=88der?= Date: Tue, 15 Dec 2020 00:28:43 +0100 Subject: [PATCH 14/16] Add quay.io as fallback registry --- .github/workflows/release.yml | 31 +++++++++++++++++++++++++++++++ README.md | 7 ++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9dbf770..32cc9ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -50,3 +50,34 @@ jobs: name: Logout of the Docker Registry run: docker logout "${DOCKER_REGISTRY}" 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' diff --git a/README.md b/README.md index 7c4d20d..b7643ca 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![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. -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? 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-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/tags/ +[netbox-dockerhub]: https://hub.docker.com/r/netboxcommunity/netbox/ [netbox-docker-github]: https://github.com/netbox-community/netbox-docker/ [ntc-slack]: http://slack.networktocode.com/ [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-quayio]: https://quay.io/repository/netboxcommunity/netbox ## Docker Tags @@ -104,7 +105,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`. 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 -[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 export VERSION=v2.7.1 From 5605b9b21840a6f4f2873cfd6bb11aeb5eaa16ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ma=CC=88der?= Date: Tue, 15 Dec 2020 00:50:02 +0100 Subject: [PATCH 15/16] Introduce our Github Community --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7c4d20d..362c48f 100644 --- a/README.md +++ b/README.md @@ -85,11 +85,13 @@ It covers advanced topics such as using files for secrets, deployment to Kuberne ## Getting Help -Please 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. +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], +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. +[netbox-community]: https://github.com/netbox-community/netbox-docker/discussions + ## Dependencies This project relies only on *Docker* and *docker-compose* meeting these requirements: From 4e8588accfd655da18c6f00a6722d74b5f15be6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ma=CC=88der?= Date: Tue, 15 Dec 2020 08:59:42 +0100 Subject: [PATCH 16/16] Preparation for 0.27.0 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 894542a..1b58cc1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.26.2 +0.27.0