Compare commits

..

No commits in common. "release" and "1.6.1" have entirely different histories.

108 changed files with 2740 additions and 743 deletions

View File

@ -1,4 +1,3 @@
---
name: push name: push
on: on:
@ -14,67 +13,53 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Checks syntax of our code name: Checks syntax of our code
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
# Full git history is needed to get a proper # Full git history is needed to get a proper list of changed files within `super-linter`
# list of changed files within `super-linter` fetch-depth: 0
fetch-depth: 0 - uses: actions/setup-python@v3
- uses: actions/setup-python@v4 - name: Lint Code Base
with: uses: github/super-linter@v4
python-version: '3.9' env:
- name: Lint Code Base DEFAULT_BRANCH: develop
uses: github/super-linter@v4 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
env: SUPPRESS_POSSUM: true
DEFAULT_BRANCH: develop LINTER_RULES_PATH: /
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_ALL_CODEBASE: false
SUPPRESS_POSSUM: true VALIDATE_DOCKERFILE: false
LINTER_RULES_PATH: / FILTER_REGEX_EXCLUDE: (.*/)?(LICENSE|configuration/.*)
VALIDATE_ALL_CODEBASE: false
VALIDATE_DOCKERFILE: false EDITORCONFIG_FILE_NAME: .ecrc
VALIDATE_GITLEAKS: false DOCKERFILE_HADOLINT_FILE_NAME: .hadolint.yaml
FILTER_REGEX_EXCLUDE: (.*/)?(LICENSE|configuration/.*) MARKDOWN_CONFIG_FILE: .markdown-lint.yml
EDITORCONFIG_FILE_NAME: .ecrc PYTHON_BLACK_CONFIG_FILE: pyproject.toml
DOCKERFILE_HADOLINT_FILE_NAME: .hadolint.yaml PYTHON_FLAKE8_CONFIG_FILE: .flake8
MARKDOWN_CONFIG_FILE: .markdown-lint.yml PYTHON_ISORT_CONFIG_FILE: pyproject.toml
PYTHON_BLACK_CONFIG_FILE: pyproject.toml
PYTHON_FLAKE8_CONFIG_FILE: .flake8
PYTHON_ISORT_CONFIG_FILE: pyproject.toml
YAML_CONFIG_FILE: .yamllint.yaml
build: build:
continue-on-error: ${{ matrix.build_cmd != './build-latest.sh' }} continue-on-error: ${{ matrix.docker_from == 'alpine:edge' }}
strategy: strategy:
matrix: matrix:
build_cmd: build_cmd:
- ./build-latest.sh - ./build-latest.sh
- PRERELEASE=true ./build-latest.sh - PRERELEASE=true ./build-latest.sh
- ./build.sh feature - ./build.sh feature
- ./build.sh develop - ./build.sh develop
platform: docker_from:
- linux/amd64 - '' # use the default of the build script
- linux/arm64
fail-fast: false fail-fast: false
env:
GH_ACTION: enable
IMAGE_NAMES: docker.io/netboxcommunity/netbox
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@v3 uses: actions/checkout@v3
- id: qemu-setup - id: docker-build
name: Set up QEMU name: Build the image from '${{ matrix.docker_from }}' with '${{ matrix.build_cmd }}'
uses: docker/setup-qemu-action@v2 run: ${{ matrix.build_cmd }}
- id: buildx-setup env:
name: Set up Docker Buildx DOCKER_FROM: ${{ matrix.docker_from }}
uses: docker/setup-buildx-action@v2 GH_ACTION: enable
- id: docker-build - id: docker-test
name: Build the image for '${{ matrix.platform }}' with '${{ matrix.build_cmd }}' name: Test the image
run: ${{ matrix.build_cmd }} run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh
env: if: steps.docker-build.outputs.skipped != 'true'
BUILDX_PLATFORM: ${{ matrix.platform }}
BUILDX_BUILDER_NAME: ${{ steps.buildx-setup.outputs.name }}
- id: docker-test
name: Test the image
run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh
if: steps.docker-build.outputs.skipped != 'true'

View File

