Compare commits

..

75 Commits

Author SHA1 Message Date
4dd7a51c7d Merge pull request #419 from netbox-community/develop
Version 1.0.1
2021-02-03 15:30:31 +01:00
c7e259e116 Merge branch 'release' into develop 2021-02-03 15:01:11 +01:00
3cbe07cb0e Preparation for 1.0.1 2021-02-03 14:52:21 +01:00
70b38d52b9 Merge pull request #413 from tobiasge/fix-412
Is greater than or equal to is "-ge" and not "-gte".
2021-01-31 21:38:49 +01:00
a21d146b60 Is greater than or equal to is "-ge" and not "-gte". 2021-01-31 16:39:45 +01:00
6e7a64bd81 Merge pull request #410 from netbox-community/renovate/django-storages-1.x
Update dependency django-storages to v1.11.1
2021-01-31 16:07:40 +01:00
f8360ba6aa Update dependency django-storages to v1.11.1 2021-01-31 14:42:24 +00:00
bab8373f66 Merge pull request #411 from netbox-community/renovate/actions-checkout-2.x
Update actions/checkout action to v2
2021-01-31 15:41:41 +01:00
ad93c99f46 Update actions/checkout action to v2 2021-01-31 11:55:20 +00:00
ed6256172f Merge pull request #394 from netbox-community/develop
Prepare version 1.0.0
2021-01-29 10:48:29 +01:00
5109e340ca Prepare for Version 1.0.0 2021-01-28 10:22:48 +01:00
62d31fda58 Merge pull request #398 from netbox-community/updateYAML
Update of ruamel.yaml
2021-01-20 16:08:03 +01:00
ed141c8a4e Merge pull request #399 from tobiasge/prevent-edge-from-failing
Prevent "alpine:edge" from failling the workflow
2021-01-20 13:14:09 +01:00
4d8d02e35a Merge pull request #397 from netbox-community/SkipStartupScriptsForUnitTests
Skip Startup Scripts in Unit Tests
2021-01-20 13:08:25 +01:00
96132e1dcc Prevent "alpine:edge" from failling the workflow
Alpine is releases a new version once a year. Therefore our workflow
runs don't need to be marked as failed when run on alpine:edge
2021-01-20 10:43:07 +01:00
896651ed97 Update of ruamel.yaml 2021-01-20 09:50:00 +01:00
f810d0342d Skip Startup Scripts in Unit Tests 2021-01-20 09:36:13 +01:00
323e18278a Merge branch 'MajesticFalcon-iss365' into develop 2021-01-20 09:03:31 +01:00
a0f7737916 rebase and fix script order 2021-01-20 09:03:04 +01:00
7f8cc76af6 address issue #365 2021-01-20 09:03:04 +01:00
21bd7f426c Merge pull request #393 from MajesticFalcon/vm_init_bug
Bugfix - Allow primary device IP without virtual machine initialization
2021-01-20 08:54:13 +01:00
3758bc805a Merge pull request #396 from netbox-community/renovate/configure
Configure Renovate
2021-01-20 08:26:35 +01:00
72859ca71a Configure Renovate 2021-01-20 08:24:47 +01:00
0022392f03 Add renovate.json 2021-01-20 08:24:28 +01:00
385c66e30d Update startup_scripts/270_primary_ips.py
Ah, of course.. I would agree. :)

