Include Initializers

Initializers are startup scripts for common tasks like creating custom
fields. These are problems many users of Netbox Docker potentially face
and are therefore worth sharing.
This commit is contained in:
Christian Mäder 2018-02-16 10:25:26 +01:00
parent 061cbb8e8d
commit 43cb9f7e50
No known key found for this signature in database
GPG Key ID: 92FFD0A711F196BB
11 changed files with 249 additions and 10 deletions

View File

@ -16,7 +16,11 @@ RUN apk add --no-cache \
postgresql-dev \ postgresql-dev \
wget wget
RUN pip install gunicorn RUN pip install \
# gunicorn is used for launching netbox
gunicorn \
# ruamel is used in startup_scripts
ruamel.yaml
WORKDIR /opt WORKDIR /opt
@ -31,11 +35,13 @@ RUN pip install -r requirements.txt
COPY docker/configuration.docker.py /opt/netbox/netbox/netbox/configuration.py COPY docker/configuration.docker.py /opt/netbox/netbox/netbox/configuration.py
COPY docker/gunicorn_config.py /opt/netbox/ COPY docker/gunicorn_config.py /opt/netbox/
COPY docker/nginx.conf /etc/netbox-nginx/nginx.conf COPY docker/nginx.conf /etc/netbox-nginx/nginx.conf
COPY docker/docker-entrypoint.sh docker-entrypoint.sh
COPY startup_scripts/ ./
COPY initializers/ ./
WORKDIR /opt/netbox/netbox WORKDIR /opt/netbox/netbox
COPY docker/docker-entrypoint.sh /docker-entrypoint.sh ENTRYPOINT [ "/opt/netbox/docker-entrypoint.sh" ]
ENTRYPOINT [ "/docker-entrypoint.sh" ]
VOLUME ["/etc/netbox-nginx/"] VOLUME ["/etc/netbox-nginx/"]

View File

@ -66,11 +66,11 @@ For example defining `ALLOWED_HOSTS=localhost ::1 127.0.0.1` would allows access
[compose-env]: https://docs.docker.com/compose/environment-variables/ [compose-env]: https://docs.docker.com/compose/environment-variables/
### Custom Initialisation Code (e.g. Automatically Setting Up Custom Fields) ### Custom Initialization Code (e.g. Automatically Setting Up Custom Fields)
When using `docker-compose`, all the python scripts present in `docker/startup_scripts` will automatically be executed after the application boots in the context of `./manage.py`. When using `docker-compose`, all the python scripts present in `/opt/netbox/startup_scripts` will automatically be executed after the application boots in the context of `./manage.py`.
That mechanism can be used for many things, and in particular to load Netbox custom fields: That mechanism can be used for many things, e.g. to create Netbox custom fields:
```python ```python
# docker/startup_scripts/load_custom_fields.py # docker/startup_scripts/load_custom_fields.py
@ -94,12 +94,54 @@ if created:
my_custom_field.obj_type.add(device_type) my_custom_field.obj_type.add(device_type)
``` ```
#### Initializers
Initializers are built-in startup scripts for defining Netbox custom fields, groups and users.
All you need to do is to mount you own `initializers` folder ([see `docker-compose.yml`][netbox-docker-compose]).
Look at the [`initializers` folder][netbox-docker-initializers] to learn how the files must look like.
Here's an example for defining a custom field:
```yaml
# initializers/custom_fields.yml
text_field:
type: text
label: Custom Text
description: Enter text in a text field.
required: false
filterable: true
weight: 0
on_objects:
- dcim.models.Device
- dcim.models.Rack
- ipam.models.IPAddress
- ipam.models.Prefix
- tenancy.models.Tenant
- virtualization.models.VirtualMachine
```
[netbox-docker-initializers]: https://github.com/ninech/netbox-docker/tree/master/initializers
[netbox-docker-compose]: https://github.com/ninech/netbox-docker/blob/master/docker-compose.yml
#### Custom Docker Image
You can also build your own Netbox Docker image containing your own startup scripts, custom fields, users and groups
like this:
```
ARG VERSION=latest
FROM ninech/netbox:$VERSION
COPY startup_scripts/ /opt/netbox/startup_scripts/
COPY initializers/ /opt/netbox/initializers/
```
### Production ### Production
The default settings are optimized for (local) development environments. The default settings are optimized for (local) development environments.
You should therefore adjust the configuration for production setups, at least the following variables: You should therefore adjust the configuration for production setups, at least the following variables:
* `ALLOWED_HOSTS`: Add all URLs that lead to your netbox instance. * `ALLOWED_HOSTS`: Add all URLs that lead to your Netbox instance.
* `DB_*`: Use a persistent database. * `DB_*`: Use a persistent database.
* `EMAIL_*`: Use your own mailserver. * `EMAIL_*`: Use your own mailserver.
* `MAX_PAGE_SIZE`: Use the recommended default of 1000. * `MAX_PAGE_SIZE`: Use the recommended default of 1000.

View File