@ -1,4 +1,3 @@
---
name: release name: release
on: on:
@ -7,77 +6,82 @@ on:
- published - published
schedule: schedule:
- cron: '45 5 * * *' - cron: '45 5 * * *'
workflow_dispatch:
jobs: jobs:
build: build:
strategy: strategy:
matrix: matrix:
build_cmd: build_cmd:
- ./build-latest.sh - ./build-latest.sh
- PRERELEASE=true ./build-latest.sh - PRERELEASE=true ./build-latest.sh
- ./build.sh feature - ./build.sh feature
- ./build.sh develop - ./build.sh develop
platform:
- linux/amd64,linux/arm64
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
env: env:
GH_ACTION: enable GH_ACTION: enable
IMAGE_NAMES: docker.io/netboxcommunity/netbox quay.io/netboxcommunity/netbox ghcr.io/netbox-community/netbox
steps: steps:
- id: source-checkout -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- id: set-netbox-docker-version -
name: Get Version of NetBox Docker name: Get Version of NetBox Docker
run: echo "::set-output name=version::$(cat VERSION)" run: |
shell: bash echo "::set-output name=version::$(cat VERSION)"
- id: qemu-setup shell: bash
name: Set up QEMU -
uses: docker/setup-qemu-action@v2 id: docker-build
- id: buildx-setup name: Build the image with '${{ matrix.build_cmd }}'
name: Set up Docker Buildx run: ${{ matrix.build_cmd }}
uses: docker/setup-buildx-action@v2 -
- id: docker-build name: Test the image
name: Build the image with '${{ matrix.build_cmd }}' run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh
run: ${{ matrix.build_cmd }} if: steps.docker-build.outputs.skipped != 'true'
- id: test-image
name: Test the image # docker.io
run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh -
if: steps.docker-build.outputs.skipped != 'true' name: Login to docker.io
# docker.io uses: docker/login-action@v1
- id: docker-io-login with:
name: Login to docker.io registry: docker.io
uses: docker/login-action@v2 username: ${{ secrets.dockerhub_username }}
with: password: ${{ secrets.dockerhub_password }}
registry: docker.io if: steps.docker-build.outputs.skipped != 'true'
username: ${{ secrets.dockerhub_username }} -
password: ${{ secrets.dockerhub_password }} name: Push the image to docker.io
if: steps.docker-build.outputs.skipped != 'true' run: ${{ matrix.build_cmd }} --push-only
# quay.io if: steps.docker-build.outputs.skipped != 'true'
- id: quay-io-login
name: Login to Quay.io # quay.io
uses: docker/login-action@v2 -
with: name: Login to Quay.io
registry: quay.io uses: docker/login-action@v1
username: ${{ secrets.quayio_username }} with:
password: ${{ secrets.quayio_password }} registry: quay.io
if: steps.docker-build.outputs.skipped != 'true' username: ${{ secrets.quayio_username }}
# ghcr.io password: ${{ secrets.quayio_password }}
- id: ghcr-io-login if: steps.docker-build.outputs.skipped != 'true'
name: Login to GitHub Container Registry -
uses: docker/login-action@v2 name: Build and push the image with '${{ matrix.build_cmd }}'
with: run: ${{ matrix.build_cmd }} --push
registry: ghcr.io env:
username: ${{ github.repository_owner }} DOCKER_REGISTRY: quay.io
password: ${{ secrets.GITHUB_TOKEN }} if: steps.docker-build.outputs.skipped != 'true'
if: steps.docker-build.outputs.skipped != 'true'
- id: build-and-push # ghcr.io
name: Push the image -
run: ${{ matrix.build_cmd }} --push name: Login to GitHub Container Registry
if: steps.docker-build.outputs.skipped != 'true' uses: docker/login-action@v1
env: with:
BUILDX_PLATFORM: ${{ matrix.platform }} registry: ghcr.io
BUILDX_BUILDER_NAME: ${{ steps.buildx-setup.outputs.name }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
if: steps.docker-build.outputs.skipped != 'true'
-
name: Build and push the image with '${{ matrix.build_cmd }}'
run: ${{ matrix.build_cmd }} --push
env:
DOCKER_REGISTRY: ghcr.io
DOCKER_ORG: netbox-community
if: steps.docker-build.outputs.skipped != 'true'

View File

@ -1,4 +1,3 @@
ignored: ignored:
- DL3006 - DL3006
- DL3008 - DL3018
- DL3003

View File

@ -1,5 +0,0 @@
---
rules:
line-length:
max: 120

View File

@ -1,38 +1,48 @@
ARG FROM ARG FROM
FROM ${FROM} as builder FROM ${FROM} as builder
RUN export DEBIAN_FRONTEND=noninteractive \ RUN apk add --no-cache \
&& apt-get update -qq \ bash \
&& apt-get upgrade \ build-base \
--yes -qq --no-install-recommends \ cargo \
&& apt-get install \
--yes -qq --no-install-recommends \
build-essential \
ca-certificates \ ca-certificates \
libldap-dev \ cmake \
libpq-dev \ cyrus-sasl-dev \
libsasl2-dev \ git \
libssl-dev \ graphviz \
libxml2-dev \ jpeg-dev \
libxml2-dev \ libevent-dev \
libxmlsec1 \ libffi-dev \
libxmlsec1-dev \
libxmlsec1-openssl \
libxslt-dev \ libxslt-dev \
pkg-config \ make \
musl-dev \
openldap-dev \
postgresql-dev \
py3-pip \
python3-dev \ python3-dev \
python3-pip \ && python3 -m venv /opt/netbox/venv \
python3-venv \ && /opt/netbox/venv/bin/python3 -m pip install --upgrade \
&& python3 -m venv /opt/netbox/venv \
&& /opt/netbox/venv/bin/python3 -m pip install --upgrade \
pip \ pip \
setuptools \ setuptools \
wheel wheel
# Build libcrc32c for google-crc32c python module
RUN git clone https://github.com/google/crc32c \
&& cd crc32c \
&& git submodule update --init --recursive \
&& mkdir build \
&& cd build \
&& cmake \
-DCMAKE_BUILD_TYPE=Release \
-DCRC32C_BUILD_TESTS=no \
-DCRC32C_BUILD_BENCHMARKS=no \
-DBUILD_SHARED_LIBS=yes \
.. \
&& make all install
ARG NETBOX_PATH ARG NETBOX_PATH
COPY ${NETBOX_PATH}/requirements.txt requirements-container.txt / COPY ${NETBOX_PATH}/requirements.txt requirements-container.txt /
RUN sed -i -e '/psycopg2-binary/d' requirements.txt && \ RUN /opt/netbox/venv/bin/pip install \
/opt/netbox/venv/bin/pip install \
-r /requirements.txt \ -r /requirements.txt \
-r /requirements-container.txt -r /requirements-container.txt
@ -43,43 +53,40 @@ RUN sed -i -e '/psycopg2-binary/d' requirements.txt && \
ARG FROM ARG FROM
FROM ${FROM} as main FROM ${FROM} as main
RUN export DEBIAN_FRONTEND=noninteractive \ RUN apk add --no-cache \
&& apt-get update -qq \ bash \
&& apt-get upgrade \
--yes -qq --no-install-recommends \
&& apt-get install \
--yes -qq --no-install-recommends \
bzip2 \
ca-certificates \ ca-certificates \
curl \ curl \
libldap-common \ graphviz \
libpq5 \ libevent \
libxmlsec1-openssl \ libffi \
libjpeg-turbo \
libxslt \
openssl \ openssl \
postgresql-client \
postgresql-libs \
py3-pip \
python3 \ python3 \
python3-distutils \
tini \ tini \
&& curl -sL https://nginx.org/keys/nginx_signing.key \ unit \
> /etc/apt/trusted.gpg.d/nginx.asc && \ unit-python3
echo "deb https://packages.nginx.org/unit/ubuntu/ jammy unit" \
> /etc/apt/sources.list.d/unit.list \
&& apt-get update -qq \
&& apt-get install \
--yes -qq --no-install-recommends \
unit=1.27.0-1~jammy \
unit-python3.10=1.27.0-1~jammy \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt
COPY --from=builder /usr/local/lib/libcrc32c.* /usr/local/lib/
COPY --from=builder /usr/local/include/crc32c /usr/local/include
COPY --from=builder /usr/local/lib/cmake/Crc32c /usr/local/lib/cmake/
COPY --from=builder /opt/netbox/venv /opt/netbox/venv 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/ldap_config.docker.py /opt/netbox/netbox/netbox/ldap_config.py
COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh
COPY docker/housekeeping.sh /opt/netbox/housekeeping.sh COPY docker/housekeeping.sh /opt/netbox/housekeeping.sh
COPY docker/launch-netbox.sh /opt/netbox/launch-netbox.sh COPY docker/launch-netbox.sh /opt/netbox/launch-netbox.sh
COPY startup_scripts/ /opt/netbox/startup_scripts/
COPY initializers/ /opt/netbox/initializers/
COPY configuration/ /etc/netbox/config/ COPY configuration/ /etc/netbox/config/
COPY docker/nginx-unit.json /etc/unit/ COPY docker/nginx-unit.json /etc/unit/
@ -94,15 +101,26 @@ RUN mkdir -p static /opt/unit/state/ /opt/unit/tmp/ \
--config-file /opt/netbox/mkdocs.yml --site-dir /opt/netbox/netbox/project-static/docs/ \ --config-file /opt/netbox/mkdocs.yml --site-dir /opt/netbox/netbox/project-static/docs/ \
&& SECRET_KEY="dummy" /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input && SECRET_KEY="dummy" /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input
ENV LANG=C.UTF-8 PATH=/opt/netbox/venv/bin:$PATH ENTRYPOINT [ "/sbin/tini", "--" ]
ENTRYPOINT [ "/usr/bin/tini", "--" ]
CMD [ "/opt/netbox/docker-entrypoint.sh", "/opt/netbox/launch-netbox.sh" ] CMD [ "/opt/netbox/docker-entrypoint.sh", "/opt/netbox/launch-netbox.sh" ]
LABEL netbox.original-tag="" \ LABEL ORIGINAL_TAG="" \
netbox.git-branch="" \ NETBOX_GIT_BRANCH="" \
netbox.git-ref="" \ NETBOX_GIT_REF="" \
netbox.git-url="" \ NETBOX_GIT_URL="" \
# See http://label-schema.org/rc1/#build-time-labels
# Also https://microbadger.com/labels
org.label-schema.schema-version="1.0" \
org.label-schema.build-date="" \
org.label-schema.name="NetBox Docker" \
org.label-schema.description="A container based distribution of NetBox, the free and open IPAM and DCIM solution." \
org.label-schema.vendor="The netbox-docker contributors." \
org.label-schema.url="https://github.com/netbox-community/netbox-docker" \
org.label-schema.usage="https://github.com/netbox-community/netbox-docker/wiki" \
org.label-schema.vcs-url="https://github.com/netbox-community/netbox-docker.git" \
org.label-schema.vcs-ref="" \
org.label-schema.version="snapshot" \
# See https://github.com/opencontainers/image-spec/blob/master/annotations.md#pre-defined-annotation-keys # See https://github.com/opencontainers/image-spec/blob/master/annotations.md#pre-defined-annotation-keys
org.opencontainers.image.created="" \ org.opencontainers.image.created="" \
org.opencontainers.image.title="NetBox Docker" \ org.opencontainers.image.title="NetBox Docker" \
@ -114,4 +132,17 @@ LABEL netbox.original-tag="" \
org.opencontainers.image.documentation="https://github.com/netbox-community/netbox-docker/wiki" \ org.opencontainers.image.documentation="https://github.com/netbox-community/netbox-docker/wiki" \
org.opencontainers.image.source="https://github.com/netbox-community/netbox-docker.git" \ org.opencontainers.image.source="https://github.com/netbox-community/netbox-docker.git" \
org.opencontainers.image.revision="" \ org.opencontainers.image.revision="" \
org.opencontainers.image.version="" org.opencontainers.image.version="snapshot"
#####
## LDAP specific configuration
#####
FROM main as ldap
RUN apk add --no-cache \
libsasl \
libldap \
util-linux
COPY docker/ldap_config.docker.py /opt/netbox/netbox/netbox/ldap_config.py

View File

@ -7,7 +7,7 @@
![Docker Pulls](https://img.shields.io/docker/pulls/netboxcommunity/netbox) ![Docker Pulls](https://img.shields.io/docker/pulls/netboxcommunity/netbox)
[![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 container. [The GitHub repository](netbox-docker-github) houses the components needed to build NetBox as a container.
Images are built regularly using the code in that repository and are pushed to [Docker Hub][netbox-dockerhub], [Quay.io][netbox-quayio] and [GitHub Container Registry][netbox-ghcr]. Images are built regularly using the code in that repository and are pushed to [Docker Hub][netbox-dockerhub], [Quay.io][netbox-quayio] and [GitHub Container Registry][netbox-ghcr].
Do you have any questions? Do you have any questions?
@ -16,6 +16,7 @@ please join [our Slack][netbox-docker-slack] and ask for help in the [`#netbox-d
[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-dockerhub]: https://hub.docker.com/r/netboxcommunity/netbox/ [netbox-dockerhub]: https://hub.docker.com/r/netboxcommunity/netbox/
[netbox-quayio]: https://quay.io/repository/netboxcommunity/netbox [netbox-quayio]: https://quay.io/repository/netboxcommunity/netbox
[netbox-ghcr]: https://github.com/netbox-community/netbox-docker/pkgs/container/netbox [netbox-ghcr]: https://github.com/netbox-community/netbox-docker/pkgs/container/netbox
@ -55,6 +56,7 @@ The default credentials are:
* API Token: **0123456789abcdef0123456789abcdef01234567** * API Token: **0123456789abcdef0123456789abcdef01234567**
[wiki-getting-started]: https://github.com/netbox-community/netbox-docker/wiki/Getting-Started [wiki-getting-started]: https://github.com/netbox-community/netbox-docker/wiki/Getting-Started
[docker-reception]: https://github.com/nxt-engineering/reception
## Container Image Tags ## Container Image Tags
@ -90,9 +92,17 @@ For each of the above tag, there is an extra tag:
This is the same version as `snapshot-a.b.c`. This is the same version as `snapshot-a.b.c`.
It always points to the latest version of _NetBox Docker_. It always points to the latest version of _NetBox Docker_.
Then there is currently one extra tags for each of the above tags:
* `-ldap`:
These container images contain additional dependencies and configuration files for connecting NetBox to an LDAP directory.
[Learn more about that in our wiki][netbox-docker-ldap].
[netbox-releases]: https://github.com/netbox-community/netbox/releases [netbox-releases]: https://github.com/netbox-community/netbox/releases
[netbox-master]: https://github.com/netbox-community/netbox/tree/master [netbox-master]: https://github.com/netbox-community/netbox/tree/master
[netbox-develop]: https://github.com/netbox-community/netbox/tree/develop [netbox-develop]: https://github.com/netbox-community/netbox/tree/develop
[netbox-branches]: https://github.com/netbox-community/netbox/branches
[netbox-docker-ldap]: https://github.com/netbox-community/netbox-docker/wiki/LDAP
## Documentation ## Documentation
@ -117,11 +127,10 @@ you may find [the `#netbox` channel][netbox-slack-channel] on the same Slack ins
## 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:
* The _Docker version_ must be at least `20.10.10`. * The *Docker version* must be at least `19.03`.
* The _containerd version_ must be at least `1.5.6`. * The *docker-compose version* must be at least `1.28.0`.
* The _docker-compose version_ must be at least `1.28.0`.
To check the version installed on your system run `docker --version` and `docker-compose --version`. To check the version installed on your system run `docker --version` and `docker-compose --version`.

View File

@ -1 +1 @@
2.4.0 1.6.1

View File

@ -0,0 +1,8 @@
#!/bin/bash
push_image_to_registry() {
local target_tag=$1
echo "⏫ Pushing '${target_tag}'"
$DRY docker push "${target_tag}"
echo "✅ Finished pushing the Docker image '${target_tag}'."
}

574
build.sh
View File

@ -6,129 +6,87 @@ echo "▶️ $0 $*"
set -e set -e
if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then
_BOLD=$(tput bold) echo "Usage: ${0} <branch> [--push|--push-only]"
_GREEN=$(tput setaf 2) echo " branch The branch or tag to build. Required."
_CYAN=$(tput setaf 6) echo " --push Pushes the built Docker image to the registry."
_CLEAR=$(tput sgr0) echo " --push-only Only pushes the Docker image to the registry, but does not build it."
echo ""
cat <<END_OF_HELP echo "You can use the following ENV variables to customize the build:"
${_BOLD}Usage:${_CLEAR} ${0} <branch> [--push] echo " SRC_ORG Which fork of netbox to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO})."
echo " Default: netbox-community"
branch The branch or tag to build. Required. echo " SRC_REPO The name of the repository to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO})."
--push Pushes the built container image to the registry. echo " Default: netbox"
echo " URL Where to fetch the code from."
${_BOLD}You can use the following ENV variables to customize the build:${_CLEAR} echo " Must be a git repository. Can be private."
echo " Default: https://github.com/\${SRC_ORG}/\${SRC_REPO}.git"
SRC_ORG Which fork of netbox to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO}). echo " NETBOX_PATH The path where netbox will be checkout out."
${_GREEN}Default:${_CLEAR} netbox-community echo " Must not be outside of the netbox-docker repository (because of Docker)!"
echo " Default: .netbox"
SRC_REPO The name of the repository to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO}). echo " SKIP_GIT If defined, git is not invoked and \${NETBOX_PATH} will not be altered."
${_GREEN}Default:${_CLEAR} netbox echo " This may be useful, if you are manually managing the NETBOX_PATH."
echo " Default: undefined"
URL Where to fetch the code from. echo " TAG The version part of the docker tag."
Must be a git repository. Can be private. echo " Default:"
${_GREEN}Default:${_CLEAR} https://github.com/\${SRC_ORG}/\${SRC_REPO}.git echo " When <branch>=master: latest"
echo " When <branch>=develop: snapshot"
NETBOX_PATH The path where netbox will be checkout out. echo " Else: same as <branch>"
Must not be outside of the netbox-docker repository (because of Docker)! echo " DOCKER_REGISTRY The Docker repository's registry (i.e. '\${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}'')"
${_GREEN}Default:${_CLEAR} .netbox echo " Used for tagging the image."
echo " Default: docker.io"
SKIP_GIT If defined, git is not invoked and \${NETBOX_PATH} will not be altered. echo " DOCKER_ORG The Docker repository's organisation (i.e. '\${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}'')"
This may be useful, if you are manually managing the NETBOX_PATH. echo " Used for tagging the image."
${_GREEN}Default:${_CLEAR} undefined echo " Default: netboxcommunity"
echo " DOCKER_REPO The Docker repository's name (i.e. '\${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}'')"
TAG The version part of the image tag. echo " Used for tagging the image."
${_GREEN}Default:${_CLEAR} echo " Default: netbox"
When <branch>=master: latest echo " DOCKER_TAG The name of the tag which is applied to the image."
When <branch>=develop: snapshot echo " Useful for pushing into another registry than hub.docker.com."
Else: same as <branch> echo " Default: \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:\${TAG}"
echo " DOCKER_SHORT_TAG The name of the short tag which is applied to the"
IMAGE_NAMES The names used for the image including the registry echo " image. This is used to tag all patch releases to their"
Used for tagging the image. echo " containing version e.g. v2.5.1 -> v2.5"
${_GREEN}Default:${_CLEAR} docker.io/netboxcommunity/netbox echo " Default: \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:<MAJOR>.<MINOR>"
${_CYAN}Example:${_CLEAR} 'docker.io/netboxcommunity/netbox quay.io/netboxcommunity/netbox' echo " DOCKERFILE The name of Dockerfile to use."
echo " Default: Dockerfile"
DOCKER_TAG The name of the tag which is applied to the image. echo " DOCKER_FROM The base image to use."
Useful for pushing into another registry than hub.docker.com. echo " Default: 'alpine:3.14'"
${_GREEN}Default:${_CLEAR} \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:\${TAG} echo " DOCKER_TARGET A specific target to build."
echo " It's currently not possible to pass multiple targets."
DOCKER_SHORT_TAG The name of the short tag which is applied to the echo " Default: main ldap"
image. This is used to tag all patch releases to their echo " HTTP_PROXY The proxy to use for http requests."
containing version e.g. v2.5.1 -> v2.5 echo " Example: http://proxy.domain.tld:3128"
${_GREEN}Default:${_CLEAR} \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:<MAJOR>.<MINOR> echo " Default: undefined"
echo " NO_PROXY Comma-separated list of domain extensions proxy should not be used for."
DOCKERFILE The name of Dockerfile to use. echo " Example: .domain1.tld,.domain2.tld"
${_GREEN}Default:${_CLEAR} Dockerfile echo " Default: undefined"
echo " DEBUG If defined, the script does not stop when certain checks are unsatisfied."
DOCKER_FROM The base image to use. echo " Default: undefined"
${_GREEN}Default:${_CLEAR} 'ubuntu:22.04' echo " DRY_RUN Prints all build statements instead of running them."
echo " Default: undefined"
BUILDX_PLATFORMS echo " GH_ACTION If defined, special 'echo' statements are enabled that set the"
Specifies the platform(s) to build the image for. echo " following environment variables in Github Actions:"
${_CYAN}Example:${_CLEAR} 'linux/amd64,linux/arm64' echo " - FINAL_DOCKER_TAG: The final value of the DOCKER_TAG env variable"
${_GREEN}Default:${_CLEAR} 'linux/amd64' echo " Default: undefined"
echo ""
BUILDX_BUILDER_NAME echo "Examples:"
If defined, the image build will be assigned to the given builder. echo " ${0} master"
If you specify this variable, make sure that the builder exists. echo " This will fetch the latest 'master' branch, build a Docker Image and tag it"
If this value is not defined, a new builx builder with the directory name of the echo " 'netboxcommunity/netbox:latest'."
current directory (i.e. '$(basename "${PWD}")') is created." echo " ${0} develop"
${_CYAN}Example:${_CLEAR} 'clever_lovelace' echo " This will fetch the latest 'develop' branch, build a Docker Image and tag it"
${_GREEN}Default:${_CLEAR} undefined echo " 'netboxcommunity/netbox:snapshot'."
echo " ${0} v2.6.6"
BUILDX_REMOVE_BUILDER echo " This will fetch the 'v2.6.6' tag, build a Docker Image and tag it"
If defined (and only if BUILDX_BUILDER_NAME is undefined), echo " 'netboxcommunity/netbox:v2.6.6' and 'netboxcommunity/netbox:v2.6'."
then the buildx builder created by this script will be removed after use. echo " ${0} develop-2.7"
This is useful if you build NetBox Docker on an automated system that does echo " This will fetch the 'develop-2.7' branch, build a Docker Image and tag it"
not manage the builders for you. echo " 'netboxcommunity/netbox:develop-2.7'."
${_CYAN}Example:${_CLEAR} 'on' echo " SRC_ORG=cimnine ${0} feature-x"
${_GREEN}Default:${_CLEAR} undefined echo " This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git,"
echo " build a Docker Image and tag it 'netboxcommunity/netbox:feature-x'."
HTTP_PROXY The proxy to use for http requests. echo " SRC_ORG=cimnine DOCKER_ORG=cimnine ${0} feature-x"
${_CYAN}Example:${_CLEAR} http://proxy.domain.tld:3128 echo " This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git,"
${_GREEN}Default:${_CLEAR} undefined echo " build a Docker Image and tag it 'cimnine/netbox:feature-x'."
NO_PROXY Comma-separated list of domain extensions proxy should not be used for.
${_CYAN}Example:${_CLEAR} .domain1.tld,.domain2.tld
${_GREEN}Default:${_CLEAR} undefined
DEBUG If defined, the script does not stop when certain checks are unsatisfied.
${_GREEN}Default:${_CLEAR} undefined
DRY_RUN Prints all build statements instead of running them.
${_GREEN}Default:${_CLEAR} undefined
GH_ACTION If defined, special 'echo' statements are enabled that set the
following environment variables in Github Actions:
- FINAL_DOCKER_TAG: The final value of the DOCKER_TAG env variable
${_GREEN}Default:${_CLEAR} undefined
${_BOLD}Examples:${_CLEAR}
${0} master
This will fetch the latest 'master' branch, build a Docker Image and tag it
'netboxcommunity/netbox:latest'.
${0} develop
This will fetch the latest 'develop' branch, build a Docker Image and tag it
'netboxcommunity/netbox:snapshot'.
${0} v2.6.6
This will fetch the 'v2.6.6' tag, build a Docker Image and tag it
'netboxcommunity/netbox:v2.6.6' and 'netboxcommunity/netbox:v2.6'.
${0} develop-2.7
This will fetch the 'develop-2.7' branch, build a Docker Image and tag it
'netboxcommunity/netbox:develop-2.7'.
SRC_ORG=cimnine ${0} feature-x
This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git,
build a Docker Image and tag it 'netboxcommunity/netbox:feature-x'.
SRC_ORG=cimnine DOCKER_ORG=cimnine ${0} feature-x
This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git,
build a Docker Image and tag it 'cimnine/netbox:feature-x'.
END_OF_HELP
if [ "${1}x" == "x" ]; then if [ "${1}x" == "x" ]; then
exit 1 exit 1
@ -139,9 +97,6 @@ fi
source ./build-functions/gh-functions.sh source ./build-functions/gh-functions.sh
IMAGE_NAMES="${IMAGE_NAMES-docker.io/netboxcommunity/netbox}"
IFS=' ' read -ra IMAGE_NAMES <<<"${IMAGE_NAMES}"
### ###
# Enabling dry-run mode # Enabling dry-run mode
### ###
@ -215,7 +170,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="ubuntu:22.04" DOCKER_FROM="alpine:3.14"
fi fi
### ###
@ -223,7 +178,7 @@ fi
### ###
BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M+00:00')" BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M+00:00')"
if [ -d ".git" ] && [ -z "${SKIP_GIT}" ]; then if [ -d ".git" ]; then
GIT_REF="$(git rev-parse HEAD)" GIT_REF="$(git rev-parse HEAD)"
fi fi
@ -231,7 +186,7 @@ fi
PROJECT_VERSION="${PROJECT_VERSION-$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' VERSION)}" PROJECT_VERSION="${PROJECT_VERSION-$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' VERSION)}"
# Get the Git information from the netbox directory # Get the Git information from the netbox directory
if [ -d "${NETBOX_PATH}/.git" ] && [ -z "${SKIP_GIT}" ]; then if [ -d "${NETBOX_PATH}/.git" ]; then
NETBOX_GIT_REF=$( NETBOX_GIT_REF=$(
cd "${NETBOX_PATH}" cd "${NETBOX_PATH}"
git rev-parse HEAD git rev-parse HEAD
@ -265,194 +220,193 @@ develop)
esac esac
### ###
# composing the final TARGET_DOCKER_TAG # Determine targets to build
### ###
TARGET_DOCKER_TAG="${DOCKER_TAG-${TAG}}" DEFAULT_DOCKER_TARGETS=("main" "ldap")
TARGET_DOCKER_TAG_PROJECT="${TARGET_DOCKER_TAG}-${PROJECT_VERSION}" DOCKER_TARGETS=("${DOCKER_TARGET:-"${DEFAULT_DOCKER_TARGETS[@]}"}")
echo "🏭 Building the following targets:" "${DOCKER_TARGETS[@]}"
gh_echo "::endgroup::"
### ###
# composing the additional DOCKER_SHORT_TAG, # Build each target
# i.e. "v2.6.1" becomes "v2.6",
# which is only relevant for version tags
# Also let "latest" follow the highest version
### ###
if [[ "${TAG}" =~ ^v([0-9]+)\.([0-9]+)\.[0-9]+$ ]]; then export DOCKER_BUILDKIT=${DOCKER_BUILDKIT-1}
MAJOR=${BASH_REMATCH[1]} for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
MINOR=${BASH_REMATCH[2]} gh_echo "::group::🏗 Building the target '${DOCKER_TARGET}'"
echo "🏗 Building the target '${DOCKER_TARGET}'"
TARGET_DOCKER_SHORT_TAG="${DOCKER_SHORT_TAG-v${MAJOR}.${MINOR}}" ###
TARGET_DOCKER_LATEST_TAG="latest" # composing the final TARGET_DOCKER_TAG
TARGET_DOCKER_SHORT_TAG_PROJECT="${TARGET_DOCKER_SHORT_TAG}-${PROJECT_VERSION}" ###
TARGET_DOCKER_LATEST_TAG_PROJECT="${TARGET_DOCKER_LATEST_TAG}-${PROJECT_VERSION}" TARGET_DOCKER_TAG="${DOCKER_TAG-${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_REPO}:${TAG}}"
fi if [ "${DOCKER_TARGET}" != "main" ]; then
TARGET_DOCKER_TAG="${TARGET_DOCKER_TAG}-${DOCKER_TARGET}"
IMAGE_NAME_TAGS=()
for IMAGE_NAME in "${IMAGE_NAMES[@]}"; do
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_TAG}")
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_TAG_PROJECT}")
done
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
for IMAGE_NAME in "${IMAGE_NAMES[@]}"; do
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_SHORT_TAG}")
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_SHORT_TAG_PROJECT}")
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_LATEST_TAG}")
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_LATEST_TAG_PROJECT}")
done
fi
gh_env "FINAL_DOCKER_TAG=${IMAGE_NAME_TAGS[0]}"
###
# Checking if the build is necessary,
# meaning build only if one of those values changed:
# - base image digest
# - netbox git ref (Label: netbox.git-ref)
# - netbox-docker git ref (Label: org.opencontainers.image.revision)
###
# Load information from registry (only for docker.io)
SHOULD_BUILD="false"
BUILD_REASON=""
if [ -z "${GH_ACTION}" ]; then
# Asuming non Github builds should always proceed
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} interactive"
elif [[ "${IMAGE_NAME_TAGS[0]}" = docker.io* ]]; then
source ./build-functions/get-public-image-config.sh
IFS=':' read -ra DOCKER_FROM_SPLIT <<<"${DOCKER_FROM}"
if ! [[ ${DOCKER_FROM_SPLIT[0]} =~ .*/.* ]]; then
# Need to use "library/..." for images the have no two part name
DOCKER_FROM_SPLIT[0]="library/${DOCKER_FROM_SPLIT[0]}"
fi fi
IFS='/' read -ra ORG_REPO <<<"${IMAGE_NAMES[0]}" TARGET_DOCKER_TAG_PROJECT="${TARGET_DOCKER_TAG}-${PROJECT_VERSION}"
echo "Checking labels for '${ORG_REPO[1]}' and '${ORG_REPO[2]}'"
BASE_LAST_LAYER=$(get_image_last_layer "${DOCKER_FROM_SPLIT[0]}" "${DOCKER_FROM_SPLIT[1]}")
mapfile -t IMAGES_LAYERS_OLD < <(get_image_layers "${ORG_REPO[1]}"/"${ORG_REPO[2]}" "${TAG}")
NETBOX_GIT_REF_OLD=$(get_image_label netbox.git-ref "${ORG_REPO[1]}"/"${ORG_REPO[2]}" "${TAG}")
GIT_REF_OLD=$(get_image_label org.opencontainers.image.revision "${ORG_REPO[1]}"/"${ORG_REPO[2]}" "${TAG}")
if ! printf '%s\n' "${IMAGES_LAYERS_OLD[@]}" | grep -q -P "^${BASE_LAST_LAYER}\$"; then gh_env "FINAL_DOCKER_TAG=${TARGET_DOCKER_TAG_PROJECT}"
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} debian"
fi
if [ "${NETBOX_GIT_REF}" != "${NETBOX_GIT_REF_OLD}" ]; then
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} netbox"
fi
if [ "${GIT_REF}" != "${GIT_REF_OLD}" ]; then
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} netbox-docker"
fi
else
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} no-check"
fi
if [ "${SHOULD_BUILD}" != "true" ]; then
echo "Build skipped because sources didn't change"
echo "::set-output name=skipped::true"
exit 0 # Nothing to do -> exit
else
gh_echo "::set-output name=skipped::false" gh_echo "::set-output name=skipped::false"
fi
gh_echo "::endgroup::"
### ###
# Build the image # composing the additional DOCKER_SHORT_TAG,
### # i.e. "v2.6.1" becomes "v2.6",
gh_echo "::group::🏗 Building the image" # which is only relevant for version tags
### # Also let "latest" follow the highest version
# Composing all arguments for `docker build` ###
### if [[ "${TAG}" =~ ^v([0-9]+)\.([0-9]+)\.[0-9]+$ ]]; then
DOCKER_BUILD_ARGS=( MAJOR=${BASH_REMATCH[1]}
--pull MINOR=${BASH_REMATCH[2]}
--target main
-f "${DOCKERFILE}" TARGET_DOCKER_SHORT_TAG="${DOCKER_SHORT_TAG-${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_REPO}:v${MAJOR}.${MINOR}}"
) TARGET_DOCKER_LATEST_TAG="${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_REPO}:latest"
for IMAGE_NAME in "${IMAGE_NAME_TAGS[@]}"; do
DOCKER_BUILD_ARGS+=(-t "${IMAGE_NAME}") if [ "${DOCKER_TARGET}" != "main" ]; then
TARGET_DOCKER_SHORT_TAG="${TARGET_DOCKER_SHORT_TAG}-${DOCKER_TARGET}"
TARGET_DOCKER_LATEST_TAG="${TARGET_DOCKER_LATEST_TAG}-${DOCKER_TARGET}"
fi
TARGET_DOCKER_SHORT_TAG_PROJECT="${TARGET_DOCKER_SHORT_TAG}-${PROJECT_VERSION}"
TARGET_DOCKER_LATEST_TAG_PROJECT="${TARGET_DOCKER_LATEST_TAG}-${PROJECT_VERSION}"
fi
###
# Proceeding to buils stage, except if `--push-only` is passed
###
if [ "${2}" != "--push-only" ]; then
###
# Checking if the build is necessary,
# meaning build only if one of those values changed:
# - Python base image digest (Label: PYTHON_BASE_DIGEST)
# - netbox git ref (Label: NETBOX_GIT_REF)
# - netbox-docker git ref (Label: org.label-schema.vcs-ref)
###
# Load information from registry (only for docker.io)
SHOULD_BUILD="false"
BUILD_REASON=""
if [ -z "${GH_ACTION}" ]; then
# Asuming non Github builds should always proceed
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} interactive"
elif [ "$DOCKER_REGISTRY" = "docker.io" ]; then
source ./build-functions/get-public-image-config.sh
IFS=':' read -ra DOCKER_FROM_SPLIT <<<"${DOCKER_FROM}"
if ! [[ ${DOCKER_FROM_SPLIT[0]} =~ .*/.* ]]; then
# Need to use "library/..." for images the have no two part name
DOCKER_FROM_SPLIT[0]="library/${DOCKER_FROM_SPLIT[0]}"
fi
PYTHON_LAST_LAYER=$(get_image_last_layer "${DOCKER_FROM_SPLIT[0]}" "${DOCKER_FROM_SPLIT[1]}")
mapfile -t IMAGES_LAYERS_OLD < <(get_image_layers "${DOCKER_ORG}"/"${DOCKER_REPO}" "${TAG}")
NETBOX_GIT_REF_OLD=$(get_image_label NETBOX_GIT_REF "${DOCKER_ORG}"/"${DOCKER_REPO}" "${TAG}")
GIT_REF_OLD=$(get_image_label org.label-schema.vcs-ref "${DOCKER_ORG}"/"${DOCKER_REPO}" "${TAG}")
if ! printf '%s\n' "${IMAGES_LAYERS_OLD[@]}" | grep -q -P "^${PYTHON_LAST_LAYER}\$"; then
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} alpine"
fi
if [ "${NETBOX_GIT_REF}" != "${NETBOX_GIT_REF_OLD}" ]; then
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} netbox"
fi
if [ "${GIT_REF}" != "${GIT_REF_OLD}" ]; then
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} netbox-docker"
fi
else
SHOULD_BUILD="true"
BUILD_REASON="${BUILD_REASON} no-check"
fi
###
# Composing all arguments for `docker build`
###
DOCKER_BUILD_ARGS=(
--pull
--target "${DOCKER_TARGET}"
-f "${DOCKERFILE}"
-t "${TARGET_DOCKER_TAG}"
-t "${TARGET_DOCKER_TAG_PROJECT}"
)
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
DOCKER_BUILD_ARGS+=(-t "${TARGET_DOCKER_SHORT_TAG}")
DOCKER_BUILD_ARGS+=(-t "${TARGET_DOCKER_SHORT_TAG_PROJECT}")
DOCKER_BUILD_ARGS+=(-t "${TARGET_DOCKER_LATEST_TAG}")
DOCKER_BUILD_ARGS+=(-t "${TARGET_DOCKER_LATEST_TAG_PROJECT}")
fi
# --label
DOCKER_BUILD_ARGS+=(
--label "ORIGINAL_TAG=${TARGET_DOCKER_TAG_PROJECT}"
--label "org.label-schema.build-date=${BUILD_DATE}"
--label "org.opencontainers.image.created=${BUILD_DATE}"
--label "org.label-schema.version=${PROJECT_VERSION}"
--label "org.opencontainers.image.version=${PROJECT_VERSION}"
)
if [ -d ".git" ]; then
DOCKER_BUILD_ARGS+=(
--label "org.label-schema.vcs-ref=${GIT_REF}"
--label "org.opencontainers.image.revision=${GIT_REF}"
)
fi
if [ -d "${NETBOX_PATH}/.git" ]; then
DOCKER_BUILD_ARGS+=(
--label "NETBOX_GIT_BRANCH=${NETBOX_GIT_BRANCH}"
--label "NETBOX_GIT_REF=${NETBOX_GIT_REF}"
--label "NETBOX_GIT_URL=${NETBOX_GIT_URL}"
)
fi
if [ -n "${BUILD_REASON}" ]; then
BUILD_REASON=$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<<"$BUILD_REASON")
DOCKER_BUILD_ARGS+=(--label "BUILD_REASON=${BUILD_REASON}")
fi
# --build-arg
DOCKER_BUILD_ARGS+=(--build-arg "NETBOX_PATH=${NETBOX_PATH}")
if [ -n "${DOCKER_FROM}" ]; then
DOCKER_BUILD_ARGS+=(--build-arg "FROM=${DOCKER_FROM}")
fi
# shellcheck disable=SC2031
if [ -n "${HTTP_PROXY}" ]; then
DOCKER_BUILD_ARGS+=(--build-arg "http_proxy=${HTTP_PROXY}")
DOCKER_BUILD_ARGS+=(--build-arg "https_proxy=${HTTPS_PROXY}")
fi
if [ -n "${NO_PROXY}" ]; then
DOCKER_BUILD_ARGS+=(--build-arg "no_proxy=${NO_PROXY}")
fi
###
# Building the docker image
###
if [ "${SHOULD_BUILD}" == "true" ]; then
echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG_PROJECT}'."
echo " Build reason set to: ${BUILD_REASON}"
$DRY docker build "${DOCKER_BUILD_ARGS[@]}" .
echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG_PROJECT}'"
echo "🔎 Inspecting labels on '${TARGET_DOCKER_TAG_PROJECT}'"
$DRY docker inspect "${TARGET_DOCKER_TAG_PROJECT}" --format "{{json .Config.Labels}}"
else
echo "Build skipped because sources didn't change"
echo "::set-output name=skipped::true"
fi
fi
###
# Pushing the docker images if either `--push` or `--push-only` are passed
###
if [ "${2}" == "--push" ] || [ "${2}" == "--push-only" ]; then
source ./build-functions/docker-functions.sh
push_image_to_registry "${TARGET_DOCKER_TAG}"
push_image_to_registry "${TARGET_DOCKER_TAG_PROJECT}"
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
push_image_to_registry "${TARGET_DOCKER_SHORT_TAG}"
push_image_to_registry "${TARGET_DOCKER_SHORT_TAG_PROJECT}"
push_image_to_registry "${TARGET_DOCKER_LATEST_TAG}"
push_image_to_registry "${TARGET_DOCKER_LATEST_TAG_PROJECT}"
fi
fi
gh_echo "::endgroup::"
done done
# --label
DOCKER_BUILD_ARGS+=(
--label "netbox.original-tag=${TARGET_DOCKER_TAG_PROJECT}"
--label "org.opencontainers.image.created=${BUILD_DATE}"
--label "org.opencontainers.image.version=${PROJECT_VERSION}"
)
if [ -d ".git" ] && [ -z "${SKIP_GIT}" ]; then
DOCKER_BUILD_ARGS+=(
--label "org.opencontainers.image.revision=${GIT_REF}"
)
fi
if [ -d "${NETBOX_PATH}/.git" ] && [ -z "${SKIP_GIT}" ]; then
DOCKER_BUILD_ARGS+=(
--label "netbox.git-branch=${NETBOX_GIT_BRANCH}"
--label "netbox.git-ref=${NETBOX_GIT_REF}"
--label "netbox.git-url=${NETBOX_GIT_URL}"
)
fi
if [ -n "${BUILD_REASON}" ]; then
BUILD_REASON=$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<<"$BUILD_REASON")
DOCKER_BUILD_ARGS+=(--label "netbox.build-reason=${BUILD_REASON}")
fi
# --build-arg
DOCKER_BUILD_ARGS+=(--build-arg "NETBOX_PATH=${NETBOX_PATH}")
if [ -n "${DOCKER_FROM}" ]; then
DOCKER_BUILD_ARGS+=(--build-arg "FROM=${DOCKER_FROM}")
fi
# shellcheck disable=SC2031
if [ -n "${HTTP_PROXY}" ]; then
DOCKER_BUILD_ARGS+=(--build-arg "http_proxy=${HTTP_PROXY}")
DOCKER_BUILD_ARGS+=(--build-arg "https_proxy=${HTTPS_PROXY}")
fi
if [ -n "${NO_PROXY}" ]; then
DOCKER_BUILD_ARGS+=(--build-arg "no_proxy=${NO_PROXY}")
fi
DOCKER_BUILD_ARGS+=(--platform "${BUILDX_PLATFORM-linux/amd64}")
if [ "${2}" == "--push" ]; then
# output type=docker does not work with pushing
DOCKER_BUILD_ARGS+=(
--output=type=image
--push
)
else
DOCKER_BUILD_ARGS+=(
--output=type=docker
)
fi
###
# Building the docker image
###
if [ -z "${BUILDX_BUILDER_NAME}" ]; then
BUILDX_BUILDER_NAME="$(basename "${PWD}")"
fi
if ! docker buildx ls | grep --quiet --word-regexp "${BUILDX_BUILDER_NAME}"; then
echo "👷 Creating new Buildx Builder '${BUILDX_BUILDER_NAME}'"
$DRY docker buildx create --name "${BUILDX_BUILDER_NAME}"
BUILDX_BUILDER_CREATED="yes"
fi
echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG_PROJECT}'."
echo " Build reason set to: ${BUILD_REASON}"
$DRY docker buildx \
--builder "${BUILDX_BUILDER_NAME}" \
build \
"${DOCKER_BUILD_ARGS[@]}" \
.
echo "✅ Finished building the Docker images"
gh_echo "::endgroup::" # End group for Build
gh_echo "::group::🏗 Image Labels"
echo "🔎 Inspecting labels on '${IMAGE_NAME_TAGS[0]}'"
$DRY docker inspect "${IMAGE_NAME_TAGS[0]}" --format "{{json .Config.Labels}}" | jq
gh_echo "::endgroup::"
gh_echo "::group::🏗 Clean up"
if [ -n "${BUILDX_REMOVE_BUILDER}" ] && [ "${BUILDX_BUILDER_CREATED}" == "yes" ]; then
echo "👷 Removing Buildx Builder '${BUILDX_BUILDER_NAME}'"
$DRY docker buildx rm "${BUILDX_BUILDER_NAME}"
fi
gh_echo "::endgroup::"