Co-authored-by: Christian Mäder <cimnine@users.noreply.github.com>
2021-01-19 12:54:52 -06:00
65023a7dd4 Merge pull request #384 from ryanmerolle/develop
Expand Initialization Support
2021-01-19 18:59:49 +01:00
426adb2333 Preparation for 0.28.0 2021-01-18 08:38:44 +01:00
584566b0f0 allow simultaneous virtual machine and device primary ip initialization 2021-01-15 23:44:10 -06:00
5399f8c890 Update 270_primary_ips.py
fix issue where user has no virtual machines defined, but startup script still tries to iterate over empty file.
2021-01-15 23:42:26 -06:00
2372c1eeff remove site from power_feeds 2021-01-15 15:02:01 -05:00
788aeacd9b correct missing required sites in power_feeds 2021-01-15 14:25:42 -05:00
00fa1793d0 update services examples 2021-01-15 14:14:07 -05:00
4260e9b864 more comment corrections 2021-01-15 09:26:09 -05:00
02713e1465 showcase protcol options in services initializer 2021-01-15 09:23:21 -05:00
728a16c93d correct initializers commenting 2021-01-15 09:19:21 -05:00
742560c571 Merge pull request #361 from tobiasge/nginx-unit
Use Nginx Unit to serve the application
2021-01-15 13:33:55 +01:00
d273391773 Gunicorn is replaced with nginx-unit
We now serve Netbox with an nginx-unit instance instead of Gunicorn.
This allows us to get rid of the extra Nginx container because Unit is
also serving the static files. The static files are now collected at container
buildtime instead of every startup.
2021-01-15 09:22:22 +01:00
380cb77080 Merge pull request #386 from netbox-community/LinksForIssues
Links for issues
2021-01-12 11:08:50 +01:00
92b6608403 Create config.yml for ISSUE_TEMPLATE
This commit adds links to the _New Issue_ section of Github.
The intention is to provide more hints where to get help and
further reduce the amount of Github issues that can not be
acted upon.
2021-01-07 14:08:06 +01:00
94509f86d7 added route_targets startup_script 2020-12-30 19:11:09 -05:00
818266ace1 added services startup_script 2020-12-30 19:10:46 -05:00
dfb0327340 Added power startup_scripts 2020-12-30 19:10:30 -05:00
e3946af27c added tenant to example aggregates.yml 2020-12-30 19:09:27 -05:00
8d8c58df54 optional assoc to cluster & circuit startup_script 2020-12-30 19:09:08 -05:00
cbaaffc589 add extra space after ▶️ to remove text overlap 2020-12-30 18:23:12 -05:00
0f1cc9eea0 add tenant to aggregate startup_script 2020-12-30 18:22:12 -05:00
90018fc6d7 add cluster group startup script 2020-12-29 22:10:19 -05:00
621fa12934 comment out secret roles 2020-12-29 22:09:31 -05:00
3094665092 add tags & secret roles 2020-12-29 19:36:58 -05:00
31f52041f8 correct circuit model startup scripts 2020-12-29 18:27:41 -05:00
6ab38472be circuits model startup_scripts 2020-12-29 16:24:16 -05:00
aa4d630a0f Merge pull request #371 from netbox-community/develop
Version 0.27.0
2020-12-15 09:53:01 +01:00
4e8588accf Preparation for 0.27.0 2020-12-15 08:59:42 +01:00
07469b2b72 Merge pull request #370 from netbox-community/GettingHelp
Introducing our Github Community
2020-12-15 08:54:30 +01:00
a7c1d9f550 Merge pull request #369 from netbox-community/AddQuayIo
Add quay.io as fallback registry
2020-12-15 08:53:35 +01:00
5605b9b218 Introduce our Github Community 2020-12-15 00:50:02 +01:00
b92c652d99 Add quay.io as fallback registry 2020-12-15 00:41:01 +01:00
d77b3c1222 Merge pull request #342 from netbox-community/Fix337
Prepare for Netbox v2.10.x
2020-12-14 22:51:24 +01:00
e383fd42bd Fix custom fields initializer 2020-12-14 22:11:01 +01:00
234baa40a5 Remove redundant assignment in for loop 2020-12-14 20:58:50 +01:00
77d3dcded0 Fix leftover from testing in test.sh 2020-12-14 20:58:50 +01:00
349e269356 Remove the legacy select_field from the samples
This was only used for testing.
2020-12-14 20:58:50 +01:00
5c9bea8b50 Update Custom Field logic for Netbox v2.10.x 2020-12-14 20:58:43 +01:00
036f94a450 Simplify test script for manual tests 2020-12-14 20:54:19 +01:00
9d51762161 Merge pull request #359 from devon-mar/login-timeout-fix
Fix TypeError when using LOGIN_TIMEOUT
2020-11-13 15:36:16 +01:00
4276c941eb Change LOGIN_TIMEOUT default to int
Co-authored-by: Christian Mäder <cimnine@users.noreply.github.com>
2020-11-03 14:57:12 -08:00
3cbdf26773 Merge pull request #356 from timrabl/enviroment-changes
Enviroment changes
2020-11-03 18:44:44 +01:00
fbfce46ce5 Fix TypeError when using LOGIN_TIMEOUT 2020-11-02 22:39:06 -08:00
255889288c Rename SESSION_FILE_PATH default environment variable in configration.py 2020-10-30 23:06:42 +01:00
c068db1751 Merge pull request #348 from tobiasge/python-upgrade
Upgrade Python image to 3.9-alpine
2020-10-29 17:12:24 +01:00
378784a19c Merge pull request #328 from pruiz/pruiz/make-ldap-group-settings-optional
Allow disabling LDAP-group related settings when AUTH_LDAP_*_GROUP environment variables are not defined
2020-10-29 16:24:17 +01:00
dcb3b5495d Remove now unneeded os. prefix from environ calls. 2020-10-29 14:57:34 +01:00
60f4e8b2ed 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) 2020-10-29 14:56:35 +01:00
bb2ac7bd71 Disable tests for Python 3.10-rc
Some of the dependencies of Netbox can't be built with Python 3.10.
2020-10-26 16:47:38 +01:00
30a98c5009 Upgrade Python image to 3.9-alpine
Upgrade the default base image to Python 3.9 and start testing on Python 3.10-rc
2020-10-26 16:25:38 +01:00
74 changed files with 875 additions and 438 deletions

View File

@ -65,13 +65,3 @@ If your log is very long, create a Gist instead (and post the link to it): https
```text ```text
LOG LOG LOG LOG LOG LOG
``` ```
The output of `docker-compose logs nginx`:
<!--
Only if you have gotten a 5xx http error, else delete this section.
If your log is very long, create a Gist instead (and post the link to it): https://gist.github.com
-->
```text
LOG LOG LOG
```

13
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,13 @@
blank_issues_enabled: false
contact_links:
- name: The \#netbox-docker Slack channel
url: http://slack.networktocode.com/
about: It's usually the quickest way to seek help when you're in trouble with regards to Netbox Docker.
- name: Github Discussions
url: https://github.com/netbox-community/netbox-docker/discussions
about: This is the right place to ask questions about how to use or do certain things with Netbox Docker.
- name: Have you had a look at our Wiki?
url: https://github.com/netbox-community/netbox-docker/wiki
about: Our wiki contains information for common problems and tips for operating Netbox Docker in production.

