Compare commits

...

115 Commits

Author SHA1 Message Date
a51ad36801 Merge pull request #355 from netbox-community/develop
Version 0.26.2
2020-10-27 16:41:48 +01:00
953ee09b0c Preparation for 0.26.2 2020-10-27 16:24:10 +01:00
94047d60ed Merge branch 'release' into develop 2020-10-27 16:22:59 +01:00
80bfd98000 Merge pull request #354 from comphilip/release
Fix ldap configuration in /etc/netbox/config/ldap/*.py not loaded
2020-10-27 16:11:55 +01:00
7694fd320a use extend method for simplification.
Co-authored-by: Christian Mäder <cimnine@users.noreply.github.com>
2020-10-27 21:12:26 +08:00
097bea8702 Merge pull request #353 from netbox-community/develop
Version 0.26.1
2020-10-27 12:32:43 +01:00
a3c21ae0ac Fix ldap configuration in /etc/netbox/config/ldap/*.py not loaded 2020-10-27 19:27:51 +08:00
074960327b Preparation for 0.26.1 2020-10-27 10:30:55 +01:00
e1462d9ca4 Merge pull request #351 from netbox-community/RevertPrometheusPreps
Revert changes to 'gunicorn_config.py'
2020-10-27 10:29:07 +01:00
a5aa1bfd3b Revert changes to 'gunicorn_config.py' 2020-10-27 09:31:47 +01:00
cb02450783 Merge pull request #350 from comphilip/release
Fix "'NoneType' object has no attribute 'lower'" when no AUTH_LDAP_MI…
2020-10-27 09:29:34 +01:00
8307560c88 Fix "'NoneType' object has no attribute 'lower'" when no AUTH_LDAP_MIRROR_GROUPS defined 2020-10-27 15:51:12 +08:00
43aea3a1dd Merge pull request #349 from netbox-community/develop
Version 0.26.0
2020-10-26 16:55:21 +01:00
a52a5547be Preparation for 0.26.0 2020-10-26 16:21:47 +01:00
120a605d35 Merge pull request #347 from tobiasge/python-final
Use release version of Python 3.9 in tests
2020-10-26 16:06:12 +01:00
e51f9cbc18 Merge pull request #344 from netbox-community/Prometheus
Prepare for Monitoring with Prometheus
2020-10-26 15:58:52 +01:00
071401b771 Merge pull request #346 from tobiasge/fix-action-warning
Fix warning in Github Actions
2020-10-26 15:48:50 +01:00
911488242f Use release version of Python 3.9 in tests 2020-10-26 15:42:31 +01:00
5512ea68b3 Ignore prometheus.yml 2020-10-26 15:40:22 +01:00
7d055fbcaf Mention monitoring in README 2020-10-26 15:29:41 +01:00
3d244a1946 Fix warning in Github Actions
Our build script is using "set-env" which has been deprecated by Github.
See this bolg entry:
https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/
2020-10-26 15:23:17 +01:00
e18d6c53b3 Revert most changes 2020-10-26 15:22:56 +01:00
48decdeb0e Monitoring parts to docker-compose.monitorin.yml 2020-10-26 15:22:56 +01:00
6ac65a64c9 Fix test 2020-10-26 15:22:56 +01:00
f46d8a7782 Adds Prometheus/Grafana monitoring infrastructure 2020-10-26 15:22:52 +01:00
64d82b5e42 Merge pull request #343 from netbox-community/DynamicVariables
Dynamic Configuration
2020-10-26 15:21:42 +01:00
58050e5287 Merge core functionality into configuration.py
Which is the file `docker/configuration.docker.py` in our repo.
The common code is then imported by `docker/ldap_config.docker.py`.
2020-10-26 14:43:11 +01:00
2dba2b8811 Fix default for secret_key in configuration.py 2020-10-20 21:45:37 +02:00
d0c7b87925 Improve order in configuration.py 2020-10-20 21:40:14 +02:00
ec3fd8a790 Revert accidential change to _read_secret order 2020-10-20 21:36:40 +02:00
75554ef5b4 Consistency in namespace name 2020-10-20 21:29:08 +02:00
dad0608834 Rename example.py to extra.py
And add some comments to the the file that hopefully guide the user.
2020-10-20 20:51:43 +02:00
dc77f1a0b1 Ignore non-default configuration files 2020-10-20 20:51:43 +02:00
16b567939c Dot character has special meaning in module names
Therefore we esacpe it using the underscore character.
2020-10-20 20:51:43 +02:00
20820e10c2 Optimize Imports in ldap_config.py 2020-10-20 20:51:35 +02:00
553c8ea470 Update configuration.py 2020-10-20 20:49:59 +02:00
811618b359 Lower gunicorn default loglevel 2020-10-20 20:46:43 +02:00
7429835970 Sample dynamic configuration file 2020-10-20 20:46:43 +02:00
43c05d816d Dynamically load configuration files 2020-10-20 20:46:43 +02:00
fd3d3d11d1 Tiny refactoring 2020-10-20 20:46:43 +02:00
121c3f800d Merge branch 'eemil-develop' into develop 2020-10-20 19:23:11 +02:00
9287995df4 Update to latest configuration 2020-10-20 19:22:52 +02:00
85fbb0af70 fix typo
Co-authored-by: Christian Mäder <cimnine@users.noreply.github.com>
2020-10-20 19:20:29 +02:00
6bada6660a Prefer secret to env variable if both are configured 2020-10-20 19:20:29 +02:00
df3ab69c0f Merge pull request #310 from shuichiro-makigaki/add-remote-auth
Add REMOTE_AUTH_* configs
2020-10-20 09:02:27 +02:00
38f4474b14 Add REMOTE_AUTH_* support 2020-10-20 11:11:32 +09:00
51331621ab Merge pull request #345 from netbox-community/enable_sponsoring
Enable Sponsoring Button
2020-10-19 17:13:34 +02:00
156681f68d Merge pull request #327 from madnutter56/mirror_ldap_groups
add option to mirror ldap groups into netbox
2020-10-19 17:06:51 +02:00
9f4a9f528c add option to mirror ldap groups into netbox
this commit allows ldap assigned groups to be mirrored into
netbox users.

The default is None as this is not the primary way to do this
change.
2020-10-19 07:50:33 -07:00
cad803ed31 Create FUNDING.yml to enable sponsoring 2020-10-19 16:50:04 +02:00
9efaccadf7 Merge branch 'Tassatux-develop' into develop 2020-10-18 11:41:03 +02:00
f553d17655 Change log message 2020-10-18 11:31:48 +02:00
2b361c4541 Merge branch 'develop' of https://github.com/Tassatux/netbox-docker into Tassatux-develop 2020-10-18 11:24:51 +02:00
c744603a85 Merge pull request #336 from ericgeldmacher/develop
Fix sites.yml initializer
2020-09-21 21:04:39 +02:00
1133ef50de Fix sites.yml initializer 2020-09-15 15:38:24 -05:00
b33a509e25 Merge pull request #333 from tobiasge/python-3.8
Move default source image to Python 3.8
2020-09-01 15:35:17 +02:00
79f0b997f3 Merge pull request #332 from tobiasge/image-deps
Update images in docker-compose
2020-09-01 14:04:21 +02:00
32cef1dcf8 Enable test on Python 3.9 2020-09-01 13:52:01 +02:00
74833a9b21 Update images in docker-compose 2020-09-01 13:34:42 +02:00
a68f315acb Move default source image to Python 3.8 2020-09-01 12:27:39 +02:00
00022e7d79 Merge pull request #331 from netbox-community/develop
Develop
2020-09-01 11:05:25 +02:00
dd490605ca Preparation for 0.25.0 2020-09-01 10:40:16 +02:00
e2711ca205 Merge pull request #326 from tobiasge/fix-startup-2.9
Fixes for Netbox 2.9
2020-09-01 10:37:37 +02:00
a87f2b3331 Merge pull request #329 from tdorsey/patch-1
Fix typo in Readme
2020-08-31 08:03:33 +02:00
f174749f98 chore: fix typo 2020-08-30 22:06:20 -04:00
b02a93904e Fixed IP address creation for Netbox 2.9 2020-08-24 14:20:35 +02:00
3ace32dfc2 Fixed creation of passwords for Netbox 2.9 2020-08-24 11:08:28 +02:00
9fae2b0f74 Fixed VM interface creation for Netbox 2.9 2020-08-24 11:08:23 +02:00
23c3240863 Merge pull request #319 from jamiereid/release
Update README.md
2020-07-14 09:09:20 +02:00
3f9e874d77 Update README.md
fix spelling mistake
2020-07-14 11:19:49 +10:00
4b0f158852 Merge pull request #313 from jgcasd/jgcasd-patch-1
Add optional LOGIN_TIMEOUT to configuration.py
2020-06-17 15:26:16 +02:00
51490d0039 Added LOGIN_TIMEOUT option to configuration.py
Added LOGIN_TIMEOUT option to configuration.py. The option is already available in standard Netbox configuration.
2020-06-16 11:27:59 -07:00
df72d6bbb5 Handle primary IPs on devices and VMs 2020-06-05 13:12:27 +02:00
688672de26 Merge pull request #308 from netbox-community/XForwardedProto
Remove the 'X-Forwarded-Proto' line from the nginx config
2020-06-05 11:57:23 +02:00
5624ecc65e Remove the 'X-Forwarded-Proto' line from the nginx config
The reason is that in the default configuration nginx is only serving 'http' traffic.
So if an upstream proxy sets the 'X-Forwarded-Proto' header, because it is terminating
TLS, then nginx will overwrite it to 'http'. This will cause django to think the page
is served via 'http' and it will not create 'https://...' URLs.

Related to #292
2020-06-02 16:06:52 +02:00
7f93b328af Merge pull request #307 from netbox-community/FixFileMatching
Don't try to run non-python files
2020-06-02 14:42:58 +02:00
ccad15ecf7 Don't try to run non-python files 2020-06-02 13:37:57 +02:00
e9a9d9b70b Merge pull request #305 from netbox-community/develop
Preparation for 0.24.1
2020-05-29 14:41:15 +02:00
03b36058cd Preparation for 0.24.1 2020-05-29 10:09:08 +02:00
9e06a38d41 Merge pull request #304 from tobiasge/fix-303
Fix #303 for Netbox v2.8.5
2020-05-29 10:03:22 +02:00
b7ad18afb0 Fix #303 for Netbox v2.8.5
In this Netbox version the ColorChoices where moved to utilities.choiceFix #303 for Netbox v2.8.5

In this Netbox version the ColorChoices where moved to utilities.choicess
2020-05-27 09:10:24 +02:00
a9232e4f30 Add optional primary_ip on virtual machines initializer 2020-05-22 17:40:11 +02:00
93a3784295 Merge pull request #296 from netbox-community/develop
Release 0.24.0
2020-05-16 18:56:06 +02:00
45f7823a17 Preparation for 0.24.0 2020-05-15 13:59:45 +02:00
a6584d2874 Merge pull request #274 from tobiasge/optimise-builds
Optimise builds
2020-05-15 13:57:43 +02:00
fd87c7cb98 Merge pull request #294 from netbox-community/Fix284
Fix #284: Fixes the bug that initializers with no data stopped the execution of the later initializers
2020-05-15 13:57:23 +02:00
1f38ca0a86 Merge pull request #295 from minitriga/#293_ssl_tls_env
Implement SSL and TLS Config
2020-05-15 09:29:02 +02:00
efec435ba0 fix some issues 2020-05-14 15:37:47 +00:00
ab8ff04852 #293 2020-05-14 15:09:25 +00:00
77feec30a0 Don't stop when startup_scripts quit
Fixes #284
2020-05-14 16:59:30 +02:00
bed40b0d05 Add STARTTLS option. (#277)
* Add STARTTLS option.
2020-05-14 16:41:42 +02:00
24f0545bc6 Merge branch 'ryanmerolle-develop' into develop 2020-05-14 16:20:07 +02:00
1bc1ab2a0a Preserve compatibility 2020-05-14 16:19:38 +02:00
bfa69dc0e9 Merge branch 'develop' of https://github.com/ryanmerolle/netbox-docker into ryanmerolle-develop 2020-05-14 16:17:59 +02:00
6c8042b63d Merge pull request #289 from weisdd/bugfix/devices.py.example
Fixes #246: Non-working devices.py.example
2020-05-14 15:35:34 +02:00
795e82be46 Fix shellcheck error 2020-05-14 13:54:40 +02:00
d8a6c321a1 Update build.sh
Include suggestion from cimnine

Co-authored-by: Christian Mäder <cimnine@users.noreply.github.com>
2020-05-14 13:49:19 +02:00
1ecf4853b8 Fixed devices.py.example report 2020-05-11 16:46:10 +03:00
5cb8e97e65 Merge pull request #276 from weisdd/bugfix/dockerfile-media-permissions
Dockerfile: Fixed file permissions for media
2020-05-06 18:40:00 +02:00
98e131fa30 Merge pull request #279 from hoanhan101/release
Fix typo in overriding docker-compose config
2020-04-22 22:51:44 +02:00
d6323ce40f Fix typo in overriding docker-compose config 2020-04-22 16:24:34 -04:00
1c65f7af10 Dockerfile: Fixed file permissions for media 2020-04-18 17:08:42 +03:00
7fb78b3fd2 Fixed script in case no branch for next exists
Build now prints a message when no devlop-* branch is found instead of crashing
2020-04-13 21:44:18 +02:00
aa9f01a778 Merge pull request #273 from cimnine/NoResponse
No response
2020-04-09 21:17:42 +02:00
786f9b50d2 Fixes build when DOCKER_FROM is set but empty
The DOCKER_FROM is set to an empty value in the push tests.
This expands the check to catch this test case
2020-04-09 10:26:21 +02:00
5909670690 Fixed "latest" tag
The variable for the latest tag didn't contain all the needed values.
2020-04-09 09:04:20 +02:00
231de236e0 Removed Travis CI configuration and script
We are no longer building the images with Travis CI.
2020-04-09 08:47:07 +02:00
9ea95950b9 probot/no-response configuration 2020-04-09 08:30:30 +02:00
d84c399c00 Let "latest" tag follow the highest version
Latest tag is now added to the new version when it is release by Netbox.
2020-04-09 08:17:05 +02:00
a217ce8ffd Changed "build-branches.sh" to "build-next.sh"
The old version of "build-branches.sh" skipped the pushing of images when one of
them didn't change its sources. When looking at the Netbox branches I noticed
that the "master" branch only changes when there is a new release. Because we
build the new releases anyway in "build-latest.sh" that leaves "develop" and
"develop-*" which change regularly. The new script "build-next.sh" is responsible
for building "develop-*" as the next major release of Netbox. The build of
"develop" is moved to the Github Action build matrix. This has the additional
advantage of being faster because more builds are done in parallel.
2020-04-08 22:11:37 +02:00
794fb45e0e Fix shellcheck items found by shellcheck 0.7.1 2020-04-08 16:06:55 +02:00
8e34f46bad Add checks to verifiy if a new build is needed
This checks if the source materials (python image, Netbox commit,
netbox-docker commit) have changed since the last build. This check is done
by comparing the digest and commit ids from the previous image with the
given tag to the current values taken from the Git and Docker repositories.

The checks are only performed for builds by the automated builds on Github.
2020-04-08 15:50:06 +02:00
ed0d099df7 Merge pull request #271 from tobiasge/labels-in-build
Add labels for all variants
2020-04-05 13:14:48 +02:00
26d08302e3 Add labels for all variants
When we don't set the --label argument on the commandline for all
build variants we lose them in the image.
This also prints out the labes on image push.
2020-04-05 09:29:31 +02:00
64b763429f update configuration.py for netbox 2.7.11 REDIS config
update configuration.py to use REDIS config referencing `tasks` in place of `webhooks`
2020-04-01 23:41:35 -04:00
38 changed files with 698 additions and 333 deletions

14
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,14 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
- cimnine
- tobiasge
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

10
.github/no-response.yml vendored Normal file
View File

@ -0,0 +1,10 @@
# Configuration for probot-no-response - https://github.com/probot/no-response
# Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 30
# Label requiring a response
responseRequiredLabel: awaiting answer
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author.

View File

@ -15,12 +15,12 @@ jobs:
build_cmd:
- ./build-latest.sh
- PRERELEASE=true ./build-latest.sh
- ./build-branches.sh
- ./build-next.sh
- ./build.sh develop
docker_from:
- '' # use the default of the DOCKERFILE
- python:3.7-alpine
- '' # use the default of the build script
- python:3.8-alpine
# - python:3.9-rc-alpine # disable until Netbox's unit tests work
- python:3.9-alpine
fail-fast: false
runs-on: ubuntu-latest
name: Builds new Netbox Docker Images

View File

@ -14,14 +14,15 @@ jobs:
build_cmd:
- ./build-latest.sh
- PRERELEASE=true ./build-latest.sh
- ./build-branches.sh
- ./build-next.sh
- ./build.sh develop
fail-fast: false
runs-on: ubuntu-latest
name: Builds new Netbox Docker Images
steps:
- id: git-checkout
name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v2
- id: docker-build
name: Build the image with '${{ matrix.build_cmd }}'
run: ${{ matrix.build_cmd }}

7
.gitignore vendored
View File

@ -2,3 +2,10 @@
.netbox
.initializers
docker-compose.override.yml
*.pem
configuration/*
!configuration/configuration.py
!configuration/extra.py
configuration/ldap/*
!configuration/ldap/ldap_config.py
prometheus.yml

View File

@ -1,32 +0,0 @@
sudo: required
language: python
env:
- BUILD=release
- BUILD=prerelease
- BUILD=branches
- BUILD=special
git:
depth: 5
services:
- docker
install:
- docker-compose pull --parallel
- docker-compose build
script:
- docker-compose run netbox ./manage.py test
after_script:
- docker-compose down
after_success:
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
- ./build-all.sh --push
notifications:
slack:
secure: F3VsWcvU/XYyjGjU8ZAVGpREe7F1NjKq6LuMRzhQORbXUvanxDQtLzEe0Y5zm/6+gHkx6t8cX/v2PiCI+v46pkapYMUimd+QEOL1WxbUdnw2kQxcgw/R3wX34l2FHXbG3/a+TmH3euqbSCTIrPy9ufju948i+Q0E0u0fyInmozl8qOT23C4joQOpVAq7y+wHxTxsEg46ZzL2Ties+dmqjMsvHocv7mPI2IWzAWA8SJZxS82Amoapww++QjgEmoY+tMimLkdeXCRgeoj41UGHDg54rbEXh/PTaWiuzyzTr1WLmsGRScC57fDRivp3mSM37/MlNxsRj1z+j4zrvWFQgNfJ2yMjBHroc1jOX/uCY4dwbpSPqUCpc4idMGCGZFItgzTQ3lAPYAsom0C6n8C08Xk8EsNKWwXrDSd4ZUhIwptkNPCFK+kXbLFsMzSApnaBYW0T+wba57nZdiWjOPYmvJr49MDm5NHv2KaRBX2gpw7t7ZLhTgwGEWcZvcDebiLneXcXY5hZ7v2NHJkx/2x1yNXo85xZDy0wK1FGoOOHwPhvqOB+pcQZ/pUOSPTKqGw5l/CexoRm1shFsK+19FnSgimqTHjcuCo4lFW3JlEvlFhtfFXIte2Wjp1ALZgTrSq8zSD5rRxYCUKmM7b3EJwdaIgbvKWPdS4sCXlXU1bHx0g=

View File

@ -1,4 +1,4 @@
ARG FROM=python:3.7-alpine
ARG FROM
FROM ${FROM} as builder
RUN apk add --no-cache \
@ -61,12 +61,12 @@ ARG NETBOX_PATH
COPY ${NETBOX_PATH} /opt/netbox
COPY docker/configuration.docker.py /opt/netbox/netbox/netbox/configuration.py
COPY configuration/gunicorn_config.py /etc/netbox/config/
COPY docker/gunicorn_config.py /etc/netbox/
COPY docker/nginx.conf /etc/netbox-nginx/nginx.conf
COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh
COPY startup_scripts/ /opt/netbox/startup_scripts/
COPY initializers/ /opt/netbox/initializers/
COPY configuration/configuration.py /etc/netbox/config/configuration.py
COPY configuration/ /etc/netbox/config/
WORKDIR /opt/netbox/netbox
@ -75,11 +75,11 @@ WORKDIR /opt/netbox/netbox
# container startup.
# Must set permissions for '/opt/netbox/netbox/media' directory
# to g+w so that pictures can be uploaded to netbox.
RUN mkdir static && chmod g+w static media
RUN mkdir static && chmod -R g+w static media
ENTRYPOINT [ "/opt/netbox/docker-entrypoint.sh" ]
CMD ["gunicorn", "-c /etc/netbox/config/gunicorn_config.py", "netbox.wsgi"]
CMD ["gunicorn", "-c /etc/netbox/gunicorn_config.py", "netbox.wsgi"]
LABEL ORIGINAL_TAG="" \
NETBOX_GIT_BRANCH="" \
@ -122,4 +122,3 @@ RUN apk add --no-cache \
util-linux
COPY docker/ldap_config.docker.py /opt/netbox/netbox/netbox/ldap_config.py
COPY configuration/ldap_config.py /etc/netbox/config/ldap_config.py

View File

@ -33,7 +33,7 @@ Before opening an issue on Github, please join the [Network To Code][ntc-slack]
Then there is currently one extra tags for each of the above tags:
* `-ldap`: Contains additional dependencies and configurations for connecting Netbox to an LDAP directroy.
* `-ldap`: Contains additional dependencies and configurations for connecting Netbox to an LDAP directory.
[Learn more about that in our wiki][netbox-docker-ldap].
New images are built and published automatically every ~24h.
@ -52,7 +52,7 @@ There is a more complete [_Getting Started_ guide on our wiki][wiki-getting-star
```bash
git clone -b release https://github.com/netbox-community/netbox-docker.git
cd netbox-docker
tee netbox-docker.override.yml <<EOF
tee docker-compose.override.yml <<EOF
version: '3.4'
services:
nginx:
@ -79,7 +79,7 @@ The default credentials are:
## Documentation
Please refer [to our wiki on Github][netbox-docker-wiki] for further information on how to use this Netbox Docker image properly.
It covers advanced topics such as using secret files, deployment to Kubernetes as well as NAPALM and LDAP configuration.
It covers advanced topics such as using files for secrets, deployment to Kubernetes, monitoring and configuring NAPALM or LDAP.
[netbox-docker-wiki]: https://github.com/netbox-community/netbox-docker/wiki/

View File

@ -1 +1 @@
0.23.0
0.26.2

View File

@ -1,51 +0,0 @@
#!/bin/bash
# Builds all Docker images this project provides
# Arguments:
# BUILD: The release to build.
# Allowed: release, prerelease, branches, special
# Default: undefined
echo "▶️ $0 $*"
ALL_BUILDS=("release" "prerelease" "branches" "special")
BUILDS=("${BUILD:-"${ALL_BUILDS[@]}"}")
echo "⚙️ Configured builds: ${BUILDS[*]}"
if [ -n "${DEBUG}" ]; then
export DEBUG
fi
ERROR=0
for BUILD in "${BUILDS[@]}"; do
echo "🛠 Building '$BUILD' from '$DOCKERFILE'"
case $BUILD in
release)
# build the latest release
# shellcheck disable=SC2068
./build-latest.sh $@ || ERROR=1
;;
prerelease)
# build the latest pre-release
# shellcheck disable=SC2068
PRERELEASE=true ./build-latest.sh $@ || ERROR=1
;;
branches)
# build all branches
# shellcheck disable=SC2068
./build-branches.sh $@ || ERROR=1
;;
*)
echo "🚨 Unrecognized build '$BUILD'."
if [ -z "$DEBUG" ]; then
exit 1
else
echo "⚠️ Would exit here with code '1', but DEBUG is enabled."
fi
;;
esac
done
exit $ERROR

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}'."
}

View File

@ -0,0 +1,82 @@
#!/bin/bash
# Retrieves image configuration from public images in DockerHub
# Functions from https://gist.github.com/cirocosta/17ea17be7ac11594cb0f290b0a3ac0d1
# Optimised for our use case
get_image_label() {
local label=$1
local image=$2
local tag=$3
local token
token=$(_get_token "$image")
local digest
digest=$(_get_digest "$image" "$tag" "$token")
local retval="null"
if [ "$digest" != "null" ]; then
retval=$(_get_image_configuration "$image" "$token" "$digest" "$label")
fi
echo "$retval"
}
get_image_layers() {
local image=$1
local tag=$2
local token
token=$(_get_token "$image")
_get_layers "$image" "$tag" "$token"
}
get_image_last_layer() {
local image=$1
local tag=$2
local token
token=$(_get_token "$image")
local layers
mapfile -t layers < <(_get_layers "$image" "$tag" "$token")
echo "${layers[-1]}"
}
_get_image_configuration() {
local image=$1
local token=$2
local digest=$3
local label=$4
curl \
--silent \
--location \
--header "Authorization: Bearer $token" \
"https://registry-1.docker.io/v2/$image/blobs/$digest" \
| jq -r ".config.Labels.\"$label\""
}
_get_token() {
local image=$1
curl \
--silent \
"https://auth.docker.io/token?scope=repository:$image:pull&service=registry.docker.io" \
| jq -r '.token'
}
_get_digest() {
local image=$1
local tag=$2
local token=$3
curl \
--silent \
--header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
--header "Authorization: Bearer $token" \
"https://registry-1.docker.io/v2/$image/manifests/$tag" \
| jq -r '.config.digest'
}
_get_layers() {
local image=$1
local tag=$2
local token=$3
curl \
--silent \
--header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
--header "Authorization: Bearer $token" \
"https://registry-1.docker.io/v2/$image/manifests/$tag" \
| jq -r '.layers[].digest'
}

View File

@ -23,25 +23,17 @@ GITHUB_REPO="${GITHUB_REPO-$ORIGINAL_GITHUB_REPO}"
URL_RELEASES="https://api.github.com/repos/${GITHUB_REPO}/branches?${GITHUB_OAUTH_PARAMS}"
# Composing the JQ commans to extract the most recent version number
JQ_BRANCHES='map(.name) | .[] | scan("^[^v].+") | match("^(master|develop).*") | .string'
JQ_NEXT='map(.name) | .[] | scan("^[^v].+") | match("^(develop-).*") | .string'
CURL="curl -sS"
# Querying the Github API to fetch all branches
BRANCHES=$($CURL "${URL_RELEASES}" | jq -r "$JQ_BRANCHES")
NEXT=$($CURL "${URL_RELEASES}" | jq -r "$JQ_NEXT")
###
# Building each branch
###
# keeping track whether an error occured
ERROR=0
# calling build.sh for each branch
for BRANCH in $BRANCHES; do
if [ -n "$NEXT" ]; then
# shellcheck disable=SC2068
./build.sh "${BRANCH}" $@ || ERROR=1
done
# returning whether an error occured
exit $ERROR
./build.sh "${NEXT}" $@
else
echo "No branch matching 'develop-*' found"
echo "::set-output name=skipped::true"
fi

131
build.sh
View File

@ -49,7 +49,7 @@ if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then
echo " DOCKERFILE The name of Dockerfile to use."
echo " Default: Dockerfile"
echo " DOCKER_FROM The base image to use."
echo " Default: Whatever is defined as default in the Dockerfile."
echo " Default: 'python:3.8-alpine'"
echo " DOCKER_TARGET A specific target to build."
echo " It's currently not possible to pass multiple targets."
echo " Default: main ldap"
@ -153,6 +153,13 @@ if [ ! -f "${DOCKERFILE}" ]; then
fi
fi
###
# Determining the value for DOCKER_FROM
###
if [ -z "$DOCKER_FROM" ]; then
DOCKER_FROM="python:3.8-alpine"
fi
###
# Variables for labelling the docker image
###
@ -167,9 +174,9 @@ PROJECT_VERSION="${PROJECT_VERSION-$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]
# Get the Git information from the netbox directory
if [ -d "${NETBOX_PATH}/.git" ]; then
NETBOX_GIT_REF=$(cd ${NETBOX_PATH}; git rev-parse HEAD)
NETBOX_GIT_BRANCH=$(cd ${NETBOX_PATH}; git rev-parse --abbrev-ref HEAD)
NETBOX_GIT_URL=$(cd ${NETBOX_PATH}; git remote get-url origin)
NETBOX_GIT_REF=$(cd "${NETBOX_PATH}"; git rev-parse HEAD)
NETBOX_GIT_BRANCH=$(cd "${NETBOX_PATH}"; git rev-parse --abbrev-ref HEAD)
NETBOX_GIT_URL=$(cd "${NETBOX_PATH}"; git remote get-url origin)
fi
###
@ -209,7 +216,7 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
TARGET_DOCKER_TAG="${TARGET_DOCKER_TAG}-${DOCKER_TARGET}"
fi
if [ -n "${GH_ACTION}" ]; then
echo "::set-env name=FINAL_DOCKER_TAG::${TARGET_DOCKER_TAG}"
echo "FINAL_DOCKER_TAG=${TARGET_DOCKER_TAG}" >> $GITHUB_ENV
echo "::set-output name=skipped::false"
fi
@ -217,15 +224,18 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
# composing the additional DOCKER_SHORT_TAG,
# 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
MAJOR=${BASH_REMATCH[1]}
MINOR=${BASH_REMATCH[2]}
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"
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
fi
@ -233,6 +243,48 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
# 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} python"
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`
###
@ -244,32 +296,35 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
)
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
DOCKER_BUILD_ARGS+=( -t "${TARGET_DOCKER_SHORT_TAG}" )
DOCKER_BUILD_ARGS+=( -t "${TARGET_DOCKER_LATEST_TAG}" )
fi
# --label
if [ "${DOCKER_TARGET}" == "main" ]; then
DOCKER_BUILD_ARGS+=(
--label "ORIGINAL_TAG=${TARGET_DOCKER_TAG}"
--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 "ORIGINAL_TAG=${TARGET_DOCKER_TAG}"
--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}"
--label "org.label-schema.vcs-ref=${GIT_REF}"
--label "org.opencontainers.image.revision=${GIT_REF}"
)
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
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
@ -289,23 +344,29 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
###
# Building the docker image
###
echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG}'."
$DRY docker build "${DOCKER_BUILD_ARGS[@]}" .
echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG}'"
if [ "${SHOULD_BUILD}" == "true" ]; then
echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG}'."
echo " Build reason set to: ${BUILD_REASON}"
$DRY docker build "${DOCKER_BUILD_ARGS[@]}" .
echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG}'"
echo "🔎 Inspecting labels on '${TARGET_DOCKER_TAG}'"
$DRY docker inspect "${TARGET_DOCKER_TAG}" --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
echo "⏫ Pushing '${TARGET_DOCKER_TAG}"
$DRY docker push "${TARGET_DOCKER_TAG}"
echo "✅ Finished pushing the Docker image '${TARGET_DOCKER_TAG}'."
source ./build-functions/docker-functions.sh
push_image_to_registry "${TARGET_DOCKER_TAG}"
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
echo "⏫ Pushing '${TARGET_DOCKER_SHORT_TAG}'"
$DRY docker push "${TARGET_DOCKER_SHORT_TAG}"
echo "✅ Finished pushing the Docker image '${TARGET_DOCKER_SHORT_TAG}'."
push_image_to_registry "${TARGET_DOCKER_SHORT_TAG}"
push_image_to_registry "${TARGET_DOCKER_LATEST_TAG}"
fi
fi
done

View File

@ -1,21 +1,28 @@
import os
import re
import socket
####
## We recommend to not edit this file.
## Create separate files to overwrite the settings.
## See `extra.py` as an example.
####
# For reference see http://netbox.readthedocs.io/en/latest/configuration/mandatory-settings/
# Based on https://github.com/netbox-community/netbox/blob/develop/netbox/netbox/configuration.example.py
import re
from os.path import dirname, abspath, join
from os import environ
# For reference see https://netbox.readthedocs.io/en/stable/configuration/
# Based on https://github.com/netbox-community/netbox/blob/master/netbox/netbox/configuration.example.py
# Read secret from file
def read_secret(secret_name):
def _read_secret(secret_name, default = None):
try:
f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8')
except EnvironmentError:
return ''
return default
else:
with f:
return f.readline().strip()
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_BASE_DIR = dirname(dirname(abspath(__file__)))
#########################
# #
@ -27,47 +34,49 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name.
#
# Example: ALLOWED_HOSTS = ['netbox.example.com', 'netbox.internal.local']
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '*').split(' ')
ALLOWED_HOSTS = environ.get('ALLOWED_HOSTS', '*').split(' ')
# PostgreSQL database configuration.
# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters:
# https://docs.djangoproject.com/en/stable/ref/settings/#databases
DATABASE = {
'NAME': os.environ.get('DB_NAME', 'netbox'), # Database name
'USER': os.environ.get('DB_USER', ''), # PostgreSQL username
'PASSWORD': os.environ.get('DB_PASSWORD', read_secret('db_password')),
'NAME': environ.get('DB_NAME', 'netbox'), # Database name
'USER': environ.get('DB_USER', ''), # PostgreSQL username
'PASSWORD': _read_secret('db_password', environ.get('DB_PASSWORD', '')),
# PostgreSQL password
'HOST': os.environ.get('DB_HOST', 'localhost'), # Database server
'PORT': os.environ.get('DB_PORT', ''), # Database port (leave blank for default)
'OPTIONS': {'sslmode': os.environ.get('DB_SSLMODE', 'prefer')},
'HOST': environ.get('DB_HOST', 'localhost'), # Database server
'PORT': environ.get('DB_PORT', ''), # Database port (leave blank for default)
'OPTIONS': {'sslmode': environ.get('DB_SSLMODE', 'prefer')},
# Database connection SSLMODE
'CONN_MAX_AGE': int(os.environ.get('DB_CONN_MAX_AGE', '300')),
# Database connection persistence
'CONN_MAX_AGE': int(environ.get('DB_CONN_MAX_AGE', '300')),
# Max database connection age
}
# Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
# configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
# to use two separate database IDs.
REDIS = {
'tasks': {
'HOST': environ.get('REDIS_HOST', 'localhost'),
'PORT': int(environ.get('REDIS_PORT', 6379)),
'PASSWORD': _read_secret('redis_password', environ.get('REDIS_PASSWORD', '')),
'DATABASE': int(environ.get('REDIS_DATABASE', 0)),
'SSL': environ.get('REDIS_SSL', 'False').lower() == 'true',
},
'caching': {
'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')),
'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', ''))),
'DATABASE': int(environ.get('REDIS_CACHE_DATABASE', 1)),
'SSL': environ.get('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False')).lower() == 'true',
},
}
# This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file.
# For optimal security, SECRET_KEY should be at least 50 characters in length and contain a mix of letters, numbers, and
# symbols. NetBox will not run without this defined. For more information, see
# https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-SECRET_KEY
SECRET_KEY = os.environ.get('SECRET_KEY', read_secret('secret_key'))
# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY
SECRET_KEY = _read_secret('secret_key', environ.get('SECRET_KEY', ''))
# Redis database settings. The Redis database is used for caching and background processing such as webhooks
REDIS = {
'webhooks': {
'HOST': os.environ.get('REDIS_HOST', 'localhost'),
'PORT': int(os.environ.get('REDIS_PORT', 6379)),
'PASSWORD': os.environ.get('REDIS_PASSWORD', read_secret('redis_password')),
'DATABASE': int(os.environ.get('REDIS_DATABASE', 0)),
'DEFAULT_TIMEOUT': int(os.environ.get('REDIS_TIMEOUT', 300)),
'SSL': os.environ.get('REDIS_SSL', 'False').lower() == 'true',
},
'caching': {
'HOST': os.environ.get('REDIS_CACHE_HOST', os.environ.get('REDIS_HOST', 'localhost')),
'PORT': int(os.environ.get('REDIS_CACHE_PORT', os.environ.get('REDIS_PORT', 6379))),
'PASSWORD': os.environ.get('REDIS_CACHE_PASSWORD', os.environ.get('REDIS_PASSWORD', read_secret('redis_cache_password'))),
'DATABASE': int(os.environ.get('REDIS_CACHE_DATABASE', 1)),
'DEFAULT_TIMEOUT': int(os.environ.get('REDIS_CACHE_TIMEOUT', os.environ.get('REDIS_TIMEOUT', 300))),
'SSL': os.environ.get('REDIS_CACHE_SSL', os.environ.get('REDIS_SSL', 'False')).lower() == 'true',
},
}
#########################
# #
@ -81,120 +90,159 @@ ADMINS = [
# ['John Doe', 'jdoe@example.com'],
]
# URL schemes that are allowed within links in NetBox
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
# content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
BANNER_TOP = os.environ.get('BANNER_TOP', '')
BANNER_BOTTOM = os.environ.get('BANNER_BOTTOM', '')
BANNER_TOP = environ.get('BANNER_TOP', '')
BANNER_BOTTOM = environ.get('BANNER_BOTTOM', '')
# Text to include on the login page above the login form. HTML is allowed.
BANNER_LOGIN = os.environ.get('BANNER_LOGIN', '')
BANNER_LOGIN = environ.get('BANNER_LOGIN', '')
# Base URL path if accessing NetBox within a directory. For example, if installed at http://example.com/netbox/, set:
# BASE_PATH = 'netbox/'
BASE_PATH = os.environ.get('BASE_PATH', '')
BASE_PATH = environ.get('BASE_PATH', '')
# Cache timeout in seconds. Set to 0 to dissable caching. Defaults to 900 (15 minutes)
CACHE_TIMEOUT = int(os.environ.get('CACHE_TIMEOUT', 900))
CACHE_TIMEOUT = int(environ.get('CACHE_TIMEOUT', 900))
# Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90)
CHANGELOG_RETENTION = int(os.environ.get('CHANGELOG_RETENTION', 90))
CHANGELOG_RETENTION = int(environ.get('CHANGELOG_RETENTION', 90))
# 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
# CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers
CORS_ORIGIN_ALLOW_ALL = os.environ.get('CORS_ORIGIN_ALLOW_ALL', 'False').lower() == 'true'
CORS_ORIGIN_WHITELIST = list(filter(None, os.environ.get('CORS_ORIGIN_WHITELIST', 'https://localhost').split(' ')))
CORS_ORIGIN_REGEX_WHITELIST = [re.compile(r) for r in list(filter(None, os.environ.get('CORS_ORIGIN_REGEX_WHITELIST', '').split(' ')))]
CORS_ORIGIN_ALLOW_ALL = environ.get('CORS_ORIGIN_ALLOW_ALL', 'False').lower() == 'true'
CORS_ORIGIN_WHITELIST = list(filter(None, environ.get('CORS_ORIGIN_WHITELIST', 'https://localhost').split(' ')))
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
# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging
# on a production system.
DEBUG = os.environ.get('DEBUG', 'False').lower() == 'true'
DEBUG = environ.get('DEBUG', 'False').lower() == 'true'
# Email settings
EMAIL = {
'SERVER': os.environ.get('EMAIL_SERVER', 'localhost'),
'PORT': int(os.environ.get('EMAIL_PORT', 25)),
'USERNAME': os.environ.get('EMAIL_USERNAME', ''),
'PASSWORD': os.environ.get('EMAIL_PASSWORD', read_secret('email_password')),
'TIMEOUT': int(os.environ.get('EMAIL_TIMEOUT', 10)), # seconds
'FROM_EMAIL': os.environ.get('EMAIL_FROM', ''),
'SERVER': environ.get('EMAIL_SERVER', 'localhost'),
'PORT': int(environ.get('EMAIL_PORT', 25)),
'USERNAME': environ.get('EMAIL_USERNAME', ''),
'PASSWORD': _read_secret('email_password', environ.get('EMAIL_PASSWORD', '')),
'USE_SSL': environ.get('EMAIL_USE_SSL', 'False').lower() == 'true',
'USE_TLS': environ.get('EMAIL_USE_TLS', 'False').lower() == 'true',
'SSL_CERTFILE': environ.get('EMAIL_SSL_CERTFILE', ''),
'SSL_KEYFILE': environ.get('EMAIL_SSL_KEYFILE', ''),
'TIMEOUT': int(environ.get('EMAIL_TIMEOUT', 10)), # seconds
'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 (all prefixes and IP addresses not assigned to a VRF),
# set ENFORCE_GLOBAL_UNIQUE to True.
ENFORCE_GLOBAL_UNIQUE = os.environ.get('ENFORCE_GLOBAL_UNIQUE', 'False').lower() == 'true'
# 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.
ENFORCE_GLOBAL_UNIQUE = environ.get('ENFORCE_GLOBAL_UNIQUE', 'False').lower() == 'true'
# 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.
EXEMPT_VIEW_PERMISSIONS = list(filter(None, os.environ.get('EXEMPT_VIEW_PERMISSIONS', '').split(' ')))
EXEMPT_VIEW_PERMISSIONS = list(filter(None, environ.get('EXEMPT_VIEW_PERMISSIONS', '').split(' ')))
# Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
# https://docs.djangoproject.com/en/1.11/topics/logging/
# https://docs.djangoproject.com/en/stable/topics/logging/
LOGGING = {}
# 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.
LOGIN_REQUIRED = os.environ.get('LOGIN_REQUIRED', 'False').lower() == 'true'
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
# re-authenticate. (Default: 1209600 [14 days])
LOGIN_TIMEOUT = environ.get('LOGIN_TIMEOUT', None)
# Setting this to True will display a "maintenance mode" banner at the top of every page.
MAINTENANCE_MODE = os.environ.get('MAINTENANCE_MODE', 'False').lower() == 'true'
MAINTENANCE_MODE = environ.get('MAINTENANCE_MODE', 'False').lower() == 'true'
# 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
# all objects by specifying "?limit=0".
MAX_PAGE_SIZE = int(os.environ.get('MAX_PAGE_SIZE', 1000))
MAX_PAGE_SIZE = int(environ.get('MAX_PAGE_SIZE', 1000))
# 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.
MEDIA_ROOT = os.environ.get('MEDIA_ROOT', os.path.join(BASE_DIR, 'media'))
MEDIA_ROOT = environ.get('MEDIA_ROOT', join(_BASE_DIR, 'media'))
# Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics'
METRICS_ENABLED = os.environ.get('METRICS_ENABLED', 'False').lower() == 'true'
METRICS_ENABLED = environ.get('METRICS_ENABLED', 'False').lower() == 'true'
# Credentials that NetBox will use to access live devices.
NAPALM_USERNAME = os.environ.get('NAPALM_USERNAME', '')
NAPALM_PASSWORD = os.environ.get('NAPALM_PASSWORD', read_secret('napalm_password'))
# Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM.
NAPALM_USERNAME = environ.get('NAPALM_USERNAME', '')
NAPALM_PASSWORD = _read_secret('napalm_password', environ.get('NAPALM_PASSWORD', ''))
# NAPALM timeout (in seconds). (Default: 30)
NAPALM_TIMEOUT = int(os.environ.get('NAPALM_TIMEOUT', 30))
NAPALM_TIMEOUT = int(environ.get('NAPALM_TIMEOUT', 30))
# NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must
# be provided as a dictionary.
NAPALM_ARGS = {}
# Determine how many objects to display per page within a list. (Default: 50)
PAGINATE_COUNT = int(os.environ.get('PAGINATE_COUNT', 50))
PAGINATE_COUNT = int(environ.get('PAGINATE_COUNT', 50))
# Enable installed plugins. Add the name of each plugin to the list.
PLUGINS = []
# 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.
PLUGINS_CONFIG = {
}
# 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 = os.environ.get('PREFER_IPV4', 'False').lower() == 'true'
PREFER_IPV4 = environ.get('PREFER_IPV4', 'False').lower() == 'true'
# This determines how often the GitHub API is called to check the latest release of NetBox in seconds. Must be at least 1 hour.
RELEASE_CHECK_TIMEOUT = os.environ.get('RELEASE_CHECK_TIMEOUT', 24 * 3600)
# Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1.
RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = int(environ.get('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 22))
RACK_ELEVATION_DEFAULT_UNIT_WIDTH = int(environ.get('RACK_ELEVATION_DEFAULT_UNIT_WIDTH', 220))
# Remote authentication support
REMOTE_AUTH_ENABLED = environ.get('REMOTE_AUTH_ENABLED', 'False').lower() == 'true'
REMOTE_AUTH_BACKEND = environ.get('REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend')
REMOTE_AUTH_HEADER = environ.get('REMOTE_AUTH_HEADER', 'HTTP_REMOTE_USER')
REMOTE_AUTH_AUTO_CREATE_USER = environ.get('REMOTE_AUTH_AUTO_CREATE_USER', 'True').lower() == 'true'
REMOTE_AUTH_DEFAULT_GROUPS = list(filter(None, environ.get('REMOTE_AUTH_DEFAULT_GROUPS', '').split(' ')))
# This determines how often the GitHub API is called to check the latest release of NetBox. Must be at least 1 hour.
RELEASE_CHECK_TIMEOUT = int(environ.get('RELEASE_CHECK_TIMEOUT', 24 * 3600))
# 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.
# https://api.github.com/repos/netbox-community/netbox/releases
RELEASE_CHECK_URL = os.environ.get('RELEASE_CHECK_URL', None)
RELEASE_CHECK_URL = environ.get('RELEASE_CHECK_URL', None)
# 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.
REPORTS_ROOT = os.environ.get('REPORTS_ROOT', '/etc/netbox/reports')
REPORTS_ROOT = environ.get('REPORTS_ROOT', '/etc/netbox/reports')
# Maximum execution time for background tasks, in seconds.
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
# this setting is derived from the installed location.
SCRIPTS_ROOT = os.environ.get('SCRIPTS_ROOT', '/etc/netbox/scripts')
SCRIPTS_ROOT = environ.get('SCRIPTS_ROOT', '/etc/netbox/scripts')
# By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use
# 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.
SESSION_FILE_PATH = environ.get('REPORTS_ROOT', None)
# Time zone (default: UTC)
TIME_ZONE = os.environ.get('TIME_ZONE', 'UTC')
TIME_ZONE = environ.get('TIME_ZONE', 'UTC')
# Date/time formatting. See the following link for supported formats:
# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
DATE_FORMAT = os.environ.get('DATE_FORMAT', 'N j, Y')
SHORT_DATE_FORMAT = os.environ.get('SHORT_DATE_FORMAT', 'Y-m-d')
TIME_FORMAT = os.environ.get('TIME_FORMAT', 'g:i a')
SHORT_TIME_FORMAT = os.environ.get('SHORT_TIME_FORMAT', 'H:i:s')
DATETIME_FORMAT = os.environ.get('DATETIME_FORMAT', 'N j, Y g:i a')
SHORT_DATETIME_FORMAT = os.environ.get('SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
# https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date
DATE_FORMAT = environ.get('DATE_FORMAT', 'N j, Y')
SHORT_DATE_FORMAT = environ.get('SHORT_DATE_FORMAT', 'Y-m-d')
TIME_FORMAT = environ.get('TIME_FORMAT', 'g:i a')
SHORT_TIME_FORMAT = environ.get('SHORT_TIME_FORMAT', 'H:i:s')
DATETIME_FORMAT = environ.get('DATETIME_FORMAT', 'N j, Y g:i a')
SHORT_DATETIME_FORMAT = environ.get('SHORT_DATETIME_FORMAT', 'Y-m-d H:i')

55
configuration/extra.py Normal file
View File

@ -0,0 +1,55 @@
####
## This file contains extra configuration options that can't be configured
## directly through environment variables.
####
## 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).
# ADMINS = [
# # ['John Doe', 'jdoe@example.com'],
# ]
## URL schemes that are allowed within links in NetBox
# ALLOWED_URL_SCHEMES = (
# 'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp',
# )
## NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must
## be provided as a dictionary.
# NAPALM_ARGS = {}
## Enable installed plugins. Add the name of each plugin to the list.
# from netbox.configuration.configuration import PLUGINS
# PLUGINS.append('my_plugin')
## 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.
# from netbox.configuration.configuration import PLUGINS_CONFIG
# PLUGINS_CONFIG['my_plugin'] = {
# 'foo': 'bar',
# 'buzz': 'bazz'
# }
## Remote authentication support
# REMOTE_AUTH_DEFAULT_PERMISSIONS = {}
## By default uploaded media is stored on the local filesystem. Using Django-storages is also supported. Provide the
## class path of the storage driver in STORAGE_BACKEND and any configuration options in STORAGE_CONFIG. For example:
# STORAGE_BACKEND = 'storages.backends.s3boto3.S3Boto3Storage'
# STORAGE_CONFIG = {
# 'AWS_ACCESS_KEY_ID': 'Key ID',
# 'AWS_SECRET_ACCESS_KEY': 'Secret',
# 'AWS_STORAGE_BUCKET_NAME': 'netbox',
# 'AWS_S3_REGION_NAME': 'eu-west-1',
# }
## This file can contain arbitrary Python code, e.g.:
# from datetime import datetime
# now = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
# BANNER_TOP = f'<marquee width="200px">This instance started on {now}.</marquee>'

View File

@ -1,21 +1,21 @@
import ldap
import os
from django_auth_ldap.config import LDAPSearch
from importlib import import_module
from os import environ
# Read secret from file
def read_secret(secret_name):
def _read_secret(secret_name, default=None):
try:
f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8')
except EnvironmentError:
return ''
return default
else:
with f:
return f.readline().strip()
# Import and return the group type based on string name
def import_group_type(group_type_name):
def _import_group_type(group_type_name):
mod = import_module('django_auth_ldap.config')
try:
return getattr(mod, group_type_name)()
@ -23,7 +23,7 @@ def import_group_type(group_type_name):
return None
# Server URI
AUTH_LDAP_SERVER_URI = os.environ.get('AUTH_LDAP_SERVER_URI', '')
AUTH_LDAP_SERVER_URI = environ.get('AUTH_LDAP_SERVER_URI', '')
# The following may be needed if you are binding to Active Directory.
AUTH_LDAP_CONNECTION_OPTIONS = {
@ -31,50 +31,54 @@ AUTH_LDAP_CONNECTION_OPTIONS = {
}
# Set the DN and password for the NetBox service account.
AUTH_LDAP_BIND_DN = os.environ.get('AUTH_LDAP_BIND_DN', '')
AUTH_LDAP_BIND_PASSWORD = os.environ.get('AUTH_LDAP_BIND_PASSWORD', read_secret('auth_ldap_bind_password'))
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.
AUTH_LDAP_USER_DN_TEMPLATE = os.environ.get('AUTH_LDAP_USER_DN_TEMPLATE', None)
AUTH_LDAP_USER_DN_TEMPLATE = environ.get('AUTH_LDAP_USER_DN_TEMPLATE', None)
# Enable STARTTLS for ldap authentication.
AUTH_LDAP_START_TLS = environ.get('AUTH_LDAP_START_TLS', 'False').lower() == 'true'
# Include this setting if you want to ignore certificate errors. This might be needed to accept a self-signed cert.
# Note that this is a NetBox-specific setting which sets:
# ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
LDAP_IGNORE_CERT_ERRORS = os.environ.get('LDAP_IGNORE_CERT_ERRORS', 'False').lower() == 'true'
LDAP_IGNORE_CERT_ERRORS = environ.get('LDAP_IGNORE_CERT_ERRORS', 'False').lower() == 'true'
AUTH_LDAP_USER_SEARCH_BASEDN = os.environ.get('AUTH_LDAP_USER_SEARCH_BASEDN', '')
AUTH_LDAP_USER_SEARCH_ATTR = os.environ.get('AUTH_LDAP_USER_SEARCH_ATTR', 'sAMAccountName')
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 = LDAPSearch(AUTH_LDAP_USER_SEARCH_BASEDN,
ldap.SCOPE_SUBTREE,
"(" + AUTH_LDAP_USER_SEARCH_ATTR + "=%(user)s)")
# This search ought to return all groups to which the user belongs. django_auth_ldap uses this to determine group
# heirarchy.
AUTH_LDAP_GROUP_SEARCH_BASEDN = os.environ.get('AUTH_LDAP_GROUP_SEARCH_BASEDN', '')
AUTH_LDAP_GROUP_SEARCH_CLASS = os.environ.get('AUTH_LDAP_GROUP_SEARCH_CLASS', 'group')
AUTH_LDAP_GROUP_SEARCH_BASEDN = environ.get('AUTH_LDAP_GROUP_SEARCH_BASEDN', '')
AUTH_LDAP_GROUP_SEARCH_CLASS = environ.get('AUTH_LDAP_GROUP_SEARCH_CLASS', 'group')
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(AUTH_LDAP_GROUP_SEARCH_BASEDN, ldap.SCOPE_SUBTREE,
"(objectClass=" + AUTH_LDAP_GROUP_SEARCH_CLASS + ")")
AUTH_LDAP_GROUP_TYPE = import_group_type(os.environ.get('AUTH_LDAP_GROUP_TYPE', 'GroupOfNamesType'))
AUTH_LDAP_GROUP_TYPE = _import_group_type(environ.get('AUTH_LDAP_GROUP_TYPE', 'GroupOfNamesType'))
# Define a group required to login.
AUTH_LDAP_REQUIRE_GROUP = os.environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', '')
AUTH_LDAP_REQUIRE_GROUP = environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', '')
# Define special user types using groups. Exercise great caution when assigning superuser status.
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"is_active": os.environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', ''),
"is_staff": os.environ.get('AUTH_LDAP_IS_ADMIN_DN', ''),
"is_superuser": os.environ.get('AUTH_LDAP_IS_SUPERUSER_DN', '')
"is_active": environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', ''),
"is_staff": environ.get('AUTH_LDAP_IS_ADMIN_DN', ''),
"is_superuser": environ.get('AUTH_LDAP_IS_SUPERUSER_DN', '')
}
# For more granular permissions, we can map LDAP groups to Django groups.
AUTH_LDAP_FIND_GROUP_PERMS = os.environ.get('AUTH_LDAP_FIND_GROUP_PERMS', 'True').lower() == 'true'
AUTH_LDAP_FIND_GROUP_PERMS = environ.get('AUTH_LDAP_FIND_GROUP_PERMS', 'True').lower() == 'true'
AUTH_LDAP_MIRROR_GROUPS = environ.get('AUTH_LDAP_MIRROR_GROUPS', '').lower() == 'true'
# Cache groups for one hour to reduce LDAP traffic
AUTH_LDAP_CACHE_TIMEOUT = int(os.environ.get('AUTH_LDAP_CACHE_TIMEOUT', 3600))
AUTH_LDAP_CACHE_TIMEOUT = int(environ.get('AUTH_LDAP_CACHE_TIMEOUT', 3600))
# Populate the Django user from the LDAP directory.
AUTH_LDAP_USER_ATTR_MAP = {
"first_name": os.environ.get('AUTH_LDAP_ATTR_FIRSTNAME', 'givenName'),
"last_name": os.environ.get('AUTH_LDAP_ATTR_LASTNAME', 'sn'),
"email": os.environ.get('AUTH_LDAP_ATTR_MAIL', 'mail')
"first_name": environ.get('AUTH_LDAP_ATTR_FIRSTNAME', 'givenName'),
"last_name": environ.get('AUTH_LDAP_ATTR_LASTNAME', 'sn'),
"email": environ.get('AUTH_LDAP_ATTR_MAIL', 'mail')
}

View File

@ -19,7 +19,7 @@ services:
- netbox-media-files:/opt/netbox/netbox/media:z
nginx:
command: nginx -c /etc/netbox-nginx/nginx.conf
image: nginx:1.17-alpine
image: nginx:1.19-alpine
depends_on:
- netbox
ports:
@ -28,17 +28,17 @@ services:
- netbox-static-files:/opt/netbox/netbox/static:ro
- netbox-nginx-config:/etc/netbox-nginx/:ro
postgres:
image: postgres:11-alpine
image: postgres:12-alpine
env_file: env/postgres.env
redis:
image: redis:5-alpine
image: redis:6-alpine
command:
- sh
- -c # this is to evaluate the $REDIS_PASSWORD from the env
- redis-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
env_file: env/redis.env
redis-cache:
image: redis:5-alpine
image: redis:6-alpine
command:
- sh
- -c # this is to evaluate the $REDIS_PASSWORD from the env

View File

@ -27,9 +27,11 @@ services:
- /opt/netbox/netbox/manage.py
command:
- rqworker
# nginx
nginx:
command: nginx -c /etc/netbox-nginx/nginx.conf
image: nginx:1.17-alpine
image: nginx:1.19-alpine
depends_on:
- netbox
ports:
@ -37,13 +39,17 @@ services:
volumes:
- netbox-static-files:/opt/netbox/netbox/static:ro
- netbox-nginx-config:/etc/netbox-nginx/:ro
# postgres
postgres:
image: postgres:11-alpine
image: postgres:12-alpine
env_file: env/postgres.env
volumes:
- netbox-postgres-data:/var/lib/postgresql/data
# redis
redis:
image: redis:5-alpine
image: redis:6-alpine
command:
- sh
- -c # this is to evaluate the $REDIS_PASSWORD from the env
@ -52,12 +58,13 @@ services:
volumes:
- netbox-redis-data:/data
redis-cache:
image: redis:5-alpine
image: redis:6-alpine
command:
- sh
- -c # this is to evaluate the $REDIS_PASSWORD from the env
- redis-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
env_file: env/redis-cache.env
volumes:
netbox-static-files:
driver: local

View File

@ -1,10 +1,79 @@
## Generic Parts
# These functions are providing the functionality to load
# arbitrary configuration files.
#
# They can be imported by other code (see `ldap_config.py` for an example).
from os.path import abspath, isfile
from os import scandir
import importlib.util
import sys
try:
spec = importlib.util.spec_from_file_location('configuration', '/etc/netbox/config/configuration.py')
def _filename(f):
return f.name
def _import(module_name, path, loaded_configurations):
spec = importlib.util.spec_from_file_location('', path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
sys.modules['netbox.configuration'] = module
except:
raise ImportError('')
sys.modules[module_name] = module
loaded_configurations.insert(0, module)
print(f"🧬 loaded config '{path}'")
def read_configurations(config_module, config_dir, main_config):
loaded_configurations = []
main_config_path = abspath(f'{config_dir}/{main_config}.py')
if isfile(main_config_path):
_import(f'{config_module}.{main_config}', main_config_path, loaded_configurations)
else:
print(f"⚠️ Main configuration '{main_config_path}' not found.")
with scandir(config_dir) as it:
for f in sorted(it, key=_filename):
if not f.is_file():
continue
if f.name.startswith('__'):
continue
if not f.name.endswith('.py'):
continue
if f.name == f'{config_dir}.py':
continue
module_name = f"{config_module}.{f.name[:-len('.py')]}".replace(".", "_")
_import(module_name, f.path, loaded_configurations)
if len(loaded_configurations) == 0:
print(f"‼️ No configuration files found in '{config_dir}'.")
raise ImportError(f"No configuration files found in '{config_dir}'.")
return loaded_configurations
## Specific Parts
# This section's code actually loads the various configuration files
# into the module with the given name.
# It contains the logic to resolve arbitrary configuration options by
# levaraging dynamic programming using `__getattr__`.
_loaded_configurations = read_configurations(
config_dir = '/etc/netbox/config/',
config_module = 'netbox.configuration',
main_config = 'configuration')
def __getattr__(name):
for config in _loaded_configurations:
try:
return getattr(config, name)
except:
pass
raise AttributeError

View File

@ -31,19 +31,15 @@ else
if [ -z ${SUPERUSER_EMAIL+x} ]; then
SUPERUSER_EMAIL='admin@example.com'
fi
if [ -z ${SUPERUSER_PASSWORD+x} ]; then
if [ -f "/run/secrets/superuser_password" ]; then
SUPERUSER_PASSWORD="$(< /run/secrets/superuser_password)"
else
SUPERUSER_PASSWORD='admin'
fi
if [ -f "/run/secrets/superuser_password" ]; then
SUPERUSER_PASSWORD="$(< /run/secrets/superuser_password)"
elif [ -z ${SUPERUSER_PASSWORD+x} ]; then
SUPERUSER_PASSWORD='admin'
fi
if [ -z ${SUPERUSER_API_TOKEN+x} ]; then
if [ -f "/run/secrets/superuser_api_token" ]; then
SUPERUSER_API_TOKEN="$(< /run/secrets/superuser_api_token)"
else
SUPERUSER_API_TOKEN='0123456789abcdef0123456789abcdef01234567'
fi
if [ -f "/run/secrets/superuser_api_token" ]; then
SUPERUSER_API_TOKEN="$(< /run/secrets/superuser_api_token)"
elif [ -z ${SUPERUSER_API_TOKEN+x} ]; then
SUPERUSER_API_TOKEN='0123456789abcdef0123456789abcdef01234567'
fi
./manage.py shell --interface python << END

View File

@ -5,4 +5,4 @@ workers = 3
errorlog = '-'
accesslog = '-'
capture_output = False
loglevel = 'debug'
loglevel = 'info'

View File

@ -1,10 +1,21 @@
import importlib.util
import sys
from .configuration import read_configurations
try:
spec = importlib.util.spec_from_file_location('ldap_config', '/etc/netbox/config/ldap_config.py')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
sys.modules['netbox.ldap_config'] = module
except:
raise ImportError('')
_loaded_configurations = read_configurations(
config_dir = '/etc/netbox/config/ldap/',
config_module = 'netbox.configuration.ldap',
main_config = 'ldap_config')
def __getattr__(name):
for config in _loaded_configurations:
try:
return getattr(config, name)
except:
pass
raise AttributeError
def __dir__():
names = []
for config in _loaded_configurations:
names.extend(config.__dir__())
return names

View File

@ -29,8 +29,16 @@ http {
proxy_pass http://netbox:8001;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
}
}
server {
listen 8081;
access_log off;
location = /stub_status {
stub_status;
}
}
}

10
env/netbox.env vendored
View File

@ -9,11 +9,17 @@ EMAIL_USERNAME=netbox
EMAIL_PASSWORD=
EMAIL_TIMEOUT=5
EMAIL_FROM=netbox@bar.com
# EMAIL_USE_SSL and EMAIL_USE_TLS are mutually exclusive, i.e. they can't both be `true`!
EMAIL_USE_SSL=false
EMAIL_USE_TLS=false
EMAIL_SSL_CERTFILE=
EMAIL_SSL_KEYFILE=
MAX_PAGE_SIZE=1000
MEDIA_ROOT=/opt/netbox/netbox/media
METRICS_ENABLED=false
NAPALM_USERNAME=
NAPALM_PASSWORD=
NAPALM_TIMEOUT=10
MAX_PAGE_SIZE=1000
REDIS_HOST=redis
REDIS_PASSWORD=H733Kdjndks81
REDIS_DATABASE=0
@ -22,6 +28,7 @@ REDIS_CACHE_HOST=redis-cache
REDIS_CACHE_PASSWORD=t4Ph722qJ5QHeQ1qfu36
REDIS_CACHE_DATABASE=1
REDIS_CACHE_SSL=false
RELEASE_CHECK_URL=https://api.github.com/repos/netbox-community/netbox/releases
SECRET_KEY=r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj
SKIP_STARTUP_SCRIPTS=false
SKIP_SUPERUSER=false
@ -30,4 +37,3 @@ SUPERUSER_EMAIL=admin@example.com
SUPERUSER_PASSWORD=admin
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
WEBHOOKS_ENABLED=true
RELEASE_CHECK_URL=https://api.github.com/repos/netbox-community/netbox/releases

View File

@ -29,6 +29,8 @@
# rack: rack-02
# face: front
# position: 2
# primary_ip4: 10.1.1.2/24
# primary_ip6: 2001:db8:a000:1::2/64
# custom_fields:
# text_field: Description
# - name: server03

View File

@ -1,7 +1,7 @@
# - name: AMS 1
# slug: ams1
# region: Downtown
# status: 1
# status: active
# facility: Amsterdam 1
# asn: 12345
# custom_fields:
@ -9,7 +9,7 @@
# - name: AMS 2
# slug: ams2
# region: Downtown
# status: 1
# status: active
# facility: Amsterdam 2
# asn: 54321
# custom_fields:
@ -17,7 +17,7 @@
# - name: AMS 3
# slug: ams3
# region: Suburbs
# status: 1
# status: active
# facility: Amsterdam 3
# asn: 67890
# custom_fields:
@ -25,7 +25,7 @@
# - name: SING 1
# slug: sing1
# region: Singapore
# status: 1
# status: active
# facility: Singapore 1
# asn: 09876
# custom_fields:

View File

@ -21,6 +21,8 @@
# 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

@ -1,4 +1,4 @@
from dcim.constants import CONNECTION_STATUS_PLANNED, DEVICE_STATUS_ACTIVE
from dcim.choices import DeviceStatusChoices
from dcim.models import ConsolePort, Device, PowerPort
from extras.reports import Report
@ -9,13 +9,14 @@ class DeviceConnectionsReport(Report):
def test_console_connection(self):
# Check that every console port for every active device has a connection defined.
for console_port in ConsolePort.objects.select_related('device').filter(device__status=DEVICE_STATUS_ACTIVE):
active = DeviceStatusChoices.STATUS_ACTIVE
for console_port in ConsolePort.objects.prefetch_related('device').filter(device__status=active):
if console_port.connected_endpoint is None:
self.log_failure(
console_port.device,
"No console connection defined for {}".format(console_port.name)
)
elif console_port.connection_status == CONNECTION_STATUS_PLANNED:
elif not console_port.connection_status:
self.log_warning(
console_port.device,
"Console connection for {} marked as planned".format(console_port.name)
@ -26,12 +27,12 @@ class DeviceConnectionsReport(Report):
def test_power_connections(self):
# Check that every active device has at least two connected power supplies.
for device in Device.objects.filter(status=DEVICE_STATUS_ACTIVE):
for device in Device.objects.filter(status=DeviceStatusChoices.STATUS_ACTIVE):
connected_ports = 0
for power_port in PowerPort.objects.filter(device=device):
if power_port.connected_endpoint is not None:
connected_ports += 1
if power_port.connection_status == CONNECTION_STATUS_PLANNED:
if not power_port.connection_status:
self.log_warning(
device,
"Power connection for {} marked as planned".format(power_port.name)
@ -43,4 +44,3 @@ class DeviceConnectionsReport(Report):
)
else:
self.log_success(device)

View File

@ -12,7 +12,7 @@ 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)
password = user_details.get('password', 0) or User.objects.make_random_password())
print("👤 Created user",username)

View File

@ -1,5 +1,5 @@
from dcim.models import RackRole
from utilities.forms import COLOR_CHOICES
from utilities.choices import ColorChoices
from startup_script_utils import load_yaml
import sys
@ -13,7 +13,7 @@ for params in rack_roles:
if 'color' in params:
color = params.pop('color')
for color_tpl in COLOR_CHOICES:
for color_tpl in ColorChoices:
if color in color_tpl:
params['color'] = color_tpl[0]

View File

@ -1,5 +1,6 @@
from dcim.models import DeviceRole
from utilities.forms import COLOR_CHOICES
from utilities.choices import ColorChoices
from startup_script_utils import load_yaml
import sys
@ -13,7 +14,7 @@ for params in device_roles:
if 'color' in params:
color = params.pop('color')
for color_tpl in COLOR_CHOICES:
for color_tpl in ColorChoices:
if color in color_tpl:
params['color'] = color_tpl[0]

View File

@ -1,5 +1,4 @@
from dcim.models import Site, Rack, DeviceRole, DeviceType, Device, Platform
from ipam.models import IPAddress
from virtualization.models import Cluster
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
@ -21,13 +20,14 @@ optional_assocs = {
'tenant': (Tenant, 'name'),
'platform': (Platform, 'name'),
'rack': (Rack, 'name'),
'cluster': (Cluster, 'name'),
'primary_ip4': (IPAddress, 'address'),
'primary_ip6': (IPAddress, 'address')
'cluster': (Cluster, 'name')
}
for params in devices:
custom_fields = params.pop('custom_fields', None)
# 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

View File

@ -22,6 +22,9 @@ optional_assocs = {
for params in virtual_machines:
custom_fields = params.pop('custom_fields', None)
# 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

View File

@ -1,5 +1,4 @@
from dcim.models import Interface
from virtualization.models import VirtualMachine
from virtualization.models import VirtualMachine, VMInterface
from extras.models import CustomField, CustomFieldValue
from startup_script_utils import load_yaml
import sys
@ -22,7 +21,7 @@ for params in interfaces:
params[assoc] = model.objects.get(**query)
interface, created = Interface.objects.get_or_create(**params)
interface, created = VMInterface.objects.get_or_create(**params)
if created:
if custom_fields is not None:

View File

@ -1,12 +1,14 @@
from ipam.models import IPAddress, VRF
from dcim.models import Device, Interface
from virtualization.models import VirtualMachine
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
import sys
from dcim.models import Device, Interface
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from extras.models import CustomField, CustomFieldValue
from ipam.models import VRF, IPAddress
from netaddr import IPNetwork
from startup_script_utils import load_yaml
import sys
from tenancy.models import Tenant
from virtualization.models import VirtualMachine, VMInterface
ip_addresses = load_yaml('/opt/netbox/initializers/ip_addresses.yml')
@ -16,9 +18,12 @@ if ip_addresses is None:
optional_assocs = {
'tenant': (Tenant, 'name'),
'vrf': (VRF, 'name'),
'interface': (Interface, '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:
vm = params.pop('virtual_machine', None)
device = params.pop('device', None)
@ -35,13 +40,17 @@ for params in ip_addresses:
if assoc == 'interface':
if vm:
vm_id = VirtualMachine.objects.get(name=vm).id
query = { field: params.pop(assoc), "virtual_machine_id": 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 = { field: params.pop(assoc), "device_id": dev_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)
params[assoc] = model.objects.get(**query)
ip_address, created = IPAddress.objects.get_or_create(**params)

View File

@ -0,0 +1,43 @@
from dcim.models import Device
from ipam.models import IPAddress
from virtualization.models import VirtualMachine
from startup_script_utils import load_yaml
import sys
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')
if devices is None and virtual_machines is None:
sys.exit()
optional_assocs = {
'primary_ip4': (IPAddress, 'address'),
'primary_ip6': (IPAddress, 'address')
}
link_primary_ip(devices, Device)
link_primary_ip(virtual_machines, VirtualMachine)

View File

@ -9,10 +9,21 @@ this_dir = dirname(abspath(__file__))
def filename(f):
return f.name
with scandir(dirname(abspath(__file__))) as it:
with scandir(this_dir) as it:
for f in sorted(it, key = filename):
if f.name.startswith('__') or not f.is_file():
if not f.is_file():
continue
print(f"Running {f.path}")
runpy.run_path(f.path)
if f.name.startswith('__'):
continue
if not f.name.endswith('.py'):
continue
print(f"▶️ Running the startup script {f.path}")
try:
runpy.run_path(f.path)
except SystemExit as e:
if e.code is not None and e.code != 0:
print(f"‼️ The startup script {f.path} returned with code {e.code}, exiting.")
raise