View File

@ -7,17 +7,12 @@
import re import re
from os import environ from os import environ
from os.path import abspath, dirname, join from os.path import abspath, dirname, join
from typing import Any, Callable, Tuple
# For reference see https://docs.netbox.dev/en/stable/configuration/ # For reference see https://netbox.readthedocs.io/en/stable/configuration/
# Based on https://github.com/netbox-community/netbox/blob/develop/netbox/netbox/configuration_example.py # Based on https://github.com/netbox-community/netbox/blob/master/netbox/netbox/configuration.example.py
###
# NetBox-Docker Helper functions
###
# Read secret from file # Read secret from file
def _read_secret(secret_name: str, default: str | None = None) -> str | None: def _read_secret(secret_name, default = None):
try: try:
f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8') f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8')
except EnvironmentError: except EnvironmentError:
@ -26,25 +21,6 @@ def _read_secret(secret_name: str, default: str | None = None) -> str | None:
with f: with f:
return f.readline().strip() return f.readline().strip()
# If the `map_fn` isn't defined, then the value that is read from the environment (or the default value if not found) is returned.
# If the `map_fn` is defined, then `map_fn` is invoked and the value (that was read from the environment or the default value if not found)
# is passed to it as a parameter. The value returned from `map_fn` is then the return value of this function.
# The `map_fn` is not invoked, if the value (that was read from the environment or the default value if not found) is None.
def _environ_get_and_map(variable_name: str, default: str | None = None, map_fn: Callable[[str], Any | None] = None) -> Any | None:
env_value = environ.get(variable_name, default)
if env_value == None:
return env_value
if not map_fn:
return env_value
return map_fn(env_value)
_AS_BOOL = lambda value : value.lower() == 'true'
_AS_INT = lambda value : int(value)
_AS_LIST = lambda value : list(filter(None, value.split(' ')))
_BASE_DIR = dirname(dirname(abspath(__file__))) _BASE_DIR = dirname(dirname(abspath(__file__)))
######################### #########################
@ -70,9 +46,9 @@ DATABASE = {
'PORT': environ.get('DB_PORT', ''), # Database port (leave blank for default) 'PORT': environ.get('DB_PORT', ''), # Database port (leave blank for default)
'OPTIONS': {'sslmode': environ.get('DB_SSLMODE', 'prefer')}, 'OPTIONS': {'sslmode': environ.get('DB_SSLMODE', 'prefer')},
# Database connection SSLMODE # Database connection SSLMODE
'CONN_MAX_AGE': _environ_get_and_map('DB_CONN_MAX_AGE', '300', _AS_INT), 'CONN_MAX_AGE': int(environ.get('DB_CONN_MAX_AGE', '300')),
# Max database connection age # Max database connection age
'DISABLE_SERVER_SIDE_CURSORS': _environ_get_and_map('DB_DISABLE_SERVER_SIDE_CURSORS', 'False', _AS_BOOL), 'DISABLE_SERVER_SIDE_CURSORS': environ.get('DB_DISABLE_SERVER_SIDE_CURSORS', 'False').lower() == 'true',
# Disable the use of server-side cursors transaction pooling # Disable the use of server-side cursors transaction pooling
} }
@ -82,19 +58,19 @@ DATABASE = {
REDIS = { REDIS = {
'tasks': { 'tasks': {
'HOST': environ.get('REDIS_HOST', 'localhost'), 'HOST': environ.get('REDIS_HOST', 'localhost'),
'PORT': _environ_get_and_map('REDIS_PORT', 6379, _AS_INT), 'PORT': int(environ.get('REDIS_PORT', 6379)),
'PASSWORD': _read_secret('redis_password', environ.get('REDIS_PASSWORD', '')), 'PASSWORD': _read_secret('redis_password', environ.get('REDIS_PASSWORD', '')),
'DATABASE': _environ_get_and_map('REDIS_DATABASE', 0, _AS_INT), 'DATABASE': int(environ.get('REDIS_DATABASE', 0)),
'SSL': _environ_get_and_map('REDIS_SSL', 'False', _AS_BOOL), 'SSL': environ.get('REDIS_SSL', 'False').lower() == 'true',
'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False', _AS_BOOL), 'INSECURE_SKIP_TLS_VERIFY': environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False').lower() == 'true',
}, },
'caching': { 'caching': {
'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')), 'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')),
'PORT': _environ_get_and_map('REDIS_CACHE_PORT', environ.get('REDIS_PORT', '6379'), _AS_INT), 'PORT': int(environ.get('REDIS_CACHE_PORT', environ.get('REDIS_PORT', 6379))),
'PASSWORD': _read_secret('redis_cache_password', environ.get('REDIS_CACHE_PASSWORD', environ.get('REDIS_PASSWORD', ''))), 'PASSWORD': _read_secret('redis_cache_password', environ.get('REDIS_CACHE_PASSWORD', environ.get('REDIS_PASSWORD', ''))),
'DATABASE': _environ_get_and_map('REDIS_CACHE_DATABASE', '1', _AS_INT), 'DATABASE': int(environ.get('REDIS_CACHE_DATABASE', 1)),
'SSL': _environ_get_and_map('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False'), _AS_BOOL), 'SSL': environ.get('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False')).lower() == 'true',
'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY', environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False'), _AS_BOOL), 'INSECURE_SKIP_TLS_VERIFY': environ.get('REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY', environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False')).lower() == 'true',
}, },
} }
@ -111,217 +87,153 @@ SECRET_KEY = _read_secret('secret_key', environ.get('SECRET_KEY', ''))
# # # #
######################### #########################
# # Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of # Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of
# # application errors (assuming correct email settings are provided). # application errors (assuming correct email settings are provided).
# ADMINS = [ ADMINS = [
# # ['John Doe', 'jdoe@example.com'], # ['John Doe', 'jdoe@example.com'],
# ] ]
if 'ALLOWED_URL_SCHEMES' in environ: # URL schemes that are allowed within links in NetBox
ALLOWED_URL_SCHEMES = _environ_get_and_map('ALLOWED_URL_SCHEMES', None, _AS_LIST) ALLOWED_URL_SCHEMES = (
'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp',
)
# Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same # Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same
# content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP. # content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
if 'BANNER_TOP' in environ: BANNER_TOP = environ.get('BANNER_TOP', '')
BANNER_TOP = environ.get('BANNER_TOP', None) BANNER_BOTTOM = environ.get('BANNER_BOTTOM', '')
if 'BANNER_BOTTOM' in environ:
BANNER_BOTTOM = environ.get('BANNER_BOTTOM', None)
# Text to include on the login page above the login form. HTML is allowed. # Text to include on the login page above the login form. HTML is allowed.
if 'BANNER_LOGIN' in environ: BANNER_LOGIN = environ.get('BANNER_LOGIN', '')
BANNER_LOGIN = environ.get('BANNER_LOGIN', None)
# Base URL path if accessing NetBox within a directory. For example, if installed at http://example.com/netbox/, set: # Base URL path if accessing NetBox within a directory. For example, if installed at http://example.com/netbox/, set:
# BASE_PATH = 'netbox/' # BASE_PATH = 'netbox/'
BASE_PATH = environ.get('BASE_PATH', '') BASE_PATH = environ.get('BASE_PATH', '')
# Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90) # Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90)
if 'CHANGELOG_RETENTION' in environ: CHANGELOG_RETENTION = int(environ.get('CHANGELOG_RETENTION', 90))
CHANGELOG_RETENTION = _environ_get_and_map('CHANGELOG_RETENTION', None, _AS_INT)
# Maximum number of days to retain job results (scripts and reports). Set to 0 to retain job results in the database indefinitely. (Default: 90)
if 'JOBRESULT_RETENTION' in environ:
JOBRESULT_RETENTION = _environ_get_and_map('JOBRESULT_RETENTION', None, _AS_INT)
# API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be # API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be
# allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or # allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or
# CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers # CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers
CORS_ORIGIN_ALLOW_ALL = _environ_get_and_map('CORS_ORIGIN_ALLOW_ALL', 'False', _AS_BOOL) CORS_ORIGIN_ALLOW_ALL = environ.get('CORS_ORIGIN_ALLOW_ALL', 'False').lower() == 'true'
CORS_ORIGIN_WHITELIST = _environ_get_and_map('CORS_ORIGIN_WHITELIST', 'https://localhost', _AS_LIST) CORS_ORIGIN_WHITELIST = list(filter(None, environ.get('CORS_ORIGIN_WHITELIST', 'https://localhost').split(' ')))
CORS_ORIGIN_REGEX_WHITELIST = [re.compile(r) for r in _environ_get_and_map('CORS_ORIGIN_REGEX_WHITELIST', '', _AS_LIST)] CORS_ORIGIN_REGEX_WHITELIST = [re.compile(r) for r in list(filter(None, environ.get('CORS_ORIGIN_REGEX_WHITELIST', '').split(' ')))]
# Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal # Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal
# sensitive information about your installation. Only enable debugging while performing testing. # sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging
# Never enable debugging on a production system. # on a production system.
DEBUG = _environ_get_and_map('DEBUG', 'False', _AS_BOOL) DEBUG = environ.get('DEBUG', 'False').lower() == 'true'
# This parameter serves as a safeguard to prevent some potentially dangerous behavior,
# such as generating new database schema migrations.
# Set this to True only if you are actively developing the NetBox code base.
DEVELOPER = _environ_get_and_map('DEVELOPER', 'False', _AS_BOOL)
# Email settings # Email settings
EMAIL = { EMAIL = {
'SERVER': environ.get('EMAIL_SERVER', 'localhost'), 'SERVER': environ.get('EMAIL_SERVER', 'localhost'),
'PORT': _environ_get_and_map('EMAIL_PORT', 25, _AS_INT), 'PORT': int(environ.get('EMAIL_PORT', 25)),
'USERNAME': environ.get('EMAIL_USERNAME', ''), 'USERNAME': environ.get('EMAIL_USERNAME', ''),
'PASSWORD': _read_secret('email_password', environ.get('EMAIL_PASSWORD', '')), 'PASSWORD': _read_secret('email_password', environ.get('EMAIL_PASSWORD', '')),
'USE_SSL': _environ_get_and_map('EMAIL_USE_SSL', 'False', _AS_BOOL), 'USE_SSL': environ.get('EMAIL_USE_SSL', 'False').lower() == 'true',
'USE_TLS': _environ_get_and_map('EMAIL_USE_TLS', 'False', _AS_BOOL), 'USE_TLS': environ.get('EMAIL_USE_TLS', 'False').lower() == 'true',
'SSL_CERTFILE': environ.get('EMAIL_SSL_CERTFILE', ''), 'SSL_CERTFILE': environ.get('EMAIL_SSL_CERTFILE', ''),
'SSL_KEYFILE': environ.get('EMAIL_SSL_KEYFILE', ''), 'SSL_KEYFILE': environ.get('EMAIL_SSL_KEYFILE', ''),
'TIMEOUT': _environ_get_and_map('EMAIL_TIMEOUT', 10, _AS_INT), # seconds 'TIMEOUT': int(environ.get('EMAIL_TIMEOUT', 10)), # seconds
'FROM_EMAIL': environ.get('EMAIL_FROM', ''), 'FROM_EMAIL': environ.get('EMAIL_FROM', ''),
} }
# Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space within the global table # Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space within the global table
# (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True. # (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True.
if 'ENFORCE_GLOBAL_UNIQUE' in environ: ENFORCE_GLOBAL_UNIQUE = environ.get('ENFORCE_GLOBAL_UNIQUE', 'False').lower() == 'true'
ENFORCE_GLOBAL_UNIQUE = _environ_get_and_map('ENFORCE_GLOBAL_UNIQUE', None, _AS_BOOL)
# Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and # Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and
# by anonymous users. List models in the form `<app>.<model>`. Add '*' to this list to exempt all models. # by anonymous users. List models in the form `<app>.<model>`. Add '*' to this list to exempt all models.
EXEMPT_VIEW_PERMISSIONS = _environ_get_and_map('EXEMPT_VIEW_PERMISSIONS', '', _AS_LIST) EXEMPT_VIEW_PERMISSIONS = list(filter(None, environ.get('EXEMPT_VIEW_PERMISSIONS', '').split(' ')))
# HTTP proxies NetBox should use when sending outbound HTTP requests (e.g. for webhooks).
# HTTP_PROXIES = {
# 'http': 'http://10.10.1.10:3128',
# 'https': 'http://10.10.1.10:1080',
# }
# IP addresses recognized as internal to the system. The debugging toolbar will be available only to clients accessing
# NetBox from an internal IP.
INTERNAL_IPS = _environ_get_and_map('INTERNAL_IPS', '127.0.0.1 ::1', _AS_LIST)
# Enable GraphQL API. # Enable GraphQL API.
if 'GRAPHQL_ENABLED' in environ: GRAPHQL_ENABLED = environ.get('GRAPHQL_ENABLED', 'True').lower() == 'true'
GRAPHQL_ENABLED = _environ_get_and_map('GRAPHQL_ENABLED', None, _AS_BOOL)
# # Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs: # Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
# # https://docs.djangoproject.com/en/stable/topics/logging/ # https://docs.djangoproject.com/en/stable/topics/logging/
# LOGGING = {} LOGGING = {}
# Automatically reset the lifetime of a valid session upon each authenticated request. Enables users to remain
# authenticated to NetBox indefinitely.
LOGIN_PERSISTENCE = _environ_get_and_map('LOGIN_PERSISTENCE', 'False', _AS_BOOL)
# Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users # Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users
# are permitted to access most data in NetBox (excluding secrets) but not make any changes. # are permitted to access most data in NetBox (excluding secrets) but not make any changes.
LOGIN_REQUIRED = _environ_get_and_map('LOGIN_REQUIRED', 'False', _AS_BOOL) 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_and_map('LOGIN_TIMEOUT', 1209600, _AS_INT) 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.
if 'MAINTENANCE_MODE' in environ: MAINTENANCE_MODE = environ.get('MAINTENANCE_MODE', 'False').lower() == 'true'
MAINTENANCE_MODE = _environ_get_and_map('MAINTENANCE_MODE', None, _AS_BOOL)
# Maps provider
if 'MAPS_URL' in environ:
MAPS_URL = environ.get('MAPS_URL', None)
# An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g. # An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g.
# "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request # "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request
# all objects by specifying "?limit=0". # all objects by specifying "?limit=0".
if 'MAX_PAGE_SIZE' in environ: MAX_PAGE_SIZE = int(environ.get('MAX_PAGE_SIZE', 1000))
MAX_PAGE_SIZE = _environ_get_and_map('MAX_PAGE_SIZE', None, _AS_INT)
# The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that # The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that
# the default value of this setting is derived from the installed location. # the default value of this setting is derived from the installed location.
MEDIA_ROOT = environ.get('MEDIA_ROOT', join(_BASE_DIR, 'media')) MEDIA_ROOT = environ.get('MEDIA_ROOT', join(_BASE_DIR, 'media'))
# Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics' # Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics'
METRICS_ENABLED = _environ_get_and_map('METRICS_ENABLED', 'False', _AS_BOOL) METRICS_ENABLED = environ.get('METRICS_ENABLED', 'False').lower() == 'true'
# Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM. # Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM.
if 'NAPALM_USERNAME' in environ: NAPALM_USERNAME = environ.get('NAPALM_USERNAME', '')
NAPALM_USERNAME = environ.get('NAPALM_USERNAME', None) NAPALM_PASSWORD = _read_secret('napalm_password', environ.get('NAPALM_PASSWORD', ''))
if 'NAPALM_PASSWORD' in environ:
NAPALM_PASSWORD = _read_secret('napalm_password', environ.get('NAPALM_PASSWORD', None))
# NAPALM timeout (in seconds). (Default: 30) # NAPALM timeout (in seconds). (Default: 30)
if 'NAPALM_TIMEOUT' in environ: NAPALM_TIMEOUT = int(environ.get('NAPALM_TIMEOUT', 30))
NAPALM_TIMEOUT = _environ_get_and_map('NAPALM_TIMEOUT', None, _AS_INT)
# # NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must # NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must
# # be provided as a dictionary. # be provided as a dictionary.
# NAPALM_ARGS = None NAPALM_ARGS = {}
# Determine how many objects to display per page within a list. (Default: 50) # Determine how many objects to display per page within a list. (Default: 50)
if 'PAGINATE_COUNT' in environ: PAGINATE_COUNT = int(environ.get('PAGINATE_COUNT', 50))
PAGINATE_COUNT = _environ_get_and_map('PAGINATE_COUNT', None, _AS_INT)
# # Enable installed plugins. Add the name of each plugin to the list. # Enable installed plugins. Add the name of each plugin to the list.
# PLUGINS = [] PLUGINS = []
# # Plugins configuration settings. These settings are used by various plugins that the user may have installed. # Plugins configuration settings. These settings are used by various plugins that the user may have installed.
# # Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings. # Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings.
# PLUGINS_CONFIG = { PLUGINS_CONFIG = {
# } }
# When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to # When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to
# prefer IPv4 instead. # prefer IPv4 instead.
if 'PREFER_IPV4' in environ: PREFER_IPV4 = environ.get('PREFER_IPV4', 'False').lower() == 'true'
PREFER_IPV4 = _environ_get_and_map('PREFER_IPV4', None, _AS_BOOL)
# The default value for the amperage field when creating new power feeds.
if 'POWERFEED_DEFAULT_AMPERAGE' in environ:
POWERFEED_DEFAULT_AMPERAGE = _environ_get_and_map('POWERFEED_DEFAULT_AMPERAGE', None, _AS_INT)
# The default value (percentage) for the max_utilization field when creating new power feeds.
if 'POWERFEED_DEFAULT_MAX_UTILIZATION' in environ:
POWERFEED_DEFAULT_MAX_UTILIZATION = _environ_get_and_map('POWERFEED_DEFAULT_MAX_UTILIZATION', None, _AS_INT)
# The default value for the voltage field when creating new power feeds.
if 'POWERFEED_DEFAULT_VOLTAGE' in environ:
POWERFEED_DEFAULT_VOLTAGE = _environ_get_and_map('POWERFEED_DEFAULT_VOLTAGE', None, _AS_INT)
# Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1. # Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1.
if 'RACK_ELEVATION_DEFAULT_UNIT_HEIGHT' in environ: RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = int(environ.get('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 22))
RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', None, _AS_INT) RACK_ELEVATION_DEFAULT_UNIT_WIDTH = int(environ.get('RACK_ELEVATION_DEFAULT_UNIT_WIDTH', 220))
if 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH' in environ:
RACK_ELEVATION_DEFAULT_UNIT_WIDTH = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_WIDTH', None, _AS_INT)
# Remote authentication support # Remote authentication support
REMOTE_AUTH_ENABLED = _environ_get_and_map('REMOTE_AUTH_ENABLED', 'False', _AS_BOOL) REMOTE_AUTH_ENABLED = environ.get('REMOTE_AUTH_ENABLED', 'False').lower() == 'true'
REMOTE_AUTH_BACKEND = environ.get('REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend') REMOTE_AUTH_BACKEND = environ.get('REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend')
REMOTE_AUTH_HEADER = environ.get('REMOTE_AUTH_HEADER', 'HTTP_REMOTE_USER') REMOTE_AUTH_HEADER = environ.get('REMOTE_AUTH_HEADER', 'HTTP_REMOTE_USER')
REMOTE_AUTH_AUTO_CREATE_USER = _environ_get_and_map('REMOTE_AUTH_AUTO_CREATE_USER', 'True', _AS_BOOL) REMOTE_AUTH_AUTO_CREATE_USER = environ.get('REMOTE_AUTH_AUTO_CREATE_USER', 'True').lower() == 'true'
REMOTE_AUTH_DEFAULT_GROUPS = _environ_get_and_map('REMOTE_AUTH_DEFAULT_GROUPS', '', _AS_LIST) REMOTE_AUTH_DEFAULT_GROUPS = list(filter(None, environ.get('REMOTE_AUTH_DEFAULT_GROUPS', '').split(' ')))
# REMOTE_AUTH_DEFAULT_PERMISSIONS = {}
# This repository is used to check whether there is a new release of NetBox available. Set to None to disable the # This repository is used to check whether there is a new release of NetBox available. Set to None to disable the
# version check or use the URL below to check for release in the official NetBox repository. # version check or use the URL below to check for release in the official NetBox repository.
# https://api.github.com/repos/netbox-community/netbox/releases
RELEASE_CHECK_URL = environ.get('RELEASE_CHECK_URL', None) RELEASE_CHECK_URL = environ.get('RELEASE_CHECK_URL', None)
# RELEASE_CHECK_URL = 'https://api.github.com/repos/netbox-community/netbox/releases'
# The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of # The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of
# this setting is derived from the installed location. # this setting is derived from the installed location.
REPORTS_ROOT = environ.get('REPORTS_ROOT', '/etc/netbox/reports') REPORTS_ROOT = environ.get('REPORTS_ROOT', '/etc/netbox/reports')
# Maximum execution time for background tasks, in seconds. # Maximum execution time for background tasks, in seconds.
RQ_DEFAULT_TIMEOUT = _environ_get_and_map('RQ_DEFAULT_TIMEOUT', 300, _AS_INT) RQ_DEFAULT_TIMEOUT = int(environ.get('RQ_DEFAULT_TIMEOUT', 300))
# The file path where custom scripts will be stored. A trailing slash is not needed. Note that the default value of # The file path where custom scripts will be stored. A trailing slash is not needed. Note that the default value of
# this setting is derived from the installed location. # this setting is derived from the installed location.
SCRIPTS_ROOT = environ.get('SCRIPTS_ROOT', '/etc/netbox/scripts') SCRIPTS_ROOT = environ.get('SCRIPTS_ROOT', '/etc/netbox/scripts')
# The name to use for the csrf token cookie.
CSRF_COOKIE_NAME = environ.get('CSRF_COOKIE_NAME', 'csrftoken')
# Cross-Site-Request-Forgery-Attack settings. If Netbox is sitting behind a reverse proxy, you might need to set the CSRF_TRUSTED_ORIGINS flag.
# Django 4.0 requires to specify the URL Scheme in this setting. An example environment variable could be specified like:
# CSRF_TRUSTED_ORIGINS=https://demo.netbox.dev http://demo.netbox.dev
CSRF_TRUSTED_ORIGINS = _environ_get_and_map('CSRF_TRUSTED_ORIGINS', '', _AS_LIST)
# The name to use for the session cookie.
SESSION_COOKIE_NAME = environ.get('SESSION_COOKIE_NAME', 'sessionid')
# 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('SESSION_FILE_PATH', environ.get('SESSIONS_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

@ -31,12 +31,9 @@ AUTH_LDAP_CONNECTION_OPTIONS = {
ldap.OPT_REFERRALS: 0 ldap.OPT_REFERRALS: 0
} }
AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = environ.get('AUTH_LDAP_BIND_AS_AUTHENTICATING_USER', 'False').lower() == 'true' # Set the DN and password for the NetBox service account.
AUTH_LDAP_BIND_DN = environ.get('AUTH_LDAP_BIND_DN', '')
# Set the DN and password for the NetBox service account if needed. AUTH_LDAP_BIND_PASSWORD = _read_secret('auth_ldap_bind_password', environ.get('AUTH_LDAP_BIND_PASSWORD', ''))
if not AUTH_LDAP_BIND_AS_AUTHENTICATING_USER:
AUTH_LDAP_BIND_DN = environ.get('AUTH_LDAP_BIND_DN', '')
AUTH_LDAP_BIND_PASSWORD = _read_secret('auth_ldap_bind_password', environ.get('AUTH_LDAP_BIND_PASSWORD', ''))
# Set a string template that describes any users distinguished name based on the username. # Set a string template that describes any users distinguished name based on the username.
AUTH_LDAP_USER_DN_TEMPLATE = environ.get('AUTH_LDAP_USER_DN_TEMPLATE', None) AUTH_LDAP_USER_DN_TEMPLATE = environ.get('AUTH_LDAP_USER_DN_TEMPLATE', None)
@ -49,16 +46,6 @@ AUTH_LDAP_START_TLS = environ.get('AUTH_LDAP_START_TLS', 'False').lower() == 'tr
# ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) # ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
LDAP_IGNORE_CERT_ERRORS = environ.get('LDAP_IGNORE_CERT_ERRORS', 'False').lower() == 'true' LDAP_IGNORE_CERT_ERRORS = environ.get('LDAP_IGNORE_CERT_ERRORS', 'False').lower() == 'true'
# Include this setting if you want to validate the LDAP server certificates against a CA certificate directory on your server
# Note that this is a NetBox-specific setting which sets:
# ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, LDAP_CA_CERT_DIR)
LDAP_CA_CERT_DIR = environ.get('LDAP_CA_CERT_DIR', None)
# Include this setting if you want to validate the LDAP server certificates against your own CA.
# Note that this is a NetBox-specific setting which sets:
# ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, LDAP_CA_CERT_FILE)
LDAP_CA_CERT_FILE = environ.get('LDAP_CA_CERT_FILE', None)
AUTH_LDAP_USER_SEARCH_BASEDN = environ.get('AUTH_LDAP_USER_SEARCH_BASEDN', '') AUTH_LDAP_USER_SEARCH_BASEDN = environ.get('AUTH_LDAP_USER_SEARCH_BASEDN', '')
AUTH_LDAP_USER_SEARCH_ATTR = environ.get('AUTH_LDAP_USER_SEARCH_ATTR', 'sAMAccountName') AUTH_LDAP_USER_SEARCH_ATTR = environ.get('AUTH_LDAP_USER_SEARCH_ATTR', 'sAMAccountName')
AUTH_LDAP_USER_SEARCH = LDAPSearch( AUTH_LDAP_USER_SEARCH = LDAPSearch(

View File

@ -1,5 +0,0 @@
version: '3.4'
services:
netbox:
ports:
- 127.0.0.1:44156:8080

View File

@ -3,37 +3,32 @@ services:
netbox: netbox:
image: ${IMAGE-netboxcommunity/netbox:latest} image: ${IMAGE-netboxcommunity/netbox:latest}
depends_on: depends_on:
postgres: - postgres
condition: service_healthy - redis
redis: - redis-cache
condition: service_started
redis-cache:
condition: service_started
env_file: env/netbox.env env_file: env/netbox.env
environment:
SKIP_STARTUP_SCRIPTS: ${SKIP_STARTUP_SCRIPTS-false}
user: 'unit:root' user: 'unit:root'
volumes: volumes:
- ./startup_scripts:/opt/netbox/startup_scripts:z,ro
- ./${INITIALIZERS_DIR-initializers}:/opt/netbox/initializers:z,ro
- ./configuration:/etc/netbox/config:z,ro - ./configuration:/etc/netbox/config:z,ro
- ./test-configuration/logging.py:/etc/netbox/config/logging.py: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-media-files:/opt/netbox/netbox/media:z - netbox-media-files:/opt/netbox/netbox/media:z
postgres: postgres:
image: postgres:15-alpine image: postgres:14-alpine
env_file: env/postgres.env env_file: env/postgres.env
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5
redis: redis:
image: redis:7-alpine image: redis:6-alpine
command: command:
- sh - sh
- -c # this is to evaluate the $REDIS_PASSWORD from the env - -c # this is to evaluate the $REDIS_PASSWORD from the env
- redis-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose - redis-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
env_file: env/redis.env env_file: env/redis.env
redis-cache: redis-cache:
image: redis:7-alpine image: redis:6-alpine
command: command:
- sh - sh
- -c # this is to evaluate the $REDIS_PASSWORD from the env - -c # this is to evaluate the $REDIS_PASSWORD from the env

View File

@ -1,19 +1,17 @@
version: '3.4' version: '3.4'
services: services:
netbox: &netbox netbox: &netbox
image: netboxcommunity/netbox:${VERSION-v3.4-2.4.0} image: netboxcommunity/netbox:${VERSION-v3.2-1.6.1}
depends_on: depends_on:
- postgres - postgres
- redis - redis
- redis-cache - redis-cache
- netbox-worker
env_file: env/netbox.env env_file: env/netbox.env
user: 'unit:root' user: 'unit:root'
healthcheck:
start_period: 60s
timeout: 3s
interval: 15s
test: "curl -f http://localhost:8080/api/ || exit 1"
volumes: volumes:
- ./startup_scripts:/opt/netbox/startup_scripts:z,ro
- ./initializers:/opt/netbox/initializers:z,ro
- ./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
@ -21,40 +19,30 @@ services:
netbox-worker: netbox-worker:
<<: *netbox <<: *netbox
depends_on: depends_on:
netbox: - redis
condition: service_healthy - postgres
command: command:
- /opt/netbox/venv/bin/python - /opt/netbox/venv/bin/python
- /opt/netbox/netbox/manage.py - /opt/netbox/netbox/manage.py
- rqworker - rqworker
healthcheck:
start_period: 20s
timeout: 3s
interval: 15s
test: "ps -aux | grep -v grep | grep -q rqworker || exit 1"
netbox-housekeeping: netbox-housekeeping:
<<: *netbox <<: *netbox
depends_on: depends_on:
netbox: - redis
condition: service_healthy - postgres
command: command:
- /opt/netbox/housekeeping.sh - /opt/netbox/housekeeping.sh
healthcheck:
start_period: 20s
timeout: 3s
interval: 15s
test: "ps -aux | grep -v grep | grep -q housekeeping || exit 1"
# postgres # postgres
postgres: postgres:
image: postgres:15-alpine image: postgres:14-alpine
env_file: env/postgres.env env_file: env/postgres.env
volumes: volumes:
- netbox-postgres-data:/var/lib/postgresql/data - netbox-postgres-data:/var/lib/postgresql/data
# redis # redis
redis: redis:
image: redis:7-alpine image: redis:6-alpine
command: command:
- sh - sh
- -c # this is to evaluate the $REDIS_PASSWORD from the env - -c # this is to evaluate the $REDIS_PASSWORD from the env
@ -63,14 +51,12 @@ services:
volumes: volumes:
- netbox-redis-data:/data - netbox-redis-data:/data
redis-cache: redis-cache:
image: redis:7-alpine image: redis:6-alpine
command: command:
- sh - sh
- -c # this is to evaluate the $REDIS_PASSWORD from the env - -c # this is to evaluate the $REDIS_PASSWORD from the env
- 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:
- netbox-redis-cache-data:/data
volumes: volumes:
netbox-media-files: netbox-media-files:
@ -79,5 +65,3 @@ volumes:
driver: local driver: local
netbox-redis-data: netbox-redis-data:
driver: local driver: local
netbox-redis-cache-data:
driver: local

View File

@ -80,6 +80,13 @@ END
echo "💡 Superuser Username: ${SUPERUSER_NAME}, E-Mail: ${SUPERUSER_EMAIL}" echo "💡 Superuser Username: ${SUPERUSER_NAME}, E-Mail: ${SUPERUSER_EMAIL}"
fi fi
# Run the startup scripts (and initializers)
if [ "$SKIP_STARTUP_SCRIPTS" == "true" ]; then
echo "↩️ Skipping startup scripts"
else
echo "import runpy; runpy.run_path('../startup_scripts')" | ./manage.py shell --interface python
fi
echo "✅ Initialisation is done." echo "✅ Initialisation is done."
# Launch whatever is passed by docker # Launch whatever is passed by docker

View File

@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
SLEEP_SECONDS=${HOUSEKEEPING_INTERVAL:=86400} SECONDS=${HOUSEKEEPING_INTERVAL:=86400}
echo "Interval set to ${SLEEP_SECONDS} seconds" echo "Interval set to ${SECONDS} seconds"
while true; do while true; do
date date
/opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py housekeeping /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py housekeeping
sleep "${SLEEP_SECONDS}s" sleep "${SECONDS}s"
done done

View File

@ -1,9 +1,6 @@
{ {
"listeners": { "listeners": {
"0.0.0.0:8080": { "*:8080": {
"pass": "routes"
},
"[::]:8080": {
"pass": "routes" "pass": "routes"
} }
}, },
@ -14,7 +11,7 @@
"uri": "/static/*" "uri": "/static/*"
}, },
"action": { "action": {
"share": "/opt/netbox/netbox${uri}" "share": "/opt/netbox/netbox"
} }
}, },

5
env/netbox.env vendored
View File

@ -16,8 +16,12 @@ EMAIL_USE_SSL=false
EMAIL_USE_TLS=false EMAIL_USE_TLS=false
GRAPHQL_ENABLED=true GRAPHQL_ENABLED=true
HOUSEKEEPING_INTERVAL=86400 HOUSEKEEPING_INTERVAL=86400
MAX_PAGE_SIZE=1000
MEDIA_ROOT=/opt/netbox/netbox/media MEDIA_ROOT=/opt/netbox/netbox/media
METRICS_ENABLED=false METRICS_ENABLED=false
NAPALM_PASSWORD=
NAPALM_TIMEOUT=10
NAPALM_USERNAME=
REDIS_CACHE_DATABASE=1 REDIS_CACHE_DATABASE=1
REDIS_CACHE_HOST=redis-cache REDIS_CACHE_HOST=redis-cache
REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY=false REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY=false
@ -30,6 +34,7 @@ REDIS_PASSWORD=H733Kdjndks81
REDIS_SSL=false REDIS_SSL=false
RELEASE_CHECK_URL=https://api.github.com/repos/netbox-community/netbox/releases RELEASE_CHECK_URL=https://api.github.com/repos/netbox-community/netbox/releases
SECRET_KEY=r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj SECRET_KEY=r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj
SKIP_STARTUP_SCRIPTS=false
SKIP_SUPERUSER=false SKIP_SUPERUSER=false
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567 SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
SUPERUSER_EMAIL=admin@example.com SUPERUSER_EMAIL=admin@example.com

View File

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

7
initializers/asns.yml Normal file
View File

@ -0,0 +1,7 @@
# - asn: 1
# rir: RFC1918
# tenant: tenant1
# - asn: 2
# rir: RFC4193 ULA
# - asn: 3
# rir: RFC3849

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

@ -0,0 +1,2 @@
# - name: Hyper-V
# slug: hyper-v

View File

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

View File

@ -0,0 +1,93 @@
## Possible Choices:
## type:
## - text
## - integer
## - boolean
## - date
## - url
## - select
## filter_logic:
## - disabled
## - loose
## - exact
##
## Examples:
# text_field:
# type: text
# label: Custom Text
# description: Enter text in a text field.
# required: false
# weight: 0
# on_objects:
# - dcim.models.Device
# - dcim.models.Rack
# - dcim.models.Site
# - dcim.models.DeviceType
# - 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
# filter_logic: loose
# weight: 10
# on_objects:
# - tenancy.models.Tenant
# select_field:
# type: select
# label: Choose between items
# required: false
# filter_logic: exact
# weight: 30
# default: First Item
# on_objects:
# - dcim.models.Device
# choices:
# - First Item
# - Second Item
# - Third Item
# - Fifth Item
# - Fourth Item
# select_field_legacy_format:
# type: select
# label: Choose between items
# required: false
# filter_logic: loose
# weight: 30
# on_objects:
# - dcim.models.Device
# choices:
# - value: A # this is the deprecated format.
# - value: B # we only use it for the tests.
# - value: C # please see above for the new format.
# - value: "D like deprecated"
# weight: 999
# - value: E
# boolean_field:
# type: boolean
# label: Yes Or No?
# required: true
# filter_logic: loose
# default: "false" # important: put "false" in quotes!
# weight: 90
# on_objects:
# - dcim.models.Device
# url_field:
# type: url
# label: Hyperlink
# description: Link to something nice.
# required: true
# filter_logic: disabled
# on_objects:
# - tenancy.models.Tenant
# date_field:
# type: date
# label: Important Date
# required: false
# filter_logic: disabled
# on_objects:
# - dcim.models.Device

View File

@ -0,0 +1,21 @@
## Possible Choices:
## new_window:
## - True
## - False
## content_type:
## - device
## - site
## - any-other-content-type
##
## Examples:
# - name: link_to_repo
# link_text: 'Link to Netbox Docker'
# link_url: 'https://github.com/netbox-community/netbox-docker'
# new_window: False
# content_type: device
# - name: link_to_localhost
# link_text: 'Link to localhost'
# link_url: 'http://localhost'
# new_window: True
# content_type: device

View File

@ -0,0 +1,18 @@
## Possible Choices:
## type:
## - virtual
## - lag
## - 1000base-t
## - ... and many more. See for yourself:
## https://github.com/netbox-community/netbox/blob/295d4f0394b431351c0cb2c3ecc791df68c6c2fb/netbox/dcim/choices.py#L510
##
## Examples:
# - device: server01
# enabled: true
# type: virtual
# name: to-server02
# - device: server02
# enabled: true
# type: virtual
# name: to-server01

View File

@ -0,0 +1,15 @@
# - name: switch
# slug: switch
# color: Grey
# - name: router
# slug: router
# color: Cyan
# - name: load-balancer
# slug: load-balancer
# color: Red
# - name: server
# slug: server
# color: Blue
# - name: patchpanel
# slug: patchpanel
# color: Black

View File

@ -0,0 +1,23 @@
# - model: Model 1
# manufacturer: Manufacturer 1
# slug: model-1
# u_height: 2
# custom_field_data:
# text_field: Description
# - model: Model 2
# manufacturer: Manufacturer 1
# slug: model-2
# custom_field_data:
# text_field: Description
# - model: Model 3
# manufacturer: Manufacturer 1
# slug: model-3
# is_full_depth: false
# u_height: 0
# custom_field_data:
# text_field: Description
# - model: Other
# manufacturer: No Name
# slug: other
# custom_field_data:
# text_field: Description

53
initializers/devices.yml Normal file
View File

@ -0,0 +1,53 @@
## Possible Choices:
## face:
## - front
## - rear
## status:
## - offline
## - active
## - planned
## - staged
## - failed
## - inventory
## - decommissioning
##
## Examples:
# - name: server01
# device_role: server
# device_type: Other
# site: AMS 1
# rack: rack-01
# face: front
# position: 1
# custom_field_data:
# text_field: Description
# - name: server02
# device_role: server
# device_type: Other
# site: AMS 2
# rack: rack-02
# face: front
# position: 2
# primary_ip4: 10.1.1.2/24
# primary_ip6: 2001:db8:a000:1::2/64
# custom_field_data:
# text_field: Description
# - name: server03
# device_role: server
# device_type: Other
# site: SING 1
# rack: rack-03
# face: front
# position: 3
# custom_field_data:
# text_field: Description
# - name: server04
# device_role: server
# device_type: Other
# site: SING 1
# location: cage 101
# face: front
# position: 3
# custom_field_data:
# text_field: Description

9
initializers/groups.yml Normal file
View File

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

View File

@ -0,0 +1,44 @@
## Possible Choices:
## status:
## - active
## - reserved
## - deprecated
## - dhcp
## role:
## - loopback
## - secondary
## - anycast
## - vip
## - vrrp
## - hsrp
## - glbp
## - carp
##
## Examples:
# - address: 10.1.1.1/24
# device: server01
# interface: to-server02
# status: active
# vrf: vrf1
# - address: 2001:db8:a000:1::1/64
# device: server01
# interface: to-server02
# status: active
# vrf: vrf1
# - address: 10.1.1.2/24
# device: server02
# interface: to-server01
# status: active
# - address: 2001:db8:a000:1::2/64
# device: server02
# interface: to-server01
# status: active
# - address: 10.1.1.10/24
# description: reserved IP
# status: reserved
# tenant: tenant1
# - address: 2001:db8:a000:1::10/64
# description: reserved IP
# status: reserved
# tenant: tenant1

View File

@ -0,0 +1,3 @@
# - name: cage 101
# slug: cage-101
# site: SING 1

View File

@ -0,0 +1,6 @@
# - name: Manufacturer 1
# slug: manufacturer-1
# - name: Manufacturer 2
# slug: manufacturer-2
# - name: No Name
# slug: no-name

View File

@ -0,0 +1,48 @@
# all.ro:
# actions:
# - view
# description: 'Read Only for All Objects'
# enabled: true
# groups:
# - applications
# - readers
# object_types: all
# users:
# - jdoe
# all.rw:
# actions:
# - add
# - change
# - delete
# - view
# description: 'Read/Write for All Objects'
# enabled: true
# groups:
# - writers
# object_types: all
# network_team.rw:
# actions:
# - add
# - change
# - delete
# - view
# description: "Network Team Permissions"
# enabled: true
# object_types:
# circuits:
# - circuit
# - circuittermination
# - circuittype
# - provider
# dcim: all
# ipam:
# - aggregate
# - ipaddress
# - prefix
# - rir
# - role
# - routetarget
# - service
# - vlan
# - vlangroup
# - vrf

View File

@ -0,0 +1,15 @@
# - name: Platform 1
# slug: platform-1
# manufacturer: Manufacturer 1
# napalm_driver: driver1
# napalm_args: "{'arg1': 'value1', 'arg2': 'value2'}"
# - name: Platform 2
# slug: platform-2
# manufacturer: Manufacturer 2
# napalm_driver: driver2
# napalm_args: "{'arg1': 'value1', 'arg2': 'value2'}"
# - name: Platform 3
# slug: platform-3
# manufacturer: No Name
# napalm_driver: driver3
# napalm_args: "{'arg1': 'value1', 'arg2': 'value2'}"

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
# location: cage 101

View File

@ -0,0 +1,2 @@
# - name: Main Management
# slug: main-management

29
initializers/prefixes.yml Normal file
View File

@ -0,0 +1,29 @@
## Possible Choices:
## status:
## - container
## - active
## - reserved
## - deprecated
##
## Examples:
# - description: prefix1
# prefix: 10.1.1.0/24
# site: AMS 1
# status: active
# tenant: tenant1
# vlan: vlan1
# - description: prefix2
# prefix: 10.1.2.0/24
# site: AMS 2
# status: active
# tenant: tenant2
# vlan: vlan2
# is_pool: true
# vrf: vrf2
# - description: ipv6 prefix1
# prefix: 2001:db8:a000:1::/64
# site: AMS 2
# status: active
# tenant: tenant2
# vlan: vlan2

View File

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

View File

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

41
initializers/racks.yml Normal file
View File

@ -0,0 +1,41 @@
## Possible Choices:
## width:
## - 19
## - 23
## types:
## - 2-post-frame
## - 4-post-frame
## - 4-post-cabinet
## - wall-frame
## - wall-cabinet
## outer_unit:
## - mm
## - in
##
## Examples:
# - site: AMS 1
# name: rack-01
# role: Role 1
# type: 4-post-cabinet
# width: 19
# u_height: 47
# custom_field_data:
# text_field: Description
# - site: AMS 2
# name: rack-02
# role: Role 2
# type: 4-post-cabinet
# width: 19
# u_height: 47
# custom_field_data:
# text_field: Description
# - site: SING 1
# name: rack-03
# location: cage 101
# role: Role 3
# type: 4-post-cabinet
# width: 19
# u_height: 47
# custom_field_data:
# text_field: Description

10
initializers/regions.yml Normal file
View File

@ -0,0 +1,10 @@
# - name: Singapore
# slug: singapore
# - name: Amsterdam
# slug: amsterdam
# - name: Downtown
# slug: downtown
# parent: Amsterdam
# - name: Suburbs
# slug: suburbs
# parent: Amsterdam

9
initializers/rirs.yml Normal file
View File

@ -0,0 +1,9 @@
# - is_private: true
# name: RFC1918
# slug: rfc1918
# - is_private: true
# name: RFC4193 ULA
# slug: rfc4193-ula
# - is_private: true
# name: RFC3849
# slug: rfc3849

View File

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

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

30
initializers/sites.yml Normal file
View File

@ -0,0 +1,30 @@
# - name: AMS 1
# slug: ams1
# region: Downtown
# status: active
# facility: Amsterdam 1
# custom_field_data:
# text_field: Description for AMS1
# - name: AMS 2
# slug: ams2
# region: Downtown
# status: active
# facility: Amsterdam 2
# custom_field_data:
# text_field: Description for AMS2
# - name: AMS 3
# slug: ams3
# region: Suburbs
# status: active
# facility: Amsterdam 3
# tenant: tenant1
# custom_field_data:
# text_field: Description for AMS3
# - name: SING 1
# slug: sing1
# region: Singapore
# status: active
# facility: Singapore 1
# tenant: tenant2
# custom_field_data:
# 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

View File

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

5
initializers/tenants.yml Normal file
View File

@ -0,0 +1,5 @@
# - name: tenant1
# slug: tenant1
# - name: tenant2
# slug: tenant2
# group: Tenant Group 2

14
initializers/users.yml Normal file
View File

@ -0,0 +1,14 @@
# technical_user:
# api_token: 0123456789technicaluser789abcdef01234567 # must be looooong!
# reader:
# password: reader
# writer:
# password: writer
# jdoe:
# first_name: John
# last_name: Doe
# api_token: 0123456789jdoe789abcdef01234567jdoe
# is_active: True
# is_superuser: False
# is_staff: False
# email: john.doe@example.com

View File

@ -0,0 +1,28 @@
## Possible Choices:
## status:
## - active
## - offline
## - staged
##
## Examples:
# - cluster: cluster1
# comments: VM1
# disk: 200
# memory: 4096
# name: virtual machine 1
# platform: Platform 2
# status: active
# tenant: tenant1
# vcpus: 8
# - cluster: cluster1
# comments: VM2
# disk: 100
# memory: 2048
# name: virtual machine 2
# platform: Platform 2
# primary_ip4: 10.1.1.10/24
# primary_ip6: 2001:db8:a000:1::10/64
# status: active
# tenant: tenant1
# vcpus: 8

View File

@ -0,0 +1,12 @@
# - description: Network Interface 1
# enabled: true
# mac_address: 00:77:77:77:77:77
# mtu: 1500
# name: Network Interface 1
# virtual_machine: virtual machine 1
# - description: Network Interface 2
# enabled: true
# mac_address: 00:55:55:55:55:55
# mtu: 1500
# name: Network Interface 2
# virtual_machine: virtual machine 1

View File

@ -0,0 +1,24 @@
# - name: VLAN group 1
# scope_type: dcim.region
# scope: Amsterdam
# slug: vlan-group-1
# - name: VLAN group 2
# scope_type: dcim.site
# scope: AMS 1
# slug: vlan-group-2
# - name: VLAN group 3
# scope_type: dcim.location
# scope: cage 101
# slug: vlan-group-3
# - name: VLAN group 4
# scope_type: dcim.rack
# scope: rack-01
# slug: vlan-group-4
# - name: VLAN group 5
# scope_type: virtualization.cluster
# scope: cluster1
# slug: vlan-group-5
# - name: VLAN group 6
# scope_type: virtualization.clustergroup
# scope: Group 1
# slug: vlan-group-6

19
initializers/vlans.yml Normal file
View File

@ -0,0 +1,19 @@
## Possible Choices:
## status:
## - active
## - reserved
## - deprecated
##
## Examples:
# - name: vlan1
# site: AMS 1
# status: active
# vid: 5
# role: Main Management
# description: VLAN 5 for MGMT
# - group: VLAN group 2
# name: vlan2
# site: AMS 1
# status: active
# vid: 1300

8
initializers/vrfs.yml Normal file
View File

@ -0,0 +1,8 @@
# - enforce_unique: true
# name: vrf1
# tenant: tenant1
# description: main VRF
# - enforce_unique: true
# name: vrf2
# rd: "6500:6500"
# tenant: tenant2

27
initializers/webhooks.yml Normal file
View File

@ -0,0 +1,27 @@
## Possible Choices:
## object_types:
## - device
## - site
## - any-other-content-type
## types:
## - type_create
## - type_update
## - type_delete
## Examples:
# - name: device_creation
# payload_url: 'http://localhost:8080'
# object_types:
# - device
# - cable
# type_create: True
# - name: device_update
# payload_url: 'http://localhost:8080'
# object_types:
# - device
# type_update: True
# - name: device_delete
# payload_url: 'http://localhost:8080'
# object_types:
# - device
# type_delete: True

View File

@ -1,6 +1,5 @@
django-auth-ldap==4.1.0 django-auth-ldap==4.0.0
django-storages[azure,boto3,dropbox,google,libcloud,sftp]==1.13.1 django-storages[azure,boto3,dropbox,google,libcloud,sftp]==1.12.3
napalm==4.0.0 google-crc32c==1.3.0
psycopg2==2.9.5 napalm==3.4.0
python3-saml==1.14.0 ruamel.yaml==0.17.21
social-auth-core[all]==4.3.0

View File

@ -0,0 +1,21 @@
import sys
from django.contrib.auth.models import User
from startup_script_utils import load_yaml
from users.models import Token
users = load_yaml("/opt/netbox/initializers/users.yml")
if users is None:
sys.exit()
for username, user_details in users.items():
if not User.objects.filter(username=username):
user = User.objects.create_user(
username=username,
password=user_details.get("password", 0) or User.objects.make_random_password(),
)
print("👤 Created user", username)
if user_details.get("api_token", 0):
Token.objects.create(user=user, key=user_details["api_token"])

View File

@ -0,0 +1,23 @@
import sys
from startup_script_utils import load_yaml
from users.models import AdminGroup, AdminUser
groups = load_yaml("/opt/netbox/initializers/groups.yml")
if groups is None:
sys.exit()
for groupname, group_details in groups.items():
group, created = AdminGroup.objects.get_or_create(name=groupname)
if created:
print("👥 Created group", groupname)
for username in group_details.get("users", []):
user = AdminUser.objects.get(username=username)
if user:
group.user_set.add(user)
print(" 👤 Assigned user %s to group %s" % (username, group.name))
group.save()

View File

@ -0,0 +1,66 @@
import sys
from django.contrib.contenttypes.models import ContentType
from startup_script_utils import load_yaml
from users.models import AdminGroup, AdminUser, ObjectPermission
object_permissions = load_yaml("/opt/netbox/initializers/object_permissions.yml")
if object_permissions is None:
sys.exit()
for permission_name, permission_details in object_permissions.items():
object_permission, created = ObjectPermission.objects.get_or_create(
name=permission_name,
description=permission_details["description"],
enabled=permission_details["enabled"],
actions=permission_details["actions"],
)
if permission_details.get("object_types", 0):
object_types = permission_details["object_types"]
if object_types == "all":
object_permission.object_types.set(ContentType.objects.all())
else:
for app_label, models in object_types.items():
if models == "all":
app_models = ContentType.objects.filter(app_label=app_label)
for app_model in app_models:
object_permission.object_types.add(app_model.id)
else:
# There is
for model in models:
object_permission.object_types.add(
ContentType.objects.get(app_label=app_label, model=model)
)
print("🔓 Created object permission", object_permission.name)
if permission_details.get("groups", 0):
for groupname in permission_details["groups"]:
group = AdminGroup.objects.filter(name=groupname).first()
if group:
object_permission.groups.add(group)
print(
" 👥 Assigned group %s object permission of %s"
% (groupname, object_permission.name)
)
if permission_details.get("users", 0):
for username in permission_details["users"]:
user = AdminUser.objects.filter(username=username).first()
if user:
object_permission.users.add(user)
print(
" 👤 Assigned user %s object permission of %s"
% (username, object_permission.name)
)
object_permission.save()

View File

@ -0,0 +1,67 @@
import sys
from extras.models import CustomField
from startup_script_utils import load_yaml
def get_class_for_class_path(class_path):
import importlib
from django.contrib.contenttypes.models import ContentType
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)
customfields = load_yaml("/opt/netbox/initializers/custom_fields.yml")
if customfields is None:
sys.exit()
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", False):
custom_field.default = cf_details["default"]
if cf_details.get("description", False):
custom_field.description = cf_details["description"]
if cf_details.get("label", False):
custom_field.label = cf_details["label"]
for object_type in cf_details.get("on_objects", []):
custom_field.content_types.add(get_class_for_class_path(object_type))
if cf_details.get("required", False):
custom_field.required = cf_details["required"]
if cf_details.get("type", False):
custom_field.type = cf_details["type"]
if cf_details.get("filter_logic", False):
custom_field.filter_logic = cf_details["filter_logic"]
if cf_details.get("weight", -1) >= 0:
custom_field.weight = cf_details["weight"]
if cf_details.get("choices", False):
custom_field.choices = []
for choice_detail in 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()
print("🔧 Created custom field", cf_name)