View File

@ -10,6 +10,7 @@ on:
jobs: jobs:
build: build:
continue-on-error: ${{ matrix.docker_from == 'alpine:edge' }}
strategy: strategy:
matrix: matrix:
build_cmd: build_cmd:
@ -19,15 +20,14 @@ 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 - alpine:edge
- 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
steps: steps:
- id: git-checkout - id: git-checkout
name: Checkout name: Checkout
uses: actions/checkout@v1 uses: actions/checkout@v2
- id: docker-build - id: docker-build
name: Build the image from '${{ matrix.docker_from }}' with '${{ matrix.build_cmd }}' name: Build the image from '${{ matrix.docker_from }}' with '${{ matrix.build_cmd }}'
run: ${{ matrix.build_cmd }} run: ${{ matrix.build_cmd }}

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

@ -12,27 +12,15 @@ RUN apk add --no-cache \
libffi-dev \ libffi-dev \
libxslt-dev \ libxslt-dev \
openldap-dev \ openldap-dev \
postgresql-dev postgresql-dev \
py3-pip \
WORKDIR /install python3-dev \
&& python3 -m venv /opt/netbox/venv \
RUN pip install --prefix="/install" --no-warn-script-location \ && /opt/netbox/venv/bin/python3 -m pip install --upgrade pip setuptools
# gunicorn is used for launching netbox
gunicorn \
greenlet \
eventlet \
# napalm is used for gathering information from network devices
napalm \
# ruamel is used in startup_scripts
'ruamel.yaml>=0.15,<0.16' \
# django_auth_ldap is required for ldap
django_auth_ldap \
# django-storages was introduced in 2.7 and is optional
django-storages
ARG NETBOX_PATH ARG NETBOX_PATH
COPY ${NETBOX_PATH}/requirements.txt / COPY ${NETBOX_PATH}/requirements.txt requirements-container.txt /
RUN pip install --prefix="/install" --no-warn-script-location -r /requirements.txt RUN /opt/netbox/venv/bin/pip install -r /requirements.txt -r /requirements-container.txt
### ###
# Main stage # Main stage
@ -44,6 +32,7 @@ FROM ${FROM} as main
RUN apk add --no-cache \ RUN apk add --no-cache \
bash \ bash \
ca-certificates \ ca-certificates \
curl \
graphviz \ graphviz \
libevent \ libevent \
libffi \ libffi \
@ -51,35 +40,38 @@ RUN apk add --no-cache \
libressl \ libressl \
libxslt \ libxslt \
postgresql-libs \ postgresql-libs \
ttf-ubuntu-font-family python3 \
py3-pip \
ttf-ubuntu-font-family \
unit \
unit-python3
WORKDIR /opt WORKDIR /opt
COPY --from=builder /install /usr/local COPY --from=builder /opt/netbox/venv /opt/netbox/venv
ARG NETBOX_PATH ARG NETBOX_PATH
COPY ${NETBOX_PATH} /opt/netbox COPY ${NETBOX_PATH} /opt/netbox
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 /etc/netbox/
COPY docker/nginx.conf /etc/netbox-nginx/nginx.conf
COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh
COPY docker/launch-netbox.sh /opt/netbox/launch-netbox.sh
COPY startup_scripts/ /opt/netbox/startup_scripts/ COPY startup_scripts/ /opt/netbox/startup_scripts/
COPY initializers/ /opt/netbox/initializers/ COPY initializers/ /opt/netbox/initializers/
COPY configuration/ /etc/netbox/config/ COPY configuration/ /etc/netbox/config/
COPY docker/nginx-unit.json /etc/unit/
WORKDIR /opt/netbox/netbox WORKDIR /opt/netbox/netbox
# Must set permissions for '/opt/netbox/netbox/static' directory
# to g+w so that `./manage.py collectstatic` can be executed during
# container startup.
# Must set permissions for '/opt/netbox/netbox/media' directory # Must set permissions for '/opt/netbox/netbox/media' directory
# to g+w so that pictures can be uploaded to netbox. # to g+w so that pictures can be uploaded to netbox.
RUN mkdir static && chmod -R g+w static media RUN mkdir -p static /opt/unit/state/ /opt/unit/tmp/ \
&& chmod -R g+w media /opt/unit/ \
&& SECRET_KEY="dummy" /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input
ENTRYPOINT [ "/opt/netbox/docker-entrypoint.sh" ] ENTRYPOINT [ "/opt/netbox/docker-entrypoint.sh" ]
CMD ["gunicorn", "-c /etc/netbox/gunicorn_config.py", "netbox.wsgi"] CMD [ "/opt/netbox/launch-netbox.sh" ]
LABEL ORIGINAL_TAG="" \ LABEL ORIGINAL_TAG="" \
NETBOX_GIT_BRANCH="" \ NETBOX_GIT_BRANCH="" \

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
@ -55,7 +56,7 @@ cd netbox-docker
tee docker-compose.override.yml <<EOF tee docker-compose.override.yml <<EOF
version: '3.4' version: '3.4'
services: services:
nginx: netbox:
ports: ports:
- 8000:8080 - 8000:8080
EOF EOF
@ -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 1.0.1

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: 'alpine:3.13'"
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"
@ -106,7 +106,7 @@ else
fi fi
### ###
# Variables for fetching the source # Variables for fetching the Netbox source
### ###
SRC_ORG="${SRC_ORG-netbox-community}" SRC_ORG="${SRC_ORG-netbox-community}"
SRC_REPO="${SRC_REPO-netbox}" SRC_REPO="${SRC_REPO-netbox}"
@ -115,10 +115,10 @@ URL="${URL-https://github.com/${SRC_ORG}/${SRC_REPO}.git}"
NETBOX_PATH="${NETBOX_PATH-.netbox}" NETBOX_PATH="${NETBOX_PATH-.netbox}"
### ###
# Fetching the source # Fetching the Netbox source
### ###
if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ] ; then if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ] ; then
echo "🌐 Checking out '${NETBOX_BRANCH}' of netbox from the url '${URL}' into '${NETBOX_PATH}'" echo "🌐 Checking out '${NETBOX_BRANCH}' of Netbox from the url '${URL}' into '${NETBOX_PATH}'"
if [ ! -d "${NETBOX_PATH}" ]; then if [ ! -d "${NETBOX_PATH}" ]; then
$DRY git clone -q --depth 10 -b "${NETBOX_BRANCH}" "${URL}" "${NETBOX_PATH}" $DRY git clone -q --depth 10 -b "${NETBOX_BRANCH}" "${URL}" "${NETBOX_PATH}"
fi fi
@ -135,7 +135,7 @@ if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ] ; then
$DRY git checkout -qf FETCH_HEAD $DRY git checkout -qf FETCH_HEAD
$DRY git prune $DRY git prune
) )
echo "✅ Checked out netbox" echo "✅ Checked out Netbox"
fi fi
### ###
@ -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="alpine:3.13"
fi fi
### ###
@ -271,7 +271,7 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
if ! printf '%s\n' "${IMAGES_LAYERS_OLD[@]}" | grep -q -P "^${PYTHON_LAST_LAYER}\$"; then if ! printf '%s\n' "${IMAGES_LAYERS_OLD[@]}" | grep -q -P "^${PYTHON_LAST_LAYER}\$"; then
SHOULD_BUILD="true" SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} python" BUILD_REASON="${BUILD_REASON} alpine"
fi fi
if [ "${NETBOX_GIT_REF}" != "${NETBOX_GIT_REF_OLD}" ]; then if [ "${NETBOX_GIT_REF}" != "${NETBOX_GIT_REF_OLD}" ]; then
SHOULD_BUILD="true" SHOULD_BUILD="true"

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,9 +60,12 @@ 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 = {}
if AUTH_LDAP_REQUIRE_GROUP is not None:
AUTH_LDAP_USER_FLAGS_BY_GROUP = { AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"is_active": environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', ''), "is_active": environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', ''),
"is_staff": environ.get('AUTH_LDAP_IS_ADMIN_DN', ''), "is_staff": environ.get('AUTH_LDAP_IS_ADMIN_DN', ''),