@ -10,7 +10,8 @@ services:
- postgres - postgres
env_file: netbox.env env_file: netbox.env
volumes: volumes:
- ./docker/startup_scripts:/opt/netbox/netbox/startup_scripts - ./startup_scripts:/opt/netbox/startup_scripts
- ./initializers:/opt/netbox/initializers
- netbox-nginx-config:/etc/netbox-nginx/ - netbox-nginx-config:/etc/netbox-nginx/
- netbox-static-files:/opt/netbox/netbox/static - netbox-static-files:/opt/netbox/netbox/static
- netbox-media-files:/opt/netbox/netbox/media - netbox-media-files:/opt/netbox/netbox/media

View File

@ -39,7 +39,8 @@ if not User.objects.filter(username='${SUPERUSER_NAME}'):
Token.objects.create(user=u, key='${SUPERUSER_API_TOKEN}') Token.objects.create(user=u, key='${SUPERUSER_API_TOKEN}')
END END
for script in $(ls startup_scripts/*.py 2> /dev/null); do for script in /opt/netbox/startup_scripts/*.py; do
echo "⚙️ Executing '$script'"
./manage.py shell --plain < "${script}" ./manage.py shell --plain < "${script}"
done done
@ -48,5 +49,6 @@ done
echo "✅ Initialisation is done." echo "✅ Initialisation is done."
# launch whatever is passed by docker via RUN # launch whatever is passed by docker
# (i.e. the RUN instruction in the Dockerfile)
exec ${@} exec ${@}

View File

@ -0,0 +1,66 @@
# text_field:
# type: text
# label: Custom Text
# description: Enter text in a text field.
# required: false
# filterable: true
# weight: 0
# on_objects:
# - dcim.models.Device
# - dcim.models.Rack
# - ipam.models.IPAddress
# - ipam.models.Prefix
# - tenancy.models.Tenant
# - virtualization.models.VirtualMachine
# integer_field:
# type: integer
# label: Custom Number
# description: Enter numbers into an integer field.
# required: true
# filterable: true
# weight: 10
# on_objects:
# - tenancy.models.Tenant
# selection_field:
# type: selection
# label: Choose between items
# required: false
# filterable: true
# 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
# boolean_field:
# type: boolean
# label: Yes Or No?
# required: true
# filterable: true
# default: "false" # important: but "false" in quotes!
# weight: 90
# on_objects:
# - dcim.models.Device
# url_field:
# type: url
# label: Hyperlink
# description: Link to something nice.
# required: true
# filterable: false
# on_objects:
# - tenancy.models.Tenant
# date_field:
# type: date
# label: Important Date
# required: false
# filterable: false
# on_objects:
# - dcim.models.Device

9
initializers/groups.yml Normal file
View File

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

6
initializers/users.yml Normal file
View File

@ -0,0 +1,6 @@
# technical_user:
# api_token: 0123456789technicaluser789abcdef01234567 # must be looooong!
# reader:
# password: reader
# writer:
# password: writer

View File

@ -0,0 +1,20 @@
from django.contrib.auth.models import Group, User
from users.models import Token
from ruamel.yaml import YAML
with open('/opt/netbox/initializers/users.yml', 'r') as stream:
yaml=YAML(typ='safe')
users = yaml.load(stream)
if users is not None:
for username, user_details in users.items():
if not User.objects.filter(username=username):
user = User.objects.create_user(
username = username,
password = user_details.get('password', 0) or User.objects.make_random_password)
print("👤 Created user ",username)
if user_details.get('api_token', 0):
Token.objects.create(user=user, key=user_details['api_token'])

View File

@ -0,0 +1,19 @@
from django.contrib.auth.models import Group, User
from ruamel.yaml import YAML
with open('/opt/netbox/initializers/groups.yml', 'r') as stream:
yaml=YAML(typ='safe')
groups = yaml.load(stream)
if groups is not None:
for groupname, group_details in groups.items():
group, created = Group.objects.get_or_create(name=groupname)
if created:
print("👥 Created group", groupname)
for username in group_details['users']:
user = User.objects.get(username=username)
if user:
user.groups.add(group)

View File

@ -0,0 +1,68 @@
from extras.constants import CF_TYPE_TEXT, CF_TYPE_INTEGER, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_URL, CF_TYPE_SELECT
from extras.models import CustomField, CustomFieldChoice
from ruamel.yaml import YAML
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
module_name, class_name = class_path.rsplit(".", 1)
module = importlib.import_module(module_name)
clazz = getattr(module, class_name)
return ContentType.objects.get_for_model(clazz)
with open('/opt/netbox/initializers/custom_fields.yml', 'r') as stream:
yaml = YAML(typ='safe')
customfields = yaml.load(stream)
if customfields is not None:
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):
custom_field.default = cf_details['default']
if cf_details.get('description', 0):
custom_field.description = cf_details['description']
if cf_details.get('filterable', 0):
custom_field.is_filterables = cf_details['filterable']
if cf_details.get('label', 0):
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))
if cf_details.get('required', 0):
custom_field.required = cf_details['required']
if cf_details.get('type', 0):
custom_field.type = text_to_fields[cf_details['type']]
if cf_details.get('weight', 0):
custom_field.weight = cf_details['weight']
custom_field.save()
for choice_details in cf_details.get('choices', []):
choice = CustomFieldChoice.objects.create(
field=custom_field,
value=choice_details['value'])
if choice_details.get('weight', 0):
choice.weight = choice_details['weight']
choice.save()
print("🔧 Created custom field", cf_name)