View File

@ -0,0 +1,33 @@
import sys
from django.contrib.contenttypes.models import ContentType
from extras.models import CustomLink
from startup_script_utils import load_yaml
custom_links = load_yaml("/opt/netbox/initializers/custom_links.yml")
if custom_links is None:
sys.exit()
def get_content_type_id(content_type):
try:
return ContentType.objects.get(model=content_type).id
except ContentType.DoesNotExist:
pass
for link in custom_links:
content_type = link.pop("content_type")
link["content_type_id"] = get_content_type_id(content_type)
if link["content_type_id"] is None:
print(
"⚠️ Unable to create Custom Link '{0}': The content_type '{1}' is unknown".format(
link.get("name"), content_type
)
)
continue
custom_link, created = CustomLink.objects.get_or_create(**link)
if created:
print("🔗 Created Custom Link '{0}'".format(custom_link.name))

View File

@ -0,0 +1,23 @@
import sys
from extras.models import Tag
from startup_script_utils import load_yaml
from utilities.choices import ColorChoices
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

@ -0,0 +1,34 @@
import sys
from django.contrib.contenttypes.models import ContentType
from extras.models import Webhook
from startup_script_utils import load_yaml
webhooks = load_yaml("/opt/netbox/initializers/webhooks.yml")
if webhooks is None:
sys.exit()
def get_content_type_id(hook_name, content_type):
try:
return ContentType.objects.get(model=content_type).id
except ContentType.DoesNotExist as ex:
print("⚠️ Webhook '{0}': The object_type '{1}' is unknown.".format(hook_name, content_type))
raise ex
for hook in webhooks:
obj_types = hook.pop("object_types")
try:
obj_type_ids = [get_content_type_id(hook["name"], obj) for obj in obj_types]
except ContentType.DoesNotExist:
continue
webhook, created = Webhook.objects.get_or_create(**hook)
if created:
webhook.content_types.set(obj_type_ids)
webhook.save()
print("🪝 Created Webhook {0}".format(webhook.name))