View File

@ -7,6 +7,8 @@ services:
- redis - redis
- redis-cache - redis-cache
env_file: env/netbox.env env_file: env/netbox.env
environment:
SKIP_STARTUP_SCRIPTS: ${SKIP_STARTUP_SCRIPTS-false}
user: '101' user: '101'
volumes: volumes:
- ./startup_scripts:/opt/netbox/startup_scripts:z,ro - ./startup_scripts:/opt/netbox/startup_scripts:z,ro
@ -14,19 +16,9 @@ services:
- ./configuration:/etc/netbox/config:z,ro - ./configuration:/etc/netbox/config:z,ro
- ./reports:/etc/netbox/reports:z,ro - ./reports:/etc/netbox/reports:z,ro
- ./scripts:/etc/netbox/scripts:z,ro - ./scripts:/etc/netbox/scripts:z,ro
- netbox-nginx-config:/etc/netbox-nginx:z
- netbox-static-files:/opt/netbox/netbox/static:z
- netbox-media-files:/opt/netbox/netbox/media:z - netbox-media-files:/opt/netbox/netbox/media:z
nginx:
command: nginx -c /etc/netbox-nginx/nginx.conf
image: nginx:1.19-alpine
depends_on:
- netbox
ports: ports:
- 8080 - 8080
volumes:
- netbox-static-files:/opt/netbox/netbox/static:ro
- netbox-nginx-config:/etc/netbox-nginx/:ro
postgres: postgres:
image: postgres:12-alpine image: postgres:12-alpine
env_file: env/postgres.env env_file: env/postgres.env
@ -45,9 +37,5 @@ services:
- redis-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose - redis-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
env_file: env/redis-cache.env env_file: env/redis-cache.env
volumes: volumes:
netbox-static-files:
driver: local
netbox-nginx-config:
driver: local
netbox-media-files: netbox-media-files:
driver: local driver: local

View File

