Merge upstream v2.7.8 into develop

This commit is contained in:
Marco Ceppi
2020-02-27 07:18:06 -05:00
1195 changed files with 107886 additions and 16972 deletions

View File

@@ -37,6 +37,10 @@ DATABASE = {
# PostgreSQL password
'HOST': os.environ.get('DB_HOST', 'localhost'), # Database server
'PORT': os.environ.get('DB_PORT', ''), # Database port (leave blank for default)
'OPTIONS': {'sslmode': os.environ.get('DB_SSLMODE', 'prefer')},
# Database connection SSLMODE
'CONN_MAX_AGE': int(os.environ.get('DB_CONN_MAX_AGE', '300')),
# Database connection persistence
}
# This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file.
@@ -47,13 +51,22 @@ SECRET_KEY = os.environ.get('SECRET_KEY', read_secret('secret_key'))
# Redis database settings. The Redis database is used for caching and background processing such as webhooks
REDIS = {
'HOST': os.environ.get('REDIS_HOST', 'localhost'),
'PORT': int(os.environ.get('REDIS_PORT', 6379)),
'PASSWORD': os.environ.get('REDIS_PASSWORD', read_secret('redis_password')),
'DATABASE': os.environ.get('REDIS_DATABASE', '0'),
'CACHE_DATABASE': os.environ.get('REDIS_CACHE_DATABASE', '1'),
'DEFAULT_TIMEOUT': os.environ.get('REDIS_TIMEOUT', '300'),
'SSL': os.environ.get('REDIS_SSL', 'False').lower() == 'true',
'webhooks': {
'HOST': os.environ.get('REDIS_HOST', 'localhost'),
'PORT': int(os.environ.get('REDIS_PORT', 6379)),
'PASSWORD': os.environ.get('REDIS_PASSWORD', read_secret('redis_password')),
'DATABASE': int(os.environ.get('REDIS_DATABASE', 0)),
'DEFAULT_TIMEOUT': int(os.environ.get('REDIS_TIMEOUT', 300)),
'SSL': os.environ.get('REDIS_SSL', 'False').lower() == 'true',
},
'caching': {
'HOST': os.environ.get('REDIS_CACHE_HOST', os.environ.get('REDIS_HOST', 'localhost')),
'PORT': int(os.environ.get('REDIS_CACHE_PORT', os.environ.get('REDIS_PORT', 6379))),
'PASSWORD': os.environ.get('REDIS_CACHE_PASSWORD', os.environ.get('REDIS_PASSWORD', read_secret('redis_cache_password'))),
'DATABASE': int(os.environ.get('REDIS_CACHE_DATABASE', 1)),
'DEFAULT_TIMEOUT': int(os.environ.get('REDIS_CACHE_TIMEOUT', os.environ.get('REDIS_TIMEOUT', 300))),
'SSL': os.environ.get('REDIS_CACHE_SSL', os.environ.get('REDIS_SSL', 'False')).lower() == 'true',
},
}
#########################
@@ -161,13 +174,13 @@ PREFER_IPV4 = os.environ.get('PREFER_IPV4', 'False').lower() == 'true'
# this setting is derived from the installed location.
REPORTS_ROOT = os.environ.get('REPORTS_ROOT', '/etc/netbox/reports')
# The file path where custom scripts will be stored. A trailing slash is not needed. Note that the default value of
# this setting is derived from the installed location.
SCRIPTS_ROOT = os.environ.get('SCRIPTS_ROOT', '/etc/netbox/scripts')
# Time zone (default: UTC)
TIME_ZONE = os.environ.get('TIME_ZONE', 'UTC')
# The Webhook event backend is disabled by default. Set this to True to enable it. Note that this requires a Redis
# database be configured and accessible by NetBox (see `REDIS` below).
WEBHOOKS_ENABLED = os.environ.get('WEBHOOKS_ENABLED', 'False').lower() == 'true'
# Date/time formatting. See the following link for supported formats:
# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
DATE_FORMAT = os.environ.get('DATE_FORMAT', 'N j, Y')

View File