View File

@ -0,0 +1,15 @@
import sys
from startup_script_utils import load_yaml
from tenancy.models import TenantGroup
tenant_groups = load_yaml("/opt/netbox/initializers/tenant_groups.yml")
if tenant_groups is None:
sys.exit()
for params in tenant_groups:
tenant_group, created = TenantGroup.objects.get_or_create(**params)
if created:
print("🔳 Created Tenant Group", tenant_group.name)

View File

@ -0,0 +1,28 @@
import sys
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
from tenancy.models import Tenant, TenantGroup
tenants = load_yaml("/opt/netbox/initializers/tenants.yml")
if tenants is None:
sys.exit()
optional_assocs = {"group": (TenantGroup, "name")}
for params in tenants:
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)
tenant, created = Tenant.objects.get_or_create(**params)
if created:
set_custom_fields_values(tenant, custom_field_data)
print("👩‍💻 Created Tenant", tenant.name)

View File

@ -0,0 +1,25 @@
import sys
from dcim.models import Region
from startup_script_utils import load_yaml
regions = load_yaml("/opt/netbox/initializers/regions.yml")
if regions is None:
sys.exit()
optional_assocs = {"parent": (Region, "name")}
for params in regions:
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)
region, created = Region.objects.get_or_create(**params)
if created:
print("🌐 Created region", region.name)

