✨ 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:
parent
061cbb8e8d
commit
43cb9f7e50
12
Dockerfile
12
Dockerfile
@ -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/"]
|
||||||
|
|
||||||
|
50
README.md
50
README.md
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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 ${@}
|
||||||
|
66
initializers/custom_fields.yml
Normal file
66
initializers/custom_fields.yml
Normal 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
9
initializers/groups.yml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# applications:
|
||||||
|
# users:
|
||||||
|
# - technical_user
|
||||||
|
# readers:
|
||||||
|
# users:
|
||||||
|
# - reader
|
||||||
|
# writers:
|
||||||
|
# users:
|
||||||
|
# - writer
|
6
initializers/users.yml
Normal file
6
initializers/users.yml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# technical_user:
|
||||||
|
# api_token: 0123456789technicaluser789abcdef01234567 # must be looooong!
|
||||||
|
# reader:
|
||||||
|
# password: reader
|
||||||
|
# writer:
|
||||||
|
# password: writer
|
20
startup_scripts/00_users.py
Normal file
20
startup_scripts/00_users.py
Normal 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'])
|
19
startup_scripts/10_groups.py
Normal file
19
startup_scripts/10_groups.py
Normal 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)
|
68
startup_scripts/20_custom_fields.py
Normal file
68
startup_scripts/20_custom_fields.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user