@@ -70,8 +70,7 @@ AUTH_LDAP_USER_FLAGS_BY_GROUP = {
AUTH_LDAP_FIND_GROUP_PERMS = os.environ.get('AUTH_LDAP_FIND_GROUP_PERMS', 'True').lower() == 'true'
# Cache groups for one hour to reduce LDAP traffic
AUTH_LDAP_CACHE_GROUPS = os.environ.get('AUTH_LDAP_CACHE_GROUPS', 'True').lower() == 'true'
AUTH_LDAP_GROUP_CACHE_TIMEOUT = int(os.environ.get('AUTH_LDAP_GROUP_CACHE_TIMEOUT', 3600))
AUTH_LDAP_CACHE_TIMEOUT = int(os.environ.get('AUTH_LDAP_CACHE_TIMEOUT', 3600))
# Populate the Django user from the LDAP directory.
AUTH_LDAP_USER_ATTR_MAP = {

30
docker/env/netbox.env vendored Normal file
View File

@@ -0,0 +1,30 @@
CORS_ORIGIN_ALLOW_ALL=True
DB_NAME=netbox
DB_USER=postgres
DB_PASSWORD=12345
DB_HOST=postgres
EMAIL_SERVER=localhost
EMAIL_PORT=25
EMAIL_USERNAME=netbox
EMAIL_PASSWORD=
EMAIL_TIMEOUT=5
EMAIL_FROM=netbox@bar.com
MEDIA_ROOT=/opt/netbox/netbox/media
NAPALM_USERNAME=
NAPALM_PASSWORD=
NAPALM_TIMEOUT=10
MAX_PAGE_SIZE=1000
REDIS_HOST=redis
REDIS_DATABASE=0
REDIS_SSL=false
REDIS_CACHE_HOST=redis-cache
REDIS_CACHE_DATABASE=0
REDIS_CACHE_SSL=false
SECRET_KEY=r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj
SKIP_STARTUP_SCRIPTS=false
SKIP_SUPERUSER=false
SUPERUSER_NAME=admin
SUPERUSER_EMAIL=admin@example.com
SUPERUSER_PASSWORD=admin
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
WEBHOOKS_ENABLED=true

View File

@@ -1,3 +1,4 @@
daemon off;
worker_processes 1;
error_log /dev/stderr info;
@@ -17,7 +18,7 @@ http {
client_max_body_size 10M;
server {
listen 80;
listen 8080;
access_log off;
location /static/ {

View File

@@ -1,19 +1,9 @@
from extras.constants import CF_TYPE_TEXT, CF_TYPE_INTEGER, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_URL, CF_TYPE_SELECT, CF_FILTER_CHOICES
from extras.models import CustomField, CustomFieldChoice
from ruamel.yaml import YAML
from pathlib import Path
import sys
text_to_fields = {
'boolean': CF_TYPE_BOOLEAN,
'date': CF_TYPE_DATE,
'integer': CF_TYPE_INTEGER,
'selection': CF_TYPE_SELECT,
'text': CF_TYPE_TEXT,
'url': CF_TYPE_URL,
}
def get_class_for_class_path(class_path):
import importlib
from django.contrib.contenttypes.models import ContentType
@@ -42,12 +32,6 @@ with file.open('r') as stream:
if cf_details.get('description', 0):
custom_field.description = cf_details['description']
# If no filter_logic is specified then it will default to 'Loose'
if cf_details.get('filter_logic', 0):
for choice_id, choice_text in CF_FILTER_CHOICES:
if choice_text.lower() == cf_details['filter_logic']:
custom_field.filter_logic = choice_id
if cf_details.get('label', 0):
custom_field.label = cf_details['label']
@@ -58,7 +42,7 @@ with file.open('r') as stream:
custom_field.required = cf_details['required']
if cf_details.get('type', 0):
custom_field.type = text_to_fields[cf_details['type']]
custom_field.type = cf_details['type']
if cf_details.get('weight', 0):
custom_field.weight = cf_details['weight']

View File

@@ -1,7 +1,6 @@
from dcim.models import Site, RackRole, Rack, RackGroup
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
from dcim.constants import RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES
from ruamel.yaml import YAML
from pathlib import Path
import sys
@@ -41,14 +40,6 @@ with file.open('r') as stream:
params[assoc] = model.objects.get(**query)
for rack_type in RACK_TYPE_CHOICES:
if params['type'] in rack_type:
params['type'] = rack_type[0]
for rack_width in RACK_WIDTH_CHOICES:
if params['width'] in rack_width:
params['width'] = rack_width[0]
rack, created = Rack.objects.get_or_create(**params)
if created:

View File

@@ -1,5 +1,4 @@
from dcim.models import Site, Rack, DeviceRole, DeviceType, Device, Platform
from dcim.constants import RACK_FACE_CHOICES
from ipam.models import IPAddress
from virtualization.models import Cluster
from tenancy.models import Tenant
@@ -26,6 +25,7 @@ with file.open('r') as stream:
optional_assocs = {
'tenant': (Tenant, 'name'),
'platform': (Platform, 'name'),
'rack': (Rack, 'name'),
'cluster': (Cluster, 'name'),
'primary_ip4': (IPAddress, 'address'),
'primary_ip6': (IPAddress, 'address')
@@ -48,14 +48,6 @@ with file.open('r') as stream:
params[assoc] = model.objects.get(**query)
if 'rack' in params:
params['rack'] = Rack.objects.get(name=params.pop('rack'), site=params['site'].id)
if 'face' in params:
for rack_face in RACK_FACE_CHOICES:
if params['face'] in rack_face:
params['face'] = rack_face[0]
device, created = Device.objects.get_or_create(**params)
if created:

View File

@@ -1,5 +1,4 @@
from dcim.models import Interface, Device, DeviceRole
from dcim.constants import IFACE_TYPE_KEYSTONE
from ruamel.yaml import YAML
from pathlib import Path
@@ -31,6 +30,6 @@ with file.open('r') as stream:
i += 2
interface, created = Interface.objects.get_or_create(name=name, device=locker, type=IFACE_TYPE_KEYSTONE)
interface, created = Interface.objects.get_or_create(name=name, device=locker, type='keystone')
if created:
print("🔗 Created interface {} for {}".format(interface.name, locker.name))

View File

@@ -0,0 +1,51 @@
from dcim.models import Site
from ipam.models import VLAN, VLANGroup, Role
from tenancy.models import Tenant, TenantGroup
from extras.models import CustomField, CustomFieldValue
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/vlans.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
vlans = yaml.load(stream)
optional_assocs = {
'site': (Site, 'name'),
'tenant': (Tenant, 'name'),
'tenant_group': (TenantGroup, 'name'),
'group': (VLANGroup, 'name'),
'role': (Role, 'name')
}
if vlans is not None:
for params in vlans:
custom_fields = params.pop('custom_fields', None)
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
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)
print("🏠 Created VLAN", vlan.name)

View File

@@ -0,0 +1,54 @@
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 ruamel.yaml import YAML
from netaddr import IPNetwork
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/prefixes.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
prefixes = yaml.load(stream)
optional_assocs = {
'site': (Site, 'name'),
'tenant': (Tenant, 'name'),
'tenant_group': (TenantGroup, 'name'),
'vlan': (VLAN, 'name'),
'role': (Role, 'name'),
'vrf': (VRF, 'name')
}
if prefixes is not None:
for params in prefixes:
custom_fields = params.pop('custom_fields', None)
params['prefix'] = IPNetwork(params['prefix'])
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
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)
print("📌 Created Prefix", prefix.prefix)

View File

@@ -0,0 +1,44 @@
from dcim.models import Interface, Device
from extras.models import CustomField, CustomFieldValue
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/dcim_interfaces.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
interfaces = yaml.load(stream)
required_assocs = {
'device': (Device, 'name')
}
if interfaces is not None:
for params in interfaces:
custom_fields = params.pop('custom_fields', None)
for assoc, details in required_assocs.items():
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
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)
print("🧷 Created interface", interface.name, interface.device.name)

View File

@@ -0,0 +1,65 @@
from ipam.models import IPAddress, VRF
from dcim.models import Device, Interface
from virtualization.models import VirtualMachine
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
from ruamel.yaml import YAML
from netaddr import IPNetwork
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/ip_addresses.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
ip_addresses = yaml.load(stream)
optional_assocs = {
'tenant': (Tenant, 'name'),
'vrf': (VRF, 'name'),
'interface': (Interface, 'name')
}
if ip_addresses is not None:
for params in ip_addresses:
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:
print("IP Address can only specify one of the following: virtual_machine or device.")
sys.exit()
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
if assoc == 'interface':
if vm:
vm_id = VirtualMachine.objects.get(name=vm).id
query = { field: params.pop(assoc), "virtual_machine_id": vm_id }
elif device:
dev_id = Device.objects.get(name=device).id
query = { field: params.pop(assoc), "device_id": dev_id }
else:
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
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)
print("🧬 Created IP Address", ip_address.address)