View File

@ -0,0 +1,29 @@
import sys
from dcim.models import Region, Site
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
from tenancy.models import Tenant
sites = load_yaml("/opt/netbox/initializers/sites.yml")
if sites is None:
sys.exit()
optional_assocs = {"region": (Region, "name"), "tenant": (Tenant, "name")}
for params in sites:
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)
site, created = Site.objects.get_or_create(**params)
if created:
set_custom_fields_values(site, custom_field_data)
print("📍 Created site", site.name)

View File

@ -0,0 +1,23 @@
import sys
from dcim.models import Location, Site
from startup_script_utils import load_yaml
rack_groups = load_yaml("/opt/netbox/initializers/locations.yml")
if rack_groups is None:
sys.exit()
required_assocs = {"site": (Site, "name")}
for params in rack_groups:
for assoc, details in required_assocs.items():
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
location, created = Location.objects.get_or_create(**params)
if created:
print("🎨 Created location", location.name)

View File

@ -0,0 +1,23 @@
import sys
from dcim.models import RackRole
from startup_script_utils import load_yaml
from utilities.choices import ColorChoices
rack_roles = load_yaml("/opt/netbox/initializers/rack_roles.yml")
if rack_roles is None:
sys.exit()
for params in rack_roles:
if "color" in params:
color = params.pop("color")
for color_tpl in ColorChoices:
if color in color_tpl:
params["color"] = color_tpl[0]
rack_role, created = RackRole.objects.get_or_create(**params)
if created:
print("🎨 Created rack role", rack_role.name)