@ -15,30 +15,19 @@ services:
- ./configuration:/etc/netbox/config:z,ro - ./configuration:/etc/netbox/config:z,ro
- ./reports:/etc/netbox/reports:z,ro - ./reports:/etc/netbox/reports:z,ro
- ./scripts:/etc/netbox/scripts:z,ro - ./scripts:/etc/netbox/scripts:z,ro
- netbox-nginx-config:/etc/netbox-nginx:z
- netbox-static-files:/opt/netbox/netbox/static:z
- netbox-media-files:/opt/netbox/netbox/media:z - netbox-media-files:/opt/netbox/netbox/media:z
ports:
- "8080"
netbox-worker: netbox-worker:
<<: *netbox <<: *netbox
depends_on: depends_on:
- redis - redis
entrypoint: entrypoint:
- python3 - /opt/netbox/venv/bin/python
- /opt/netbox/netbox/manage.py - /opt/netbox/netbox/manage.py
command: command:
- rqworker - rqworker
ports: []
# nginx
nginx:
command: nginx -c /etc/netbox-nginx/nginx.conf
image: nginx:1.19-alpine
depends_on:
- netbox
ports:
- 8080
volumes:
- netbox-static-files:/opt/netbox/netbox/static:ro
- netbox-nginx-config:/etc/netbox-nginx/:ro
# postgres # postgres
postgres: postgres:
@ -66,10 +55,6 @@ services:
env_file: env/redis-cache.env env_file: env/redis-cache.env
volumes: volumes:
netbox-static-files:
driver: local
netbox-nginx-config:
driver: local
netbox-media-files: netbox-media-files:
driver: local driver: local
netbox-postgres-data: netbox-postgres-data:

View File

@ -9,6 +9,7 @@ from os import scandir
import importlib.util import importlib.util
import sys import sys
def _filename(f): def _filename(f):
return f.name return f.name

View File

@ -7,6 +7,9 @@ set -e
# Allows Netbox to be run as non-root users # Allows Netbox to be run as non-root users
umask 002 umask 002
# Load correct Python3 env
source /opt/netbox/venv/bin/activate
# Try to connect to the DB # Try to connect to the DB
DB_WAIT_TIMEOUT=${DB_WAIT_TIMEOUT-3} DB_WAIT_TIMEOUT=${DB_WAIT_TIMEOUT-3}
MAX_DB_WAIT_TIME=${MAX_DB_WAIT_TIME-30} MAX_DB_WAIT_TIME=${MAX_DB_WAIT_TIME-30}
@ -60,9 +63,6 @@ else
echo "import runpy; runpy.run_path('../startup_scripts')" | ./manage.py shell --interface python echo "import runpy; runpy.run_path('../startup_scripts')" | ./manage.py shell --interface python
fi fi
# Copy static files
./manage.py collectstatic --no-input
echo "✅ Initialisation is done." echo "✅ Initialisation is done."
# Launch whatever is passed by docker # Launch whatever is passed by docker

View File

@ -1,8 +0,0 @@
command = '/usr/bin/gunicorn'
pythonpath = '/opt/netbox/netbox'
bind = '0.0.0.0:8001'
workers = 3
errorlog = '-'
accesslog = '-'
capture_output = False
loglevel = 'info'

53
docker/launch-netbox.sh Executable file
View File

@ -0,0 +1,53 @@
#!/bin/bash
UNIT_CONFIG="${UNIT_CONFIG-/etc/unit/nginx-unit.json}"
UNIT_SOCKET="/opt/unit/unit.sock"
load_configuration() {
MAX_WAIT=10
WAIT_COUNT=0
while [ ! -S $UNIT_SOCKET ]; do
if [ $WAIT_COUNT -ge $MAX_WAIT ]; then
echo "⚠️ No control socket found; configuration will not be loaded."
return 1
fi
WAIT_COUNT=$((WAIT_COUNT + 1))
echo "⏳ Waiting for control socket to be created... (${WAIT_COUNT}/${MAX_WAIT})"
sleep 1
done
# even when the control socket exists, it does not mean unit has finished initialisation
# this curl call will get a reply once unit is fully launched
curl --silent --output /dev/null --request GET --unix-socket $UNIT_SOCKET http://localhost/
echo "⚙️ Applying configuration from $UNIT_CONFIG";
RESP_CODE=$(curl \
--silent \
--output /dev/null \
--write-out '%{http_code}' \
--request PUT \
--data-binary "@${UNIT_CONFIG}" \
--unix-socket $UNIT_SOCKET \
http://localhost/config
)
if [ "$RESP_CODE" != "200" ]; then
echo "⚠️ Could no load Unit configuration"
kill "$(cat /opt/unit/unit.pid)"
return 1
fi
echo "✅ Unit configuration loaded successfully"
}
load_configuration &
exec unitd \
--no-daemon \
--control unix:$UNIT_SOCKET \
--pid /opt/unit/unit.pid \
--log /dev/stdout \
--state /opt/unit/state/ \
--tmp /opt/unit/tmp/

40
docker/nginx-unit.json Normal file
View File