View File

@ -0,0 +1,41 @@
import sys
from dcim.models import Location, Rack, RackRole, Site
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
from tenancy.models import Tenant
racks = load_yaml("/opt/netbox/initializers/racks.yml")
if racks is None:
sys.exit()
required_assocs = {"site": (Site, "name")}
optional_assocs = {
"role": (RackRole, "name"),
"tenant": (Tenant, "name"),
"location": (Location, "name"),
}
for params in racks:
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)
rack, created = Rack.objects.get_or_create(**params)
if created:
set_custom_fields_values(rack, custom_field_data)
print("🔳 Created rack", rack.site, rack.name)

View File

@ -0,0 +1,36 @@
import sys
from dcim.models import Location, PowerPanel, Site
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
power_panels = load_yaml("/opt/netbox/initializers/power_panels.yml")
if power_panels is None:
sys.exit()
required_assocs = {"site": (Site, "name")}
optional_assocs = {"location": (Location, "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,36 @@
import sys
from dcim.models import PowerFeed, PowerPanel, Rack
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
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

@ -0,0 +1,15 @@
import sys
from dcim.models import Manufacturer
from startup_script_utils import load_yaml
manufacturers = load_yaml("/opt/netbox/initializers/manufacturers.yml")
if manufacturers is None:
sys.exit()
for params in manufacturers:
manufacturer, created = Manufacturer.objects.get_or_create(**params)
if created:
print("🏭 Created Manufacturer", manufacturer.name)

View File

@ -0,0 +1,24 @@
import sys
from dcim.models import DeviceRole
from startup_script_utils import load_yaml
from utilities.choices import ColorChoices
device_roles = load_yaml("/opt/netbox/initializers/device_roles.yml")
if device_roles is None:
sys.exit()
for params in device_roles:
if "color" in params:
color = params.pop("color")
for color_tpl in ColorChoices:
if color in color_tpl:
params["color"] = color_tpl[0]
device_role, created = DeviceRole.objects.get_or_create(**params)
if created:
print("🎨 Created device role", device_role.name)

View File

@ -0,0 +1,37 @@
import sys
from dcim.models import DeviceType, Manufacturer, Region
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
from tenancy.models import Tenant
device_types = load_yaml("/opt/netbox/initializers/device_types.yml")
if device_types is None:
sys.exit()
required_assocs = {"manufacturer": (Manufacturer, "name")}
optional_assocs = {"region": (Region, "name"), "tenant": (Tenant, "name")}
for params in device_types:
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)
device_type, created = DeviceType.objects.get_or_create(**params)
if created:
set_custom_fields_values(device_type, custom_field_data)
print("🔡 Created device type", device_type.manufacturer, device_type.model)

View File

@ -0,0 +1,52 @@
import sys
from dcim.models import Device, DeviceRole, DeviceType, Location, Platform, Rack, Site
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
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"),
"location": (Location, "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

@ -0,0 +1,27 @@
import sys
from dcim.models import Device, Interface
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
interfaces = load_yaml("/opt/netbox/initializers/dcim_interfaces.yml")
if interfaces is None:
sys.exit()
required_assocs = {"device": (Device, "name")}
for params in interfaces:
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)
interface, created = Interface.objects.get_or_create(**params)
if created:
set_custom_fields_values(interface, custom_field_data)
print("🧷 Created interface", interface.name, interface.device.name)

View File

@ -0,0 +1,27 @@
import sys
from dcim.models import Manufacturer, Platform
from startup_script_utils import load_yaml
platforms = load_yaml("/opt/netbox/initializers/platforms.yml")
if platforms is None:
sys.exit()
optional_assocs = {
"manufacturer": (Manufacturer, "name"),
}
for params in platforms:
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)
platform, created = Platform.objects.get_or_create(**params)
if created:
print("💾 Created platform", platform.name)