@ -0,0 +1,40 @@
{
"listeners": {
"*:8080": {
"pass": "routes"
}
},
"routes": [
{
"match": {
"uri": "/static/*"
},
"action": {
"share": "/opt/netbox/netbox"
}
},
{
"action": {
"pass": "applications/netbox"
}
}
],
"applications": {
"netbox": {
"type": "python 3",
"path": "/opt/netbox/netbox/",
"module": "netbox.wsgi",
"home": "/opt/netbox/venv",
"processes": {
"max": 4,
"spare": 1,
"idle_timeout": 120
}
}
},
"access_log": "/dev/stdout"
}

View File

@ -1,44 +0,0 @@
daemon off;
worker_processes 1;
error_log /dev/stderr info;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
server_tokens off;
client_max_body_size 10M;
server {
listen 8080;
access_log off;
location /static/ {
alias /opt/netbox/netbox/static/;
}
location / {
proxy_pass http://netbox:8001;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
}
}
server {
listen 8081;
access_log off;
location = /stub_status {
stub_status;
}
}
}

View File

@ -1,5 +1,6 @@
# - prefix: 10.0.0.0/16 # - prefix: 10.0.0.0/16
# rir: RFC1918 # rir: RFC1918
# tenant: tenant1
# - prefix: fd00:ccdd::/32 # - prefix: fd00:ccdd::/32
# rir: RFC4193 ULA # rir: RFC4193 ULA
# - prefix: 2001:db8::/32 # - prefix: 2001:db8::/32

View File

@ -0,0 +1,6 @@
# - name: VPLS
# slug: vpls
# - name: MPLS
# slug: mpls
# - name: Internet
# slug: internet

View File

@ -0,0 +1,7 @@
# - cid: Circuit_ID-1
# provider: Provider1
# type: Internet
# tenant: tenant1
# - cid: Circuit_ID-2
# provider: Provider2
# type: MPLS

View File

@ -0,0 +1,4 @@
# - name: Group 1
# slug: group-1
# - name: Group 2
# slug: group-2

View File

@ -1,5 +1,7 @@
# - name: cluster1 # - name: cluster1
# type: Hyper-V # type: Hyper-V
# group: Group 1
# tenant: tenant1
# - name: cluster2 # - name: cluster2
# type: Hyper-V # type: Hyper-V
# site: SING 1 # site: SING 1

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

@ -0,0 +1,14 @@
# - name: power feed 1
# power_panel: power panel AMS 1
# voltage: 208
# amperage: 50
# max_utilization: 80
# phase: Single phase
# rack: rack-01
# - name: power feed 2
# power_panel: power panel SING 1
# voltage: 208
# amperage: 50
# max_utilization: 80
# phase: Three-phase
# rack: rack-03

View File

@ -0,0 +1,5 @@
# - name: power panel AMS 1
# site: AMS 1
# - name: power panel SING 1
# site: SING 1
# rack_group: cage 101

View File

@ -0,0 +1,6 @@
# - name: Provider1
# slug: provider1
# asn: 121
# - name: Provider2
# slug: provider2
# asn: 122

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

@ -0,0 +1,3 @@
# - name: 65000:1001
# tenant: tenant1
# - name: 65000:1002

View File

@ -0,0 +1,4 @@
# - name: Super Secret Passwords
# slug: super-secret
# - name: SNMP Communities
# slug: snmp

15
initializers/services.yml Normal file
View File

@ -0,0 +1,15 @@
# - name: DNS
# protocol: TCP
# ports:
# - 53
# virtual_machine: virtual machine 1
# - name: DNS
# protocol: UDP
# ports:
# - 53
# virtual_machine: virtual machine 1
# - name: MISC
# protocol: UDP
# ports:
# - 4000
# device: server01

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

12
initializers/tags.yml Normal file
View File

@ -0,0 +1,12 @@
# - name: Tag 1
# slug: tag-1
# color: Pink
# - name: Tag 2
# slug: tag-2
# color: Cyan
# - name: Tag 3
# slug: tag-3
# color: Grey
# - name: Tag 4
# slug: tag-4
# color: Teal

13
renovate.json Normal file
View File

@ -0,0 +1,13 @@
{
"extends": [
"config:base"
],
"enabled": true,
"labels": ["maintenance"],
"baseBranches": ["develop"],
"pip_requirements": {
"fileMatch": [
"requirements-container.txt"
]
}
}

View File

@ -0,0 +1,4 @@
napalm==3.2.0
ruamel.yaml==0.16.12
django-auth-ldap==2.2.0
django-storages==1.11.1

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

@ -0,0 +1,23 @@
from extras.models import Tag
from utilities.choices import ColorChoices
from startup_script_utils import load_yaml
import sys
tags = load_yaml('/opt/netbox/initializers/tags.yml')
if tags is None:
sys.exit()
for params in tags:
if 'color' in params:
color = params.pop('color')
for color_tpl in ColorChoices:
if color in color_tpl:
params['color'] = color_tpl[0]
tag, created = Tag.objects.get_or_create(**params)
if created:
print("🎨 Created Tag", tag.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

@ -0,0 +1,14 @@
from virtualization.models import ClusterGroup
from startup_script_utils import load_yaml
import sys
cluster_groups = load_yaml('/opt/netbox/initializers/cluster_groups.yml')
if cluster_groups is None:
sys.exit()
for params in cluster_groups:
cluster_group, created = ClusterGroup.objects.get_or_create(**params)
if created:
print("🗄️ Created Cluster Group", cluster_group.name)

View File

@ -1,9 +1,10 @@
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
from tenancy.models import Tenant
clusters = load_yaml('/opt/netbox/initializers/clusters.yml') clusters = load_yaml('/opt/netbox/initializers/clusters.yml')
if clusters is None: if clusters is None:
@ -15,11 +16,12 @@ required_assocs = {
optional_assocs = { optional_assocs = {
'site': (Site, 'name'), 'site': (Site, 'name'),
'group': (ClusterGroup, 'name') 'group': (ClusterGroup, 'name'),
'tenant': (Tenant, 'name')
} }
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 +39,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

@ -0,0 +1,44 @@
import sys
from dcim.models import Site
from startup_script_utils import *
from virtualization.models import Cluster, ClusterType, ClusterGroup
from tenancy.models import Tenant
clusters = load_yaml('/opt/netbox/initializers/clusters.yml')
if clusters is None:
sys.exit()
required_assocs = {
'type': (ClusterType, 'name')
}
optional_assocs = {
'site': (Site, 'name'),
'group': (ClusterGroup, 'name'),
'tenant': (Tenant, 'name')
}
for params in clusters:
custom_field_data = pop_custom_fields(params)
for assoc, details in required_assocs.items():
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
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)
cluster, created = Cluster.objects.get_or_create(**params)
if created:
set_custom_fields_values(cluster, custom_field_data)
print("🗄️ Created cluster", cluster.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

@ -0,0 +1,51 @@
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:
sys.exit()
required_assocs = {
'device_role': (DeviceRole, 'name'),
'device_type': (DeviceType, 'model'),
'site': (Site, 'name')
}
optional_assocs = {
'tenant': (Tenant, 'name'),
'platform': (Platform, 'name'),
'rack': (Rack, 'name'),
'cluster': (Cluster, 'name')
}
for params in devices:
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)
for assoc, details in required_assocs.items():
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
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)
device, created = Device.objects.get_or_create(**params)
if created:
set_custom_fields_values(device, custom_field_data)
print("🖥️ Created device", device.name)

View File

@ -1,11 +1,10 @@
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 *
from tenancy.models import Tenant
aggregates = load_yaml('/opt/netbox/initializers/aggregates.yml') aggregates = load_yaml('/opt/netbox/initializers/aggregates.yml')
if aggregates is None: if aggregates is None:
@ -15,8 +14,13 @@ required_assocs = {
'rir': (RIR, 'name') 'rir': (RIR, 'name')
} }
optional_assocs = {
'tenant': (Tenant, 'name'),
}
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():
@ -25,18 +29,16 @@ for params in aggregates:
params[assoc] = model.objects.get(**query) params[assoc] = model.objects.get(**query)
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)
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

@ -0,0 +1,14 @@
from virtualization.models import ClusterGroup
from startup_script_utils import load_yaml
import sys
cluster_groups = load_yaml('/opt/netbox/initializers/cluster_groups.yml')
if cluster_groups is None:
sys.exit()
for params in cluster_groups:
cluster_group, created = ClusterGroup.objects.get_or_create(**params)
if created:
print("🗄️ Created Cluster Group", cluster_group.name)

View File