View File

@ -0,0 +1,29 @@
import sys
from ipam.models import RouteTarget
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
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

@ -0,0 +1,29 @@
import sys
from ipam.models import VRF
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
from tenancy.models import Tenant
vrfs = load_yaml("/opt/netbox/initializers/vrfs.yml")
if vrfs is None:
sys.exit()
optional_assocs = {"tenant": (Tenant, "name")}
for params in vrfs:
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)
vrf, created = VRF.objects.get_or_create(**params)
if created:
set_custom_fields_values(vrf, custom_field_data)
print("📦 Created VRF", vrf.name)

View File

@ -0,0 +1,15 @@
import sys
from ipam.models import RIR
from startup_script_utils import load_yaml
rirs = load_yaml("/opt/netbox/initializers/rirs.yml")
if rirs is None:
sys.exit()
for params in rirs:
rir, created = RIR.objects.get_or_create(**params)
if created:
print("🗺️ Created RIR", rir.name)

View File

@ -0,0 +1,33 @@
import sys
from ipam.models import ASN, RIR
from startup_script_utils import load_yaml
from tenancy.models import Tenant
asns = load_yaml("/opt/netbox/initializers/asns.yml")
if asns is None:
sys.exit()
required_assocs = {"rir": (RIR, "name")}
optional_assocs = {"tenant": (Tenant, "name")}
for params in asns:
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)
asn, created = ASN.objects.get_or_create(**params)
if created:
print(f"🔡 Created ASN {asn.asn}")

View File

@ -0,0 +1,42 @@
import sys
from ipam.models import RIR, Aggregate
from netaddr import IPNetwork
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
from tenancy.models import Tenant
aggregates = load_yaml("/opt/netbox/initializers/aggregates.yml")
if aggregates is None:
sys.exit()
required_assocs = {"rir": (RIR, "name")}
optional_assocs = {
"tenant": (Tenant, "name"),
}
for params in aggregates:
custom_field_data = pop_custom_fields(params)
params["prefix"] = IPNetwork(params["prefix"])
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)
aggregate, created = Aggregate.objects.get_or_create(**params)
if created:
set_custom_fields_values(aggregate, custom_field_data)
print("🗞️ Created Aggregate", aggregate.prefix)

View File

@ -0,0 +1,15 @@
import sys
from ipam.models import Role
from startup_script_utils import load_yaml
roles = load_yaml("/opt/netbox/initializers/prefix_vlan_roles.yml")
if roles is None:
sys.exit()
for params in roles:
role, created = Role.objects.get_or_create(**params)
if created:
print("⛹️‍ Created Prefix/VLAN Role", role.name)

View File

@ -0,0 +1,15 @@
import sys
from startup_script_utils import load_yaml
from virtualization.models import ClusterType
cluster_types = load_yaml("/opt/netbox/initializers/cluster_types.yml")
if cluster_types is None:
sys.exit()
for params in cluster_types:
cluster_type, created = ClusterType.objects.get_or_create(**params)
if created:
print("🧰 Created Cluster Type", cluster_type.name)

View File

@ -0,0 +1,15 @@
import sys
from startup_script_utils import load_yaml
from virtualization.models import ClusterGroup
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,42 @@
import sys
from dcim.models import Site
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
from tenancy.models import Tenant
from virtualization.models import Cluster, ClusterGroup, ClusterType
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

@ -0,0 +1,40 @@
import sys
from django.contrib.contenttypes.models import ContentType
from ipam.models import VLANGroup
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
vlan_groups = load_yaml("/opt/netbox/initializers/vlan_groups.yml")
if vlan_groups is None:
sys.exit()
optional_assocs = {"scope": (None, "name")}
for params in vlan_groups:
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)}
# Get model from Contenttype
scope_type = params.pop("scope_type", None)
if not scope_type:
print(f"VLAN Group '{params['name']}': scope_type is missing from VLAN Group")
continue
app_label, model = str(scope_type).split(".")
ct = ContentType.objects.filter(app_label=app_label, model=model).first()
if not ct:
print(
f"VLAN Group '{params['name']}': ContentType for "
+ f"app_label = '{app_label}' and model = '{model}' not found"
)
continue
params["scope_id"] = ct.model_class().objects.get(**query).id
vlan_group, created = VLANGroup.objects.get_or_create(**params)
if created:
set_custom_fields_values(vlan_group, custom_field_data)
print("🏘️ Created VLAN Group", vlan_group.name)

View File

@ -0,0 +1,36 @@
import sys
from dcim.models import Site
from ipam.models import VLAN, Role, VLANGroup
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
from tenancy.models import Tenant, TenantGroup
vlans = load_yaml("/opt/netbox/initializers/vlans.yml")
if vlans is None:
sys.exit()
optional_assocs = {
"site": (Site, "name"),
"tenant": (Tenant, "name"),
"tenant_group": (TenantGroup, "name"),
"group": (VLANGroup, "name"),
"role": (Role, "name"),
}
for params in vlans:
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)
vlan, created = VLAN.objects.get_or_create(**params)
if created:
set_custom_fields_values(vlan, custom_field_data)
print("🏠 Created VLAN", vlan.name)

View File

@ -0,0 +1,46 @@
import sys
from dcim.models import DeviceRole, Platform
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
from tenancy.models import Tenant
from virtualization.models import Cluster, VirtualMachine
virtual_machines = load_yaml("/opt/netbox/initializers/virtual_machines.yml")
if virtual_machines is None:
sys.exit()
required_assocs = {"cluster": (Cluster, "name")}
optional_assocs = {
"tenant": (Tenant, "name"),
"platform": (Platform, "name"),
"role": (DeviceRole, "name"),
}
for params in virtual_machines:
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)
virtual_machine, created = VirtualMachine.objects.get_or_create(**params)
if created:
set_custom_fields_values(virtual_machine, custom_field_data)
print("🖥️ Created virtual machine", virtual_machine.name)

View File

@ -0,0 +1,27 @@
import sys
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
from virtualization.models import VirtualMachine, VMInterface
interfaces = load_yaml("/opt/netbox/initializers/virtualization_interfaces.yml")
if interfaces is None:
sys.exit()
required_assocs = {"virtual_machine": (VirtualMachine, "name")}
for params in interfaces:
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)
interface, created = VMInterface.objects.get_or_create(**params)
if created:
set_custom_fields_values(interface, custom_field_data)
print("🧷 Created interface", interface.name, interface.virtual_machine.name)

View File

@ -0,0 +1,39 @@
import sys
from dcim.models import Site
from ipam.models import VLAN, VRF, Prefix, Role
from netaddr import IPNetwork
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
from tenancy.models import Tenant, TenantGroup
prefixes = load_yaml("/opt/netbox/initializers/prefixes.yml")
if prefixes is None:
sys.exit()
optional_assocs = {
"site": (Site, "name"),
"tenant": (Tenant, "name"),
"tenant_group": (TenantGroup, "name"),
"vlan": (VLAN, "name"),
"role": (Role, "name"),
"vrf": (VRF, "name"),
}
for params in prefixes:
custom_field_data = pop_custom_fields(params)
params["prefix"] = IPNetwork(params["prefix"])
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
prefix, created = Prefix.objects.get_or_create(**params)
if created:
set_custom_fields_values(prefix, custom_field_data)
print("📌 Created Prefix", prefix.prefix)

View File

@ -0,0 +1,63 @@
import sys
from dcim.models import Device, Interface
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from ipam.models import VRF, IPAddress
from netaddr import IPNetwork
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
from tenancy.models import Tenant
from virtualization.models import VirtualMachine, VMInterface
ip_addresses = load_yaml("/opt/netbox/initializers/ip_addresses.yml")
if ip_addresses is None:
sys.exit()
optional_assocs = {
"tenant": (Tenant, "name"),
"vrf": (VRF, "name"),
"interface": (None, None),
}
vm_interface_ct = ContentType.objects.filter(
Q(app_label="virtualization", model="vminterface")
).first()
interface_ct = ContentType.objects.filter(Q(app_label="dcim", model="interface")).first()
for params in ip_addresses:
custom_field_data = pop_custom_fields(params)
vm = params.pop("virtual_machine", None)
device = params.pop("device", None)
params["address"] = IPNetwork(params["address"])
if vm and device:
print("IP Address can only specify one of the following: virtual_machine or device.")
sys.exit()
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
if assoc == "interface":
if vm:
vm_id = VirtualMachine.objects.get(name=vm).id
query = {"name": params.pop(assoc), "virtual_machine_id": vm_id}
params["assigned_object_type"] = vm_interface_ct
params["assigned_object_id"] = VMInterface.objects.get(**query).id
elif device:
dev_id = Device.objects.get(name=device).id
query = {"name": params.pop(assoc), "device_id": dev_id}
params["assigned_object_type"] = interface_ct
params["assigned_object_id"] = Interface.objects.get(**query).id
else:
query = {field: params.pop(assoc)}
params[assoc] = model.objects.get(**query)
ip_address, created = IPAddress.objects.get_or_create(**params)
if created:
set_custom_fields_values(ip_address, custom_field_data)
print("🧬 Created IP Address", ip_address.address)

View File

@ -0,0 +1,47 @@
import sys
from dcim.models import Device
from ipam.models import IPAddress
from startup_script_utils import load_yaml
from virtualization.models import VirtualMachine
def link_primary_ip(assets, asset_model):
for params in assets:
primary_ip_fields = set(params) & {"primary_ip4", "primary_ip6"}
if not primary_ip_fields:
continue
for assoc, details in optional_assocs.items():
if assoc in params:
model, field = details
query = {field: params.pop(assoc)}
try:
params[assoc] = model.objects.get(**query)
except model.DoesNotExist:
primary_ip_fields -= {assoc}
print(f"⚠️ IP Address '{query[field]}' not found")
asset = asset_model.objects.get(name=params["name"])
for field in primary_ip_fields:
if getattr(asset, field) != params[field]:
setattr(asset, field, params[field])
print(f"🔗 Define primary IP '{params[field].address}' on '{asset.name}'")
asset.save()
devices = load_yaml("/opt/netbox/initializers/devices.yml")
virtual_machines = load_yaml("/opt/netbox/initializers/virtual_machines.yml")
optional_assocs = {
"primary_ip4": (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)
if virtual_machines is not None:
link_primary_ip(virtual_machines, VirtualMachine)

View File

@ -0,0 +1,30 @@
import sys
from dcim.models import Device
from ipam.models import Service
from startup_script_utils import load_yaml
from virtualization.models import VirtualMachine
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,19 @@
import sys
from circuits.models import Provider
from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values
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)

Some files were not shown because too many files have changed in this diff Show More