@ -0,0 +1,31 @@
import sys
from ipam.models import RouteTarget
from startup_script_utils import *
from tenancy.models import Tenant
route_targets = load_yaml('/opt/netbox/initializers/route_targets.yml')
if route_targets is None:
sys.exit()
optional_assocs = {
'tenant': (Tenant, 'name')
}
for params in route_targets:
custom_field_data = pop_custom_fields(params)
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)
route_target, created = RouteTarget.objects.get_or_create(**params)
if created:
set_custom_fields_values(route_target, custom_field_data)
print("🎯 Created Route Target", route_target.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,8 +1,8 @@
from dcim.models import Interface, Device
from extras.models import CustomField, CustomFieldValue
from startup_script_utils import load_yaml
import sys import sys
from dcim.models import Interface, Device
from startup_script_utils import *
interfaces = load_yaml('/opt/netbox/initializers/dcim_interfaces.yml') interfaces = load_yaml('/opt/netbox/initializers/dcim_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 = 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

@ -31,13 +31,14 @@ def link_primary_ip(assets, asset_model):
devices = load_yaml('/opt/netbox/initializers/devices.yml') devices = load_yaml('/opt/netbox/initializers/devices.yml')
virtual_machines = load_yaml('/opt/netbox/initializers/virtual_machines.yml') virtual_machines = load_yaml('/opt/netbox/initializers/virtual_machines.yml')
if devices is None and virtual_machines is None:
sys.exit()
optional_assocs = { optional_assocs = {
'primary_ip4': (IPAddress, 'address'), 'primary_ip4': (IPAddress, 'address'),
'primary_ip6': (IPAddress, 'address') 'primary_ip6': (IPAddress, 'address')
} }
if devices is None and virtual_machines is None:
sys.exit()
if devices is not None:
link_primary_ip(devices, Device) link_primary_ip(devices, Device)
if virtual_machines is not None:
link_primary_ip(virtual_machines, VirtualMachine) link_primary_ip(virtual_machines, VirtualMachine)

View File

@ -0,0 +1,18 @@
from circuits.models import Provider
from startup_script_utils import *
import sys
providers = load_yaml('/opt/netbox/initializers/providers.yml')
if providers is None:
sys.exit()
for params in providers:
custom_field_data = pop_custom_fields(params)
provider, created = Provider.objects.get_or_create(**params)
if created:
set_custom_fields_values(provider, custom_field_data)
print("📡 Created provider", provider.name)

View File

@ -0,0 +1,18 @@
from circuits.models import CircuitType
from startup_script_utils import *
import sys
circuit_types = load_yaml('/opt/netbox/initializers/circuit_types.yml')
if circuit_types is None:
sys.exit()
for params in circuit_types:
custom_field_data = pop_custom_fields(params)
circuit_type, created = CircuitType.objects.get_or_create(**params)
if created:
set_custom_fields_values(circuit_type, custom_field_data)
print("⚡ Created Circuit Type", circuit_type.name)

View File

@ -0,0 +1,41 @@
from circuits.models import Circuit, Provider, CircuitType
from tenancy.models import Tenant
from startup_script_utils import *
import sys
circuits = load_yaml('/opt/netbox/initializers/circuits.yml')
if circuits is None:
sys.exit()
required_assocs = {
'provider': (Provider, 'name'),
'type': (CircuitType, 'name')
}
optional_assocs = {
'tenant': (Tenant, 'name')
}
for params in circuits:
custom_field_data = pop_custom_fields(params)
for assoc, details in required_assocs.items():
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
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)
circuit, created = Circuit.objects.get_or_create(**params)
if created:
set_custom_fields_values(circuit, custom_field_data)
print("⚡ Created Circuit", circuit.cid)

View File

@ -0,0 +1,14 @@
from secrets.models import SecretRole
from startup_script_utils import load_yaml
import sys
secret_roles = load_yaml('/opt/netbox/initializers/secret_roles.yml')
if secret_roles is None:
sys.exit()
for params in secret_roles:
secret_role, created = SecretRole.objects.get_or_create(**params)
if created:
print("🔑 Created Secret Role", secret_role.name)

View File

@ -0,0 +1,29 @@
from ipam.models import Service
from dcim.models import Device
from virtualization.models import VirtualMachine
from startup_script_utils import load_yaml
import sys
services = load_yaml('/opt/netbox/initializers/services.yml')
if services is None:
sys.exit()
optional_assocs = {
'device': (Device, 'name'),
'virtual_machine': (VirtualMachine, 'name')
}
for params in services:
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)
service, created = Service.objects.get_or_create(**params)
if created:
print("🧰 Created Service", service.name)

View File

@ -0,0 +1,41 @@
import sys
from dcim.models import Site, RackGroup, PowerPanel
from startup_script_utils import *
from tenancy.models import Tenant
power_panels = load_yaml('/opt/netbox/initializers/power_panels.yml')
if power_panels is None:
sys.exit()
required_assocs = {
'site': (Site, 'name')
}
optional_assocs = {
'rack_group': (RackGroup, 'name')
}
for params in power_panels:
custom_field_data = pop_custom_fields(params)
for assoc, details in required_assocs.items():
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
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)
power_panel, created = PowerPanel.objects.get_or_create(**params)
if created:
set_custom_fields_values(power_panel, custom_field_data)
print("⚡ Created Power Panel", power_panel.site, power_panel.name)

View File

@ -0,0 +1,41 @@
import sys
from dcim.models import Rack, PowerPanel, PowerFeed
from startup_script_utils import *
from tenancy.models import Tenant
power_feeds = load_yaml('/opt/netbox/initializers/power_feeds.yml')
if power_feeds is None:
sys.exit()
required_assocs = {
'power_panel': (PowerPanel, 'name')
}
optional_assocs = {
'rack': (Rack, 'name')
}
for params in power_feeds:
custom_field_data = pop_custom_fields(params)
for assoc, details in required_assocs.items():
model, field = details
query = { field: params.pop(assoc) }
params[assoc] = model.objects.get(**query)
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)
power_feed, created = PowerFeed.objects.get_or_create(**params)
if created:
set_custom_fields_values(power_feed, custom_field_data)
print("⚡ Created Power Feed", power_feed.name)

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)

23
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
if [ "${1}x" != "x" ]; then
# Use the command line argument
export IMAGE="netboxcommunity/netbox:${1}"
else
export IMAGE="${IMAGE-netboxcommunity/netbox:latest}" 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"
@ -18,7 +35,7 @@ if [ -z "${IMAGE}" ]; then
fi fi
# The docker compose command to use # The docker compose command to use
doco="docker-compose -f docker-compose.test.yml" doco="docker-compose --file docker-compose.test.yml --project-name netbox_docker_test_${1}"
INITIALIZERS_DIR=".initializers" INITIALIZERS_DIR=".initializers"
@ -39,7 +56,7 @@ test_setup() {
test_netbox_unit_tests() { test_netbox_unit_tests() {
echo "⏱ Running Netbox Unit Tests" echo "⏱ Running Netbox Unit Tests"
$doco run --rm netbox ./manage.py test SKIP_STARTUP_SCRIPTS=true $doco run --rm netbox ./manage.py test
} }
test_initializers() { test_initializers() {