Compare commits

..

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

114 changed files with 2864 additions and 2250 deletions

View File

@ -7,4 +7,5 @@ build*
docker-compose.override.yml docker-compose.override.yml
.netbox/.git* .netbox/.git*
.netbox/.travis.yml .netbox/.travis.yml
.netbox/docs
.netbox/scripts .netbox/scripts

23
.ecrc
View File

@ -1,23 +0,0 @@
{
"Verbose": false,
"Debug": false,
"IgnoreDefaults": false,
"SpacesAftertabs": false,
"NoColor": false,
"Exclude": [
"LICENSE",
"\\.initializers",
"\\.vscode"
],
"AllowedContentTypes": [],
"PassedFiles": [],
"Disable": {
// set these options to true to disable specific checks
"EndOfLine": false,
"Indentation": false,
"InsertFinalNewline": false,
"TrimTrailingWhitespace": false,
"IndentSize": true,
"MaxLineLength": false
}
}

View File

@ -1,11 +0,0 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2
[*.py]
indent_size = 4

View File

@ -1,7 +0,0 @@
[flake8]
max-line-length = 100
extend-ignore = E203, W503
per-file-ignores =
configuration/*:E131,E251,E266,E302,E305,E501,E722
startup_scripts/startup_script_utils/__init__.py:F401
docker/*:E266,E722

14
.github/FUNDING.yml vendored
View File

@ -1,14 +0,0 @@
# 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']

77
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,77 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
<!--
Before raising an issue here, answer the following questions for yourself, please:
* Did you read through the troubleshooting section? (https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting)
* Have you had a look at the rest of the wiki? (https://github.com/netbox-community/netbox-docker/wiki)
* Have you updated to the latest version and tried again? (i.e. `git pull` and `docker-compose pull`)
* Have you reset the project and tried again? (i.e. `docker-compose down -v`)
* Are you confident that your problem is related to the Docker image or Docker Compose file this project provides?
(Otherwise ask on the Netbox mailing list, please: https://groups.google.com/d/forum/netbox-discuss)
* Have you looked through the issues already resolved?
Please try this means to get help before opening an issue here:
* On the networktocode Slack in the #netbox-docker channel: http://slack.networktocode.com/
* On the networktocode Slack in the #netbox channel: http://slack.networktocode.com/
* On the Netbox mailing list: https://groups.google.com/d/forum/netbox-discuss
Please don't open an issue when you have a PR ready. Just submit the PR, that's good enough.
-->
## Current Behavior
<!-- describe what you did and how it misbehaved -->
...
## Expected Behavior
<!-- describe what you expected instead -->
...
## Debug Information
<!-- please fill in the following information that might helps us debug your problem more quickly -->
The output of `docker-compose version`: `XXXXX`
The output of `docker version`: `XXXXX`
The output of `git rev-parse HEAD`: `XXXXX`
The command you used to start the project: `XXXXX`
<!-- adjust the `latest` tag to the version you're using -->
The output of `docker inspect netboxcommunity/netbox:latest --format "{{json .ContainerConfig.Labels}}"`:
```json
{
"JSON JSON JSON":
"--> Please paste formatted json. (Use e.g. `jq` or https://jsonformatter.curiousconcept.com/)"
}
```
The output of `docker-compose logs netbox`:
<!--
If your log is very long, create a Gist instead (and post the link to it): https://gist.github.com
-->
```text
LOG LOG LOG
```
The output of `docker-compose logs nginx`:
<!--
Only if you have gotten a 5xx http error, else delete this section.
If your log is very long, create a Gist instead (and post the link to it): https://gist.github.com
-->
```text
LOG LOG LOG
```

View File

@ -1,148 +0,0 @@
name: Bug report
description: Create a report about a malfunction of the Docker setup
body:
- type: markdown
attributes:
value: |
Please only raise an issue if you're certain that you've found a bug.
Else, see these other means to get help:
- See our troubleshooting section:
https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting
- Have a look at the rest of the wiki:
https://github.com/netbox-community/netbox-docker/wiki
- Check the release notes:
https://github.com/netbox-community/netbox-docker/releases
- Look through the issues already resolved:
https://github.com/netbox-community/netbox-docker/issues?q=is%3Aclosed
If you did not find what you're looking for,
try the help of our community:
- Post to Github Discussions:
https://github.com/netbox-community/netbox-docker/discussions
- Join the `#netbox-docker` channel on our Slack:
https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ
- Ask on the NetBox mailing list:
https://groups.google.com/d/forum/netbox-discuss
Please don't open an issue to open a PR.
Just submit the PR, that's good enough.
- type: textarea
id: current-behavior
attributes:
label: Current Behavior
description: Please describe what you did and how you think it misbehaved
placeholder: I tried to … by doing …, but it …
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected Behavior
description: Please describe what you expected instead
placeholder: I expected that … when I do …
validations:
required: true
- type: input
id: docker-compose-version
attributes:
label: Docker Compose Version
description: Please paste the output of `docker-compose version`
placeholder: Docker Compose version vX.Y.Z
validations:
required: true
- type: textarea
id: docker-version
attributes:
label: Docker Version
description: Please paste the output of `docker version`
render: text
placeholder: |
Client:
Cloud integration: 1.0.17
Version: 20.10.8
API version: 1.41
Go version: go1.16.6
Git commit: 3967b7d
Built: Fri Jul 30 19:55:20 2021
OS/Arch: darwin/amd64
Context: default
Experimental: true
Server: Docker Engine - Community
Engine:
Version: 20.10.8
API version: 1.41 (minimum version 1.12)
Go version: go1.16.6
Git commit: 75249d8
Built: Fri Jul 30 19:52:10 2021
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.4.9
GitCommit: e25210fe30a0a703442421b0f60afac609f950a3
runc:
Version: 1.0.1
GitCommit: v1.0.1-0-g4144b63
docker-init:
Version: 0.19.0
GitCommit: de40ad0
validations:
required: true
- type: input
id: git-rev
attributes:
label: The git Revision
description: Please paste the output of `git rev-parse HEAD`
validations:
required: true
- type: textarea
id: git-status
attributes:
label: The git Status
description: Please paste the output of `git status`
render: text
placeholder: |
On branch main
nothing to commit, working tree clean
validations:
required: true
- type: input
id: run-command
attributes:
label: Startup Command
description: Please specify the command you used to start the project
placeholder: docker compose up
validations:
required: true
- type: textarea
id: netbox-logs
attributes:
label: NetBox Logs
description: Please paste the output of `docker-compose logs netbox` (or `docker compose logs netbox`)
render: text
placeholder: |
netbox_1 | ⚙️ Applying database migrations
netbox_1 | 🧬 loaded config '/etc/netbox/config/configuration.py'
netbox_1 | 🧬 loaded config '/etc/netbox/config/a.py'
netbox_1 | 🧬 loaded config '/etc/netbox/config/extra.py'
netbox_1 | 🧬 loaded config '/etc/netbox/config/logging.py'
netbox_1 | 🧬 loaded config '/etc/netbox/config/plugins.py'
...
validations:
required: true
- type: textarea
id: docker-compose-override-yml
attributes:
label: Content of docker-compose.override.yml
description: Please paste the output of `cat docker-compose.override.yml`
render: yaml
placeholder: |
version: '3.4'
services:
netbox:
ports:
- '8080:8080'
validations:
required: true

View File

@ -1,15 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Question
url: https://github.com/netbox-community/netbox-docker/discussions
about: The Github Discussions are the right place to ask questions about how to use or do certain things with NetBox Docker.
- name: Chat
url: https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ
about: 'Usually the quickest way to seek help with small issues is to join our #netbox-docker Slack channel.'
- name: Community Wiki
url: https://github.com/netbox-community/netbox-docker/wiki
about: |
Our wiki contains information for common problems and tips for operating NetBox Docker in production.
It's maintained by our excellent community.

View File

@ -0,0 +1,54 @@
---
name: Feature or Change Request
about: Request a new feature or a change of the current behavior
title: ''
labels: ''
assignees: ''
---
<!--
Before raising an issue here, answer the following questions for yourself, please:
* Did you read through the troubleshooting section? (https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting)
* Have you had a look at the rest of the wiki? (https://github.com/netbox-community/netbox-docker/wiki)
* Have you read the release notes recently (https://github.com/netbox-community/netbox-docker/releases)
* Are you confident that your feature/change request is related to the Docker image or Docker Compose file this project provides?
(Otherwise ask on the Netbox mailing list, please: https://groups.google.com/d/forum/netbox-discuss)
* Have you looked through the issues already resolved?
Please try this means to get help before opening an issue here:
* On the networktocode Slack in the #netbox-docker channel: http://slack.networktocode.com/
* On the networktocode Slack in the #netbox channel: http://slack.networktocode.com/
* On the Netbox mailing list: https://groups.google.com/d/forum/netbox-discuss
Please don't open an issue when you have a PR ready. Just submit the PR, that's good enough.
-->
## Desired Behavior
<!-- please describe the behavior you desire -->
...
## Contrast to Current Behavior
<!-- please describe how the desired behavior is different from the current behavior -->
...
## Changes Required
<!-- if you can, please elaborate what changes would exactly be required -->
...
## Discussion: Benefits and Drawbacks
<!--
Please make your case here:
- Why do you think this project and the community will benefit from your suggestion?
- What are the drawbacks of this change? Is it backwards-compatible?
- Anything else that you think is relevant to the discussion of this feature/change request.
-->
...

View File

@ -1,68 +0,0 @@
name: Feature or Change Request
description: Request a new feature or a change of the current behavior
body:
- type: markdown
attributes:
value: |
This issue type is to propose new features for the Docker setup.
To just spin an idea, see the Github Discussions section, please.
Before asking for help, see these links first:
- See our troubleshooting section:
https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting
- Have a look at the rest of the wiki:
https://github.com/netbox-community/netbox-docker/wiki
- Check the release notes:
https://github.com/netbox-community/netbox-docker/releases
- Look through the issues already resolved:
https://github.com/netbox-community/netbox-docker/issues?q=is%3Aclosed
If you did not find what you're looking for,
try the help of our community:
- Post to Github Discussions:
https://github.com/netbox-community/netbox-docker/discussions
- Join the `#netbox-docker` channel on our Slack:
https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ
- Ask on the NetBox mailing list:
https://groups.google.com/d/forum/netbox-discuss
Please don't open an issue to open a PR.
Just submit the PR, that's good enough.
- type: textarea
id: desired-behavior
attributes:
label: Desired Behavior
description: Please describe the desired behavior
placeholder: To me, it would be useful, if … because …
validations:
required: true
- type: textarea
id: contrast-to-current
attributes:
label: Contrast to Current Behavior
description: Please describe how the desired behavior is different from the current behavior
placeholder: The current behavior is …, but this lacks …
validations:
required: true
- type: textarea
id: required-changes
attributes:
label: Required Changes
description: If you can, please elaborate what changes will be required to implement the desired behavior
placeholder: I suggest to change the file …
validations:
required: false
- type: textarea
id: discussion
attributes:
label: 'Discussion: Benefits and Drawbacks'
description: |
Please make your case here:
- Why do you think this project and the community will benefit from your suggestion?
- What are the drawbacks of this change? Is it backwards-compatible?
- Anything else that you think is relevant to the discussion of this feature/change request.
placeholder: I suggest to change the file …
validations:
required: false

View File

@ -1,10 +0,0 @@
# 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

@ -1,5 +1,5 @@
<!-- <!--
######################################################################### ###############################################################################
Thank you for sharing your work and for opening a PR. Thank you for sharing your work and for opening a PR.
@ -9,11 +9,11 @@ First make sure that you point your PR to the `develop` branch!
Now please read the comments carefully and try to provide information Now please read the comments carefully and try to provide information
on all relevant titles. on all relevant titles.
######################################################################### ###############################################################################
--> -->
<!-- <!--
Please don't open an extra issue when submitting a PR. Please don't open an extra issue when submiting a PR.
But if there is already a related issue, please put it's number here. But if there is already a related issue, please put it's number here.
@ -81,5 +81,5 @@ Please put an x into the brackets (like `[x]`) if you've completed that task.
--> -->
* [ ] I have read the comments and followed the PR template. * [ ] I have read the comments and followed the PR template.
* [ ] I have explained my PR according to the information in the comments. * [ ] I have provided and explained my PR according to the information in the comments.
* [ ] My PR targets the `develop` branch. * [ ] My PR targets the `develop` branch.

View File

@ -1,80 +1,35 @@
---
name: push
on: on:
push: push:
branches-ignore: branches-ignore:
- release - release
pull_request:
branches-ignore:
- release
jobs: jobs:
lint:
runs-on: ubuntu-latest
name: Checks syntax of our code
steps:
- uses: actions/checkout@v3
with:
# Full git history is needed to get a proper
# list of changed files within `super-linter`
fetch-depth: 0
- uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Lint Code Base
uses: github/super-linter@v4
env:
DEFAULT_BRANCH: develop
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SUPPRESS_POSSUM: true
LINTER_RULES_PATH: /
VALIDATE_ALL_CODEBASE: false
VALIDATE_DOCKERFILE: false
VALIDATE_GITLEAKS: false
FILTER_REGEX_EXCLUDE: (.*/)?(LICENSE|configuration/.*)
EDITORCONFIG_FILE_NAME: .ecrc
DOCKERFILE_HADOLINT_FILE_NAME: .hadolint.yaml
MARKDOWN_CONFIG_FILE: .markdown-lint.yml
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' }}
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-branches.sh
- ./build.sh develop docker_from:
platform: - '' # use the default of the DOCKERFILE
- linux/amd64 - python:3.7-alpine
- linux/arm64 - python:3.8-alpine
- python:3.9-rc-alpine
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@v1
- 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,83 +1,49 @@
---
name: release
on: on:
release: push:
types: branches:
- published - release
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-branches.sh
- ./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:
GH_ACTION: enable
IMAGE_NAMES: docker.io/netboxcommunity/netbox quay.io/netboxcommunity/netbox ghcr.io/netbox-community/netbox
steps: steps:
- id: source-checkout - id: git-checkout
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v1
- id: set-netbox-docker-version - id: docker-build
name: Get Version of NetBox Docker name: Build the image with '${{ matrix.build_cmd }}'
run: echo "::set-output name=version::$(cat VERSION)" run: ${{ matrix.build_cmd }}
shell: bash env:
- id: qemu-setup GH_ACTION: enable
name: Set up QEMU - id: docker-test
uses: docker/setup-qemu-action@v2 name: Test the image
- id: buildx-setup run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh
name: Set up Docker Buildx if: steps.docker-build.outputs.skipped != 'true'
uses: docker/setup-buildx-action@v2 - id: registry-login
- id: docker-build name: Login to the Docker Registry
name: Build the image with '${{ matrix.build_cmd }}' run: |
run: ${{ matrix.build_cmd }} echo "::add-mask::$DOCKERHUB_USERNAME"
- id: test-image echo "::add-mask::$DOCKERHUB_PASSWORD"
name: Test the image docker login -u "$DOCKERHUB_USERNAME" --password "${DOCKERHUB_PASSWORD}" "${DOCKER_REGISTRY}"
run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh env:
if: steps.docker-build.outputs.skipped != 'true' DOCKERHUB_USERNAME: ${{ secrets.dockerhub_username }}
# docker.io DOCKERHUB_PASSWORD: ${{ secrets.dockerhub_password }}
- id: docker-io-login if: steps.docker-build.outputs.skipped != 'true'
name: Login to docker.io - id: registry-push
uses: docker/login-action@v2 name: Push the image
with: run: ${{ matrix.build_cmd }} --push-only
registry: docker.io if: steps.docker-build.outputs.skipped != 'true'
username: ${{ secrets.dockerhub_username }} - id: registry-logout
password: ${{ secrets.dockerhub_password }} name: Logout of the Docker Registry
if: steps.docker-build.outputs.skipped != 'true' run: docker logout "${DOCKER_REGISTRY}"
# quay.io if: steps.docker-build.outputs.skipped != 'true'
- id: quay-io-login
name: Login to Quay.io
uses: docker/login-action@v2
with:
registry: quay.io
username: ${{ secrets.quayio_username }}
password: ${{ secrets.quayio_password }}
if: steps.docker-build.outputs.skipped != 'true'
# ghcr.io
- id: ghcr-io-login
name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
if: steps.docker-build.outputs.skipped != 'true'
- id: build-and-push
name: Push the image
run: ${{ matrix.build_cmd }} --push
if: steps.docker-build.outputs.skipped != 'true'
env:
BUILDX_PLATFORM: ${{ matrix.platform }}
BUILDX_BUILDER_NAME: ${{ steps.buildx-setup.outputs.name }}

11
.gitignore vendored
View File

@ -2,14 +2,3 @@
.netbox .netbox
.initializers .initializers
docker-compose.override.yml docker-compose.override.yml
*.pem
configuration/*
!configuration/configuration.py
!configuration/extra.py
configuration/ldap/*
!configuration/ldap/extra.py
!configuration/ldap/ldap_config.py
!configuration/logging.py
!configuration/plugins.py
prometheus.yml
super-linter.log

View File

@ -1,4 +0,0 @@
ignored:
- DL3006
- DL3008
- DL3003

View File

@ -1,2 +0,0 @@
MD013: false
MD041: false

32
.travis.yml Normal file
View File

@ -0,0 +1,32 @@
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,5 +0,0 @@
---
rules:
line-length:
max: 120

63
DOCKER_HUB.md Normal file
View File

@ -0,0 +1,63 @@
# cloud.docker.com Configuration
The automatic build is configured in cloud.docker.com.
The following build configuration is expected:
```yaml
Source Repository: github.com/netbox-community/netbox-docker
Build Location: Build on Docker Hub's infrastructure
Autotest: Internal and External Pull Requests
Repository Links: Enable for Base Image
Build Rules:
- Source Type: Branch
Source: release
Docker Tag: branches
Dockerfile location: Dockerfile
Build Context: /
Autobuild: on
Build Caching: on
- Source Type: Branch
Source: release
Docker Tag: prerelease
Dockerfile location: Dockerfile
Build Context: /
Autobuild: on
Build Caching: on
- Source Type: Branch
Source: release
Docker Tag: release
Dockerfile location: Dockerfile
Build Context: /
Autobuild: on
Build Caching: on
Build Environment Variables:
# Create an app on Github and use it's OATH credentials here
- Key: GITHUB_OAUTH_CLIENT_ID
Value: <secret>
- Key: GITHUB_OAUTH_CLIENT_SECRET
Value: <secret>
Build Triggers:
- Name: Cron Trigger
Trigger URL: <generated>
# Use this trigger in combination with e.g. https://cron-job.org in order to regularly schedule builds
```
## Background Knowledge
The build system of cloud.docker.com is not made for this kind of project.
But we found a way to make it work, and this is how:
1. The docker hub build system [allows to overwrite the scripts that get executed
for `build`, `test` and `push`](overwrite). See `/hooks/*`.
2. Shared functionality of the scripts `build`, `test` and `push` is extracted to `/hooks/common`.
3. The `build` script runs `run_build()` from `/hooks/common`.
This triggers either `/build-branches.sh`, `/build-latest.sh` or directly `/build.sh`.
4. The `test` script just invokes `docker-compose` commands.
5. The `push` script runs `run_build()` from `hooks/common` with a `--push-only` flag.
This causes the `build.sh` script to not re-build the Docker image, but just the just built image.
The _Docker Tag_ configuration setting (`$DOCKER_TAG`) is only used to select the type (_release_, _prerelease_, _branches_) of the build in `hooks/common`.
Because it has a different meaning in all the other build scripts, it is `unset` after it has served it's purpose.
[overwrite]: https://docs.docker.com/docker-hub/builds/advanced/#override-build-test-or-push-commands

View File

@ -1,40 +1,38 @@
ARG FROM ARG FROM=python:3.7-alpine
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 \
&& apt-get install \
--yes -qq --no-install-recommends \
build-essential \
ca-certificates \ ca-certificates \
libldap-dev \ cyrus-sasl-dev \
libpq-dev \ graphviz \
libsasl2-dev \ jpeg-dev \
libssl-dev \ libevent-dev \
libxml2-dev \ libffi-dev \
libxml2-dev \
libxmlsec1 \
libxmlsec1-dev \
libxmlsec1-openssl \
libxslt-dev \ libxslt-dev \
pkg-config \ openldap-dev \
python3-dev \ postgresql-dev
python3-pip \
python3-venv \ WORKDIR /install
&& python3 -m venv /opt/netbox/venv \
&& /opt/netbox/venv/bin/python3 -m pip install --upgrade \ RUN pip install --prefix="/install" --no-warn-script-location \
pip \ # gunicorn is used for launching netbox
setuptools \ gunicorn \
wheel greenlet \
eventlet \
# napalm is used for gathering information from network devices
napalm \
# ruamel is used in startup_scripts
'ruamel.yaml>=0.15,<0.16' \
# django_auth_ldap is required for ldap
django_auth_ldap \
# django-storages was introduced in 2.7 and is optional
django-storages
ARG NETBOX_PATH ARG NETBOX_PATH
COPY ${NETBOX_PATH}/requirements.txt requirements-container.txt / COPY ${NETBOX_PATH}/requirements.txt /
RUN sed -i -e '/psycopg2-binary/d' requirements.txt && \ RUN pip install --prefix="/install" --no-warn-script-location -r /requirements.txt
/opt/netbox/venv/bin/pip install \
-r /requirements.txt \
-r /requirements-container.txt
### ###
# Main stage # Main stage
@ -43,70 +41,66 @@ 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 \ graphviz \
libldap-common \ libevent \
libpq5 \ libffi \
libxmlsec1-openssl \ libjpeg-turbo \
openssl \ libressl \
python3 \ libxslt \
python3-distutils \ postgresql-libs \
tini \ ttf-ubuntu-font-family
&& curl -sL https://nginx.org/keys/nginx_signing.key \
> /etc/apt/trusted.gpg.d/nginx.asc && \
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/*
COPY --from=builder /opt/netbox/venv /opt/netbox/venv WORKDIR /opt
COPY --from=builder /install /usr/local
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 configuration/gunicorn_config.py /etc/netbox/config/
COPY docker/nginx.conf /etc/netbox-nginx/nginx.conf
COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh
COPY docker/housekeeping.sh /opt/netbox/housekeeping.sh COPY startup_scripts/ /opt/netbox/startup_scripts/
COPY docker/launch-netbox.sh /opt/netbox/launch-netbox.sh COPY initializers/ /opt/netbox/initializers/
COPY configuration/ /etc/netbox/config/ COPY configuration/configuration.py /etc/netbox/config/configuration.py
COPY docker/nginx-unit.json /etc/unit/
WORKDIR /opt/netbox/netbox WORKDIR /opt/netbox/netbox
# Must set permissions for '/opt/netbox/netbox/static' directory
# to g+w so that `./manage.py collectstatic` can be executed during
# container startup.
# Must set permissions for '/opt/netbox/netbox/media' directory # Must set permissions for '/opt/netbox/netbox/media' directory
# to g+w so that pictures can be uploaded to netbox. # to g+w so that pictures can be uploaded to netbox.
RUN mkdir -p static /opt/unit/state/ /opt/unit/tmp/ \ RUN mkdir static && chmod g+w static media
&& chown -R unit:root media /opt/unit/ \
&& chmod -R g+w media /opt/unit/ \
&& cd /opt/netbox/ && SECRET_KEY="dummy" /opt/netbox/venv/bin/python -m mkdocs build \
--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
ENV LANG=C.UTF-8 PATH=/opt/netbox/venv/bin:$PATH ENTRYPOINT [ "/opt/netbox/docker-entrypoint.sh" ]
ENTRYPOINT [ "/usr/bin/tini", "--" ]
CMD [ "/opt/netbox/docker-entrypoint.sh", "/opt/netbox/launch-netbox.sh" ] CMD ["gunicorn", "-c /etc/netbox/config/gunicorn_config.py", "netbox.wsgi"]
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" \
org.opencontainers.image.description="A container based distribution of NetBox, the free and open IPAM and DCIM solution." \ org.opencontainers.image.description="A container based distribution of Netbox, the free and open IPAM and DCIM solution." \
org.opencontainers.image.licenses="Apache-2.0" \ org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.authors="The netbox-docker contributors." \ org.opencontainers.image.authors="The netbox-docker contributors." \
org.opencontainers.image.vendor="The netbox-docker contributors." \ org.opencontainers.image.vendor="The netbox-docker contributors." \
@ -114,4 +108,18 @@ 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
COPY configuration/ldap_config.py /etc/netbox/config/ldap_config.py

210
README.md
View File

@ -3,156 +3,156 @@
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/netbox-community/netbox-docker)][github-release] [![GitHub release (latest by date)](https://img.shields.io/github/v/release/netbox-community/netbox-docker)][github-release]
[![GitHub stars](https://img.shields.io/github/stars/netbox-community/netbox-docker)][github-stargazers] [![GitHub stars](https://img.shields.io/github/stars/netbox-community/netbox-docker)][github-stargazers]
![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed-raw/netbox-community/netbox-docker) ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed-raw/netbox-community/netbox-docker)
![Github release workflow](https://img.shields.io/github/workflow/status/netbox-community/netbox-docker/release) ![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/netboxcommunity/netbox)
![Docker Pulls](https://img.shields.io/docker/pulls/netboxcommunity/netbox) ![Docker Pulls](https://img.shields.io/docker/pulls/netboxcommunity/netbox)
[![MicroBadger Layers](https://img.shields.io/microbadger/layers/netboxcommunity/netbox)][netbox-docker-microbadger]
[![MicroBadger Size](https://img.shields.io/microbadger/image-size/netboxcommunity/netbox)][netbox-docker-microbadger]
[![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 Docker 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 using this code and are released to [Docker Hub][netbox-dockerhub] once a day.
Do you have any questions? Do you have any questions? Before opening an issue on Github, please join the [Network To Code][ntc-slack] Slack and ask for help in our [`#netbox-docker`][netbox-docker-slack] channel.
Before opening an issue on Github,
please join [our Slack][netbox-docker-slack] and ask for help in the [`#netbox-docker`][netbox-docker-slack-channel] channel.
[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-dockerhub]: https://hub.docker.com/r/netboxcommunity/netbox/ [netbox-docker-microbadger]: https://microbadger.com/images/netboxcommunity/netbox
[netbox-quayio]: https://quay.io/repository/netboxcommunity/netbox [netbox-dockerhub]: https://hub.docker.com/r/netboxcommunity/netbox/tags/
[netbox-ghcr]: https://github.com/netbox-community/netbox-docker/pkgs/container/netbox
[netbox-docker-github]: https://github.com/netbox-community/netbox-docker/ [netbox-docker-github]: https://github.com/netbox-community/netbox-docker/
[netbox-docker-slack]: https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ [ntc-slack]: http://slack.networktocode.com/
[netbox-docker-slack-channel]: https://netdev-community.slack.com/archives/C01P0GEVBU7 [netbox-docker-slack]: https://slack.com/app_redirect?channel=netbox-docker&team=T09LQ7E9E
[netbox-slack-channel]: https://netdev-community.slack.com/archives/C01P0FRSXRV [netbox-docker-license]: https://github.com/netbox-community/netbox-docker/blob/master/LICENSE
[netbox-docker-license]: https://github.com/netbox-community/netbox-docker/blob/release/LICENSE
## Docker Tags
* `vX.Y.Z`: Release builds, built from [releases of Netbox][netbox-releases].
* `latest`: Release builds, built from [`master` branch of Netbox][netbox-master].
* `snapshot`: Pre-release builds, built from the [`develop` branch of Netbox][netbox-develop].
* `develop-X.Y`: Pre-release builds, built from the corresponding [branch of Netbox][netbox-branches].
Then there is currently one extra tags for each of the above labels:
* `-ldap`: Contains additional dependencies and configurations for connecting Netbox to an LDAP directroy.
[Learn more about that in our wiki][netbox-docker-ldap].
[netbox-releases]: https://github.com/netbox-community/netbox/releases
[netbox-master]: https://github.com/netbox-community/netbox/tree/master
[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
## Quickstart ## Quickstart
To get _NetBox Docker_ up and running run the following commands. To get Netbox up and running in Docker:
There is a more complete [_Getting Started_ guide on our wiki][wiki-getting-started] which explains every step.
```bash ```bash
git clone -b release https://github.com/netbox-community/netbox-docker.git git clone -b release https://github.com/netbox-community/netbox-docker.git
cd netbox-docker cd netbox-docker
tee docker-compose.override.yml <<EOF
version: '3.4'
services:
netbox:
ports:
- 8000:8080
EOF
docker-compose pull docker-compose pull
docker-compose up docker-compose up -d
``` ```
The whole application will be available after a few minutes. The application will be available after a few minutes.
Open the URL `http://0.0.0.0:8000/` in a web-browser. Use `docker-compose port nginx 8080` to find out where to connect to.
You should see the NetBox homepage.
In the top-right corner you can login. ```bash
$ echo "http://$(docker-compose port nginx 8080)/"
http://0.0.0.0:32768/
# Open netbox in your default browser on macOS:
$ open "http://$(docker-compose port nginx 8080)/"
# Open netbox in your default browser on (most) linuxes:
$ xdg-open "http://$(docker-compose port nginx 8080)/" &>/dev/null &
```
Alternatively, use something like [Reception][docker-reception] to connect to _docker-compose_ projects.
The default credentials are: The default credentials are:
* Username: **admin** * Username: **admin**
* Password: **admin** * Password: **admin**
* API Token: **0123456789abcdef0123456789abcdef01234567** * API Token: **0123456789abcdef0123456789abcdef01234567**
There is a more complete [Getting Started guide on our Wiki][wiki-getting-started].
[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
New container images are built and published automatically every ~24h.
> We recommend to use either the `vX.Y.Z-a.b.c` tags or the `vX.Y-a.b.c` tags in production!
* `vX.Y.Z-a.b.c`, `vX.Y-a.b.c`:
These are release builds containing _NetBox version_ `vX.Y.Z`.
They contain the support files of _NetBox Docker version_ `a.b.c`.
You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility.
These images are automatically built from [the corresponding releases of NetBox][netbox-releases].
* `latest-a.b.c`:
These are release builds, containing the latest stable version of NetBox.
They contain the support files of _NetBox Docker version_ `a.b.c`.
You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility.
These images are automatically built from [the `master` branch of NetBox][netbox-master].
* `snapshot-a.b.c`:
These are prerelease builds.
They contain the support files of _NetBox Docker version_ `a.b.c`.
You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility.
These images are automatically built from the [`develop` branch of NetBox][netbox-develop].
For each of the above tag, there is an extra tag:
* `vX.Y.Z`, `vX.Y`:
This is the same version as `vX.Y.Z-a.b.c` (or `vX.Y-a.b.c`, respectively).
It always points to the latest version of _NetBox Docker_.
* `latest`
This is the same version as `latest-a.b.c`.
It always points to the latest version of _NetBox Docker_.
* `snapshot`
This is the same version as `snapshot-a.b.c`.
It always points to the latest version of _NetBox Docker_.
[netbox-releases]: https://github.com/netbox-community/netbox/releases
[netbox-master]: https://github.com/netbox-community/netbox/tree/master
[netbox-develop]: https://github.com/netbox-community/netbox/tree/develop
## Documentation
Please refer [to our wiki on GitHub][netbox-docker-wiki] for further information on how to use the NetBox Docker image properly.
The wiki covers advanced topics such as using files for secrets, configuring TLS, deployment to Kubernetes, monitoring and configuring NAPALM and LDAP.
Our wiki is a community effort.
Feel free to correct errors, update outdated information or provide additional guides and insights.
[netbox-docker-wiki]: https://github.com/netbox-community/netbox-docker/wiki/
## Getting Help
Feel free to ask questions in our [GitHub Community][netbox-community]
or [join our Slack][netbox-docker-slack] and ask [in our channel `#netbox-docker`][netbox-docker-slack-channel],
which is free to use and where there are almost always people online that can help you in the Slack channel.
If you need help with using NetBox or developing for it or against it's API
you may find [the `#netbox` channel][netbox-slack-channel] on the same Slack instance very helpful.
[netbox-community]: https://github.com/netbox-community/netbox-docker/discussions
## Dependencies ## Dependencies
This project relies only on _Docker_ and _docker-compose_ meeting these requirements: This project relies only on *Docker* and *docker-compose* meeting these requirements:
* The _Docker version_ must be at least `20.10.10`. * The *Docker version* must be at least `17.05`.
* The _containerd version_ must be at least `1.5.6`. * The *docker-compose version* must be at least `1.17.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`.
## Updating ## 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.
[netbox-docker-wiki]: https://github.com/netbox-community/netbox-docker/wiki/
## Netbox Version
The `docker-compose.yml` file is prepared to run a specific version of Netbox.
To use this feature, set the environment-variable `VERSION` before launching `docker-compose`, as shown below.
`VERSION` may be set to the name of
[any tag of the `netboxcommunity/netbox` Docker image on Docker Hub][netbox-dockerhub].
```bash
export VERSION=v2.7.1
docker-compose pull netbox
docker-compose up -d
```
You can also build a specific version of the Netbox Docker image yourself.
`VERSION` can be any valid [git ref][git-ref] in that case.
```bash
export VERSION=v2.7.1
./build.sh $VERSION
docker-compose up -d
```
[git-ref]: https://git-scm.com/book/en/v2/Git-Internals-Git-References
[netbox-github]: https://github.com/netbox-community/netbox/releases
## Breaking Changes
From time to time it might become necessary to re-engineer the structure of this setup.
Things like the `docker-compose.yml` file or your Kubernetes or OpenShift configurations have to be adjusted as a consequence.
Since November 2019 each image built from this repo contains a `org.opencontainers.image.version` label.
(The images contained labels since April 2018, although in November 2019 the labels' names changed.)
You can check the label of your local image by running `docker inspect netboxcommunity/netbox:v2.7.1 --format "{{json .ContainerConfig.Labels}}"`.
Please read [the release notes][releases] carefully when updating to a new image version. Please read [the release notes][releases] carefully when updating to a new image version.
Note that the version of the NetBox Docker container image must stay in sync with the code.
If you update for the first time, be sure [to follow our _How To Update NetBox Docker_ guide in the wiki][netbox-docker-wiki-updating].
[releases]: https://github.com/netbox-community/netbox-docker/releases [releases]: https://github.com/netbox-community/netbox-docker/releases
[netbox-docker-wiki-updating]: https://github.com/netbox-community/netbox-docker/wiki/Updating
## Rebuilding the Image ## Rebuilding & Publishing images
`./build.sh` can be used to rebuild the container image. See `./build.sh --help` for more information. `./build.sh` can be used to rebuild the Docker image. See `./build.sh --help` for more information.
For more details on custom builds [consult our wiki][netbox-docker-wiki-build]. ### Publishing Docker Images
[netbox-docker-wiki-build]: https://github.com/netbox-community/netbox-docker/wiki/Build New Docker images are built and published every 24h on the [Docker Build Infrastructure][docker-build-infra].
`DOCKER_HUB.md` contains more information about the build infrastructure.
[docker-build-infra]: https://hub.docker.com/r/netboxcommunity/netbox/builds/
## Tests ## Tests
We have a test script. To run the tests coming with Netbox, use the `docker-compose.yml` file as such:
It runs NetBox's own unit tests and ensures that all initializers work:
```bash ```bash
IMAGE=netboxcommunity/netbox:latest ./test.sh docker-compose run netbox ./manage.py test
``` ```
## Support ## About
This repository is currently maintained by the community. This repository is currently maintained and funded by [nxt][nxt].
Please consider sponsoring the maintainers of this project.
[nxt]: https://nxt.engineering/en/

View File

@ -1 +1 @@
2.4.0 0.21.0

51
build-all.sh Executable file
View File

@ -0,0 +1,51 @@
#!/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

47
build-branches.sh Executable file
View File

@ -0,0 +1,47 @@
#!/bin/bash
# Builds develop, develop-* and master branches of Netbox
echo "▶️ $0 $*"
###
# Checking for the presence of GITHUB_OAUTH_CLIENT_ID
# and GITHUB_OAUTH_CLIENT_SECRET
###
if [ -n "${GITHUB_OAUTH_CLIENT_ID}" ] && [ -n "${GITHUB_OAUTH_CLIENT_SECRET}" ]; then
echo "🗝 Performing authenticated Github API calls."
GITHUB_OAUTH_PARAMS="client_id=${GITHUB_OAUTH_CLIENT_ID}&client_secret=${GITHUB_OAUTH_CLIENT_SECRET}"
else
echo "🕶 Performing unauthenticated Github API calls. This might result in lower Github rate limits!"
GITHUB_OAUTH_PARAMS=""
fi
###
# Calling Github to get the all branches
###
ORIGINAL_GITHUB_REPO="${SRC_ORG-netbox-community}/${SRC_REPO-netbox}"
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'
CURL="curl -sS"
# Querying the Github API to fetch all branches
BRANCHES=$($CURL "${URL_RELEASES}" | jq -r "$JQ_BRANCHES")
###
# Building each branch
###
# keeping track whether an error occured
ERROR=0
# calling build.sh for each branch
for BRANCH in $BRANCHES; do
# shellcheck disable=SC2068
./build.sh "${BRANCH}" $@ || ERROR=1
done
# returning whether an error occured
exit $ERROR

View File

@ -1,82 +0,0 @@
#!/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

@ -1,21 +0,0 @@
#!/bin/bash
###
# A regular echo, that only prints if ${GH_ACTION} is defined.
###
gh_echo() {
if [ -n "${GH_ACTION}" ]; then
echo "${@}"
fi
}
###
# Prints the output to the file defined in ${GITHUB_ENV}.
# Only executes if ${GH_ACTION} is defined.
# Example Usage: gh_env "FOO_VAR=bar_value"
###
gh_env() {
if [ -n "${GH_ACTION}" ]; then
echo "${@}" >>"${GITHUB_ENV}"
fi
}

View File

@ -3,14 +3,6 @@
echo "▶️ $0 $*" echo "▶️ $0 $*"
###
# Check for the jq library needed for parsing JSON
###
if ! command -v jq; then
echo "⚠️ jq command missing from \$PATH!"
exit 1
fi
### ###
# Checking for the presence of GITHUB_OAUTH_CLIENT_ID # Checking for the presence of GITHUB_OAUTH_CLIENT_ID
# and GITHUB_OAUTH_CLIENT_SECRET # and GITHUB_OAUTH_CLIENT_SECRET
@ -27,7 +19,7 @@ fi
# Checking if PRERELEASE is either unset, 'true' or 'false' # Checking if PRERELEASE is either unset, 'true' or 'false'
### ###
if [ -n "${PRERELEASE}" ] && if [ -n "${PRERELEASE}" ] &&
{ [ "${PRERELEASE}" != "true" ] && [ "${PRERELEASE}" != "false" ]; }; then { [ "${PRERELEASE}" != "true" ] && [ "${PRERELEASE}" != "false" ]; }; then
if [ -z "${DEBUG}" ]; then if [ -z "${DEBUG}" ]; then
echo "⚠️ PRERELEASE must be either unset, 'true' or 'false', but was '${PRERELEASE}'!" echo "⚠️ PRERELEASE must be either unset, 'true' or 'false', but was '${PRERELEASE}'!"
@ -68,10 +60,9 @@ if [ "${PRERELEASE}" == "true" ]; then
# shellcheck disable=SC2003 # shellcheck disable=SC2003
MINOR_UNSTABLE=$(expr match "${VERSION}" 'v[0-9]\+\.\([0-9]\+\)') MINOR_UNSTABLE=$(expr match "${VERSION}" 'v[0-9]\+\.\([0-9]\+\)')
if { if { [ "${MAJOR_STABLE}" -eq "${MAJOR_UNSTABLE}" ] \
[ "${MAJOR_STABLE}" -eq "${MAJOR_UNSTABLE}" ] && && [ "${MINOR_STABLE}" -ge "${MINOR_UNSTABLE}" ];
[ "${MINOR_STABLE}" -ge "${MINOR_UNSTABLE}" ] } || [ "${MAJOR_STABLE}" -gt "${MAJOR_UNSTABLE}" ]; then
} || [ "${MAJOR_STABLE}" -gt "${MAJOR_UNSTABLE}" ]; then
echo "❎ Latest unstable version '${VERSION}' is not higher than the latest stable version '$STABLE_VERSION'." echo "❎ Latest unstable version '${VERSION}' is not higher than the latest stable version '$STABLE_VERSION'."
if [ -z "$DEBUG" ]; then if [ -z "$DEBUG" ]; then

573
build.sh
View File

@ -1,134 +1,92 @@
#!/bin/bash #!/bin/bash
# Clones the NetBox repository with git from Github and builds the Dockerfile # Clones the Netbox repository with git from Github and builds the Dockerfile
echo "▶️ $0 $*" 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: Whatever is defined as default in the Dockerfile."
${_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
@ -137,11 +95,6 @@ END_OF_HELP
fi fi
fi fi
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
### ###
@ -152,10 +105,8 @@ else
DRY="echo" DRY="echo"
fi fi
gh_echo "::group::⤵️ Fetching the NetBox source code"
### ###
# Variables for fetching the NetBox source # Variables for fetching the source
### ###
SRC_ORG="${SRC_ORG-netbox-community}" SRC_ORG="${SRC_ORG-netbox-community}"
SRC_REPO="${SRC_REPO-netbox}" SRC_REPO="${SRC_REPO-netbox}"
@ -164,23 +115,17 @@ URL="${URL-https://github.com/${SRC_ORG}/${SRC_REPO}.git}"
NETBOX_PATH="${NETBOX_PATH-.netbox}" NETBOX_PATH="${NETBOX_PATH-.netbox}"
### ###
# Fetching the NetBox source # Fetching the source
### ###
if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ]; then if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ] ; then
REMOTE_EXISTS=$(git ls-remote --heads --tags "${URL}" "${NETBOX_BRANCH}" | wc -l) echo "🌐 Checking out '${NETBOX_BRANCH}' of netbox from the url '${URL}' into '${NETBOX_PATH}'"
if [ "${REMOTE_EXISTS}" == "0" ]; then
echo "❌ Remote branch '${NETBOX_BRANCH}' not found in '${URL}'; Nothing to do"
gh_echo "::set-output name=skipped::true"
exit 0
fi
echo "🌐 Checking out '${NETBOX_BRANCH}' of NetBox from the url '${URL}' into '${NETBOX_PATH}'"
if [ ! -d "${NETBOX_PATH}" ]; then if [ ! -d "${NETBOX_PATH}" ]; then
$DRY git clone -q --depth 10 -b "${NETBOX_BRANCH}" "${URL}" "${NETBOX_PATH}" $DRY git clone -q --depth 10 -b "${NETBOX_BRANCH}" "${URL}" "${NETBOX_PATH}"
fi fi
( (
$DRY cd "${NETBOX_PATH}" $DRY cd "${NETBOX_PATH}"
# shellcheck disable=SC2030
if [ -n "${HTTP_PROXY}" ]; then if [ -n "${HTTP_PROXY}" ]; then
git config http.proxy "${HTTP_PROXY}" git config http.proxy "${HTTP_PROXY}"
fi fi
@ -190,12 +135,9 @@ if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ]; then
$DRY git checkout -qf FETCH_HEAD $DRY git checkout -qf FETCH_HEAD
$DRY git prune $DRY git prune
) )
echo "✅ Checked out NetBox" echo "✅ Checked out netbox"
fi fi
gh_echo "::endgroup::"
gh_echo "::group::🧮 Calculating Values"
### ###
# Determining the value for DOCKERFILE # Determining the value for DOCKERFILE
# and checking whether it exists # and checking whether it exists
@ -211,19 +153,12 @@ if [ ! -f "${DOCKERFILE}" ]; then
fi fi
fi fi
###
# Determining the value for DOCKER_FROM
###
if [ -z "$DOCKER_FROM" ]; then
DOCKER_FROM="ubuntu:22.04"
fi
### ###
# Variables for labelling the docker image # Variables for labelling the docker image
### ###
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,19 +166,10 @@ 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}; git rev-parse HEAD)
cd "${NETBOX_PATH}" NETBOX_GIT_BRANCH=$(cd ${NETBOX_PATH}; git rev-parse --abbrev-ref HEAD)
git rev-parse HEAD NETBOX_GIT_URL=$(cd ${NETBOX_PATH}; git remote get-url origin)
)
NETBOX_GIT_BRANCH=$(
cd "${NETBOX_PATH}"
git rev-parse --abbrev-ref HEAD
)
NETBOX_GIT_URL=$(
cd "${NETBOX_PATH}"
git remote get-url origin
)
fi fi
### ###
@ -253,206 +179,133 @@ DOCKER_REGISTRY="${DOCKER_REGISTRY-docker.io}"
DOCKER_ORG="${DOCKER_ORG-netboxcommunity}" DOCKER_ORG="${DOCKER_ORG-netboxcommunity}"
DOCKER_REPO="${DOCKER_REPO-netbox}" DOCKER_REPO="${DOCKER_REPO-netbox}"
case "${NETBOX_BRANCH}" in case "${NETBOX_BRANCH}" in
master) master)
TAG="${TAG-latest}" TAG="${TAG-latest}";;
;; develop)
develop) TAG="${TAG-snapshot}";;
TAG="${TAG-snapshot}" *)
;; TAG="${TAG-$NETBOX_BRANCH}";;
*)
TAG="${TAG-$NETBOX_BRANCH}"
;;
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[@]}"
### ###
# 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]} 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}"
fi
if [ -n "${GH_ACTION}" ]; then
echo "::set-env name=FINAL_DOCKER_TAG::${TARGET_DOCKER_TAG}"
echo "::set-output name=skipped::false"
fi
IMAGE_NAME_TAGS=() ###
for IMAGE_NAME in "${IMAGE_NAMES[@]}"; do # composing the additional DOCKER_SHORT_TAG,
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_TAG}") # i.e. "v2.6.1" becomes "v2.6",
IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_TAG_PROJECT}") # which is only relevant for version tags
###
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}}"
if [ "${DOCKER_TARGET}" != "main" ]; then
TARGET_DOCKER_SHORT_TAG="${TARGET_DOCKER_SHORT_TAG}-${DOCKER_TARGET}"
fi
fi
###
# Proceeding to buils stage, except if `--push-only` is passed
###
if [ "${2}" != "--push-only" ] ; then
###
# Composing all arguments for `docker build`
###
DOCKER_BUILD_ARGS=(
--pull
--target "${DOCKER_TARGET}"
-f "${DOCKERFILE}"
-t "${TARGET_DOCKER_TAG}"
)
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
DOCKER_BUILD_ARGS+=( -t "${TARGET_DOCKER_SHORT_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 "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
# --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
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
###
echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG}'."
$DRY docker build "${DOCKER_BUILD_ARGS[@]}" .
echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG}'"
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}'."
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}'."
fi
fi
done 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
IFS='/' read -ra ORG_REPO <<<"${IMAGE_NAMES[0]}"
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
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"
fi
gh_echo "::endgroup::"
###
# Build the image
###
gh_echo "::group::🏗 Building the image"
###
# Composing all arguments for `docker build`
###
DOCKER_BUILD_ARGS=(
--pull
--target main
-f "${DOCKERFILE}"
)
for IMAGE_NAME in "${IMAGE_NAME_TAGS[@]}"; do
DOCKER_BUILD_ARGS+=(-t "${IMAGE_NAME}")
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

@ -1,51 +1,21 @@
#### import os
## We recommend to not edit this file.
## Create separate files to overwrite the settings.
## See `extra.py` as an example.
####
import re import re
from os import environ import socket
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 http://netbox.readthedocs.io/en/latest/configuration/mandatory-settings/
# Based on https://github.com/netbox-community/netbox/blob/develop/netbox/netbox/configuration_example.py # Based on https://github.com/netbox-community/netbox/blob/develop/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):
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:
return default return ''
else: else:
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. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 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__)))
######################### #########################
# # # #
@ -57,53 +27,47 @@ _BASE_DIR = dirname(dirname(abspath(__file__)))
# access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name. # 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'] # Example: ALLOWED_HOSTS = ['netbox.example.com', 'netbox.internal.local']
ALLOWED_HOSTS = environ.get('ALLOWED_HOSTS', '*').split(' ') ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '*').split(' ')
# PostgreSQL database configuration. See the Django documentation for a complete list of available parameters: # PostgreSQL database configuration.
# https://docs.djangoproject.com/en/stable/ref/settings/#databases
DATABASE = { DATABASE = {
'NAME': environ.get('DB_NAME', 'netbox'), # Database name 'NAME': os.environ.get('DB_NAME', 'netbox'), # Database name
'USER': environ.get('DB_USER', ''), # PostgreSQL username 'USER': os.environ.get('DB_USER', ''), # PostgreSQL username
'PASSWORD': _read_secret('db_password', environ.get('DB_PASSWORD', '')), 'PASSWORD': os.environ.get('DB_PASSWORD', read_secret('db_password')),
# PostgreSQL password # PostgreSQL password
'HOST': environ.get('DB_HOST', 'localhost'), # Database server 'HOST': os.environ.get('DB_HOST', 'localhost'), # Database server
'PORT': environ.get('DB_PORT', ''), # Database port (leave blank for default) 'PORT': os.environ.get('DB_PORT', ''), # Database port (leave blank for default)
'OPTIONS': {'sslmode': environ.get('DB_SSLMODE', 'prefer')}, 'OPTIONS': {'sslmode': os.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(os.environ.get('DB_CONN_MAX_AGE', '300')),
# Max database connection age # Database connection persistence
'DISABLE_SERVER_SIDE_CURSORS': _environ_get_and_map('DB_DISABLE_SERVER_SIDE_CURSORS', 'False', _AS_BOOL),
# Disable the use of server-side cursors transaction pooling
}
# 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': _environ_get_and_map('REDIS_PORT', 6379, _AS_INT),
'PASSWORD': _read_secret('redis_password', environ.get('REDIS_PASSWORD', '')),
'DATABASE': _environ_get_and_map('REDIS_DATABASE', 0, _AS_INT),
'SSL': _environ_get_and_map('REDIS_SSL', 'False', _AS_BOOL),
'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False', _AS_BOOL),
},
'caching': {
'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),
'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),
'SSL': _environ_get_and_map('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False'), _AS_BOOL),
'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY', environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False'), _AS_BOOL),
},
} }
# This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file. # 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 # 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 # symbols. NetBox will not run without this defined. For more information, see
# https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-SECRET_KEY
SECRET_KEY = _read_secret('secret_key', environ.get('SECRET_KEY', '')) SECRET_KEY = os.environ.get('SECRET_KEY', read_secret('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',
},
}
######################### #########################
# # # #
@ -111,226 +75,117 @@ 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:
ALLOWED_URL_SCHEMES = _environ_get_and_map('ALLOWED_URL_SCHEMES', None, _AS_LIST)
# 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 = os.environ.get('BANNER_TOP', '')
BANNER_TOP = environ.get('BANNER_TOP', None) BANNER_BOTTOM = os.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 = os.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 = os.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))
# 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(os.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 = os.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, os.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, os.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 = os.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': os.environ.get('EMAIL_SERVER', 'localhost'),
'PORT': _environ_get_and_map('EMAIL_PORT', 25, _AS_INT), 'PORT': int(os.environ.get('EMAIL_PORT', 25)),
'USERNAME': environ.get('EMAIL_USERNAME', ''), 'USERNAME': os.environ.get('EMAIL_USERNAME', ''),
'PASSWORD': _read_secret('email_password', environ.get('EMAIL_PASSWORD', '')), 'PASSWORD': os.environ.get('EMAIL_PASSWORD', read_secret('email_password')),
'USE_SSL': _environ_get_and_map('EMAIL_USE_SSL', 'False', _AS_BOOL), 'TIMEOUT': int(os.environ.get('EMAIL_TIMEOUT', 10)), # seconds
'USE_TLS': _environ_get_and_map('EMAIL_USE_TLS', 'False', _AS_BOOL), 'FROM_EMAIL': os.environ.get('EMAIL_FROM', ''),
'SSL_CERTFILE': environ.get('EMAIL_SSL_CERTFILE', ''),
'SSL_KEYFILE': environ.get('EMAIL_SSL_KEYFILE', ''),
'TIMEOUT': _environ_get_and_map('EMAIL_TIMEOUT', 10, _AS_INT), # 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 # Enforcement of unique IP space can be toggled on a per-VRF basis.
# (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True. # To enforce unique IP space within the global table (all prefixes and IP addresses not assigned to a VRF),
if 'ENFORCE_GLOBAL_UNIQUE' in environ: # set ENFORCE_GLOBAL_UNIQUE to True.
ENFORCE_GLOBAL_UNIQUE = _environ_get_and_map('ENFORCE_GLOBAL_UNIQUE', None, _AS_BOOL) ENFORCE_GLOBAL_UNIQUE = os.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 # 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, os.environ.get('EXEMPT_VIEW_PERMISSIONS', '').split(' ')))
# HTTP proxies NetBox should use when sending outbound HTTP requests (e.g. for webhooks). # Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs:
# HTTP_PROXIES = { # https://docs.djangoproject.com/en/1.11/topics/logging/
# 'http': 'http://10.10.1.10:3128', LOGGING = {}
# '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.
if 'GRAPHQL_ENABLED' in environ:
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:
# # https://docs.djangoproject.com/en/stable/topics/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 = os.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_and_map('LOGIN_TIMEOUT', 1209600, _AS_INT)
# 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 = os.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(os.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 = os.environ.get('MEDIA_ROOT', os.path.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 = os.environ.get('METRICS_ENABLED', 'False').lower() == 'true'
# Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM. # Credentials that NetBox will use to access live devices.
if 'NAPALM_USERNAME' in environ: NAPALM_USERNAME = os.environ.get('NAPALM_USERNAME', '')
NAPALM_USERNAME = environ.get('NAPALM_USERNAME', None) NAPALM_PASSWORD = os.environ.get('NAPALM_PASSWORD', read_secret('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(os.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(os.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.
# 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 # 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 = os.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.
if 'RACK_ELEVATION_DEFAULT_UNIT_HEIGHT' in environ:
RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', None, _AS_INT)
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_AUTH_ENABLED = _environ_get_and_map('REMOTE_AUTH_ENABLED', 'False', _AS_BOOL)
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_and_map('REMOTE_AUTH_AUTO_CREATE_USER', 'True', _AS_BOOL)
REMOTE_AUTH_DEFAULT_GROUPS = _environ_get_and_map('REMOTE_AUTH_DEFAULT_GROUPS', '', _AS_LIST)
# 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
# version check or use the URL below to check for release in the official NetBox repository.
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 = os.environ.get('REPORTS_ROOT', '/etc/netbox/reports')
# Maximum execution time for background tasks, in seconds.
RQ_DEFAULT_TIMEOUT = _environ_get_and_map('RQ_DEFAULT_TIMEOUT', 300, _AS_INT)
# 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 = os.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
# 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('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 = os.environ.get('TIME_ZONE', 'UTC')
# Date/time formatting. See the following link for supported formats: # Date/time formatting. See the following link for supported formats:
# https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date # https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
DATE_FORMAT = environ.get('DATE_FORMAT', 'N j, Y') DATE_FORMAT = os.environ.get('DATE_FORMAT', 'N j, Y')
SHORT_DATE_FORMAT = environ.get('SHORT_DATE_FORMAT', 'Y-m-d') SHORT_DATE_FORMAT = os.environ.get('SHORT_DATE_FORMAT', 'Y-m-d')
TIME_FORMAT = environ.get('TIME_FORMAT', 'g:i a') TIME_FORMAT = os.environ.get('TIME_FORMAT', 'g:i a')
SHORT_TIME_FORMAT = environ.get('SHORT_TIME_FORMAT', 'H:i:s') SHORT_TIME_FORMAT = os.environ.get('SHORT_TIME_FORMAT', 'H:i:s')
DATETIME_FORMAT = environ.get('DATETIME_FORMAT', 'N j, Y g:i a') DATETIME_FORMAT = os.environ.get('DATETIME_FORMAT', 'N j, Y g:i a')
SHORT_DATETIME_FORMAT = environ.get('SHORT_DATETIME_FORMAT', 'Y-m-d H:i') SHORT_DATETIME_FORMAT = os.environ.get('SHORT_DATETIME_FORMAT', 'Y-m-d H:i')

View File

@ -1,55 +0,0 @@
####
## 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

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

View File

@ -1,28 +0,0 @@
####
## This file contains extra configuration options that can't be configured
## directly through environment variables.
## All vairables set here overwrite any existing found in ldap_config.py
####
# # This Python script inherits all the imports from ldap_config.py
# from django_auth_ldap.config import LDAPGroupQuery # Imported since not in ldap_config.py
# # Sets a base requirement of membetship to netbox-user-ro, netbox-user-rw, or netbox-user-admin.
# AUTH_LDAP_REQUIRE_GROUP = (
# LDAPGroupQuery("cn=netbox-user-ro,ou=groups,dc=example,dc=com")
# | LDAPGroupQuery("cn=netbox-user-rw,ou=groups,dc=example,dc=com")
# | LDAPGroupQuery("cn=netbox-user-admin,ou=groups,dc=example,dc=com")
# )
# # Sets LDAP Flag groups variables with example.
# AUTH_LDAP_USER_FLAGS_BY_GROUP = {
# "is_staff": (
# LDAPGroupQuery("cn=netbox-user-ro,ou=groups,dc=example,dc=com")
# | LDAPGroupQuery("cn=netbox-user-rw,ou=groups,dc=example,dc=com")
# | LDAPGroupQuery("cn=netbox-user-admin,ou=groups,dc=example,dc=com")
# ),
# "is_superuser": "cn=netbox-user-admin,ou=groups,dc=example,dc=com",
# }
# # Sets LDAP Mirror groups variables with example groups
# AUTH_LDAP_MIRROR_GROUPS = ["netbox-user-ro", "netbox-user-rw", "netbox-user-admin"]

View File

@ -1,103 +0,0 @@
from importlib import import_module
from os import environ
import ldap
from django_auth_ldap.config import LDAPSearch
# Read secret from file
def _read_secret(secret_name, default=None):
try:
f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8')
except EnvironmentError:
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):
mod = import_module('django_auth_ldap.config')
try:
return getattr(mod, group_type_name)()
except:
return None
# 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 = {
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 if needed.
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.
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 = 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_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 = 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(environ.get('AUTH_LDAP_GROUP_TYPE', 'GroupOfNamesType'))
# Define a group required to login.
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 = {}
if AUTH_LDAP_REQUIRE_GROUP is not None:
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
"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 = 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(environ.get('AUTH_LDAP_CACHE_TIMEOUT', 3600))
# Populate the Django user from the LDAP directory.
AUTH_LDAP_USER_ATTR_MAP = {
"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

@ -0,0 +1,81 @@
import ldap
import os
from django_auth_ldap.config import LDAPSearch
from importlib import import_module
# Read secret from file
def read_secret(secret_name):
try:
f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8')
except EnvironmentError:
return ''
else:
with f:
return f.readline().strip()
# Import and return the group type based on string name
def import_group_type(group_type_name):
mod = import_module('django_auth_ldap.config')
try:
return getattr(mod, group_type_name)()
except:
return None
# Server URI
AUTH_LDAP_SERVER_URI = os.environ.get('AUTH_LDAP_SERVER_URI', '')
# The following may be needed if you are binding to Active Directory.
AUTH_LDAP_CONNECTION_OPTIONS = {
ldap.OPT_REFERRALS: 0
}
# 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'))
# 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)
# 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'
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 = 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 = 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'))
# Define a group required to login.
AUTH_LDAP_REQUIRE_GROUP = os.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', '')
}
# 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'
# Cache groups for one hour to reduce LDAP traffic
AUTH_LDAP_CACHE_GROUPS = os.environ.get('AUTH_LDAP_CACHE_GROUPS', 'True').lower() == 'true'
AUTH_LDAP_GROUP_CACHE_TIMEOUT = int(os.environ.get('AUTH_LDAP_GROUP_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')
}

View File

@ -1,55 +0,0 @@
# # Remove first comment(#) on each line to implement this working logging example.
# # Add LOGLEVEL environment variable to netbox if you use this example & want a different log level.
# from os import environ
# # Set LOGLEVEL in netbox.env or docker-compose.overide.yml to override a logging level of INFO.
# LOGLEVEL = environ.get('LOGLEVEL', 'INFO')
# LOGGING = {
# 'version': 1,
# 'disable_existing_loggers': False,
# 'formatters': {
# 'verbose': {
# 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
# 'style': '{',
# },
# 'simple': {
# 'format': '{levelname} {message}',
# 'style': '{',
# },
# },
# 'filters': {
# 'require_debug_false': {
# '()': 'django.utils.log.RequireDebugFalse',
# },
# },
# 'handlers': {
# 'console': {
# 'level': LOGLEVEL,
# 'filters': ['require_debug_false'],
# 'class': 'logging.StreamHandler',
# 'formatter': 'simple'
# },
# 'mail_admins': {
# 'level': 'ERROR',
# 'class': 'django.utils.log.AdminEmailHandler',
# 'filters': ['require_debug_false']
# }
# },
# 'loggers': {
# 'django': {
# 'handlers': ['console'],
# 'propagate': True,
# },
# 'django.request': {
# 'handlers': ['mail_admins'],
# 'level': 'ERROR',
# 'propagate': False,
# },
# 'django_auth_ldap': {
# 'handlers': ['console',],
# 'level': LOGLEVEL,
# }
# }
# }

View File

@ -1,13 +0,0 @@
# Add your plugins and plugin settings here.
# Of course uncomment this file out.
# To learn how to build images with your required plugins
# See https://github.com/netbox-community/netbox-docker/wiki/Using-Netbox-Plugins
# PLUGINS = ["netbox_bgp"]
# PLUGINS_CONFIG = {
# "netbox_bgp": {
# ADD YOUR SETTINGS HERE
# }
# }

View File

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

View File

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

View File

@ -3,42 +3,43 @@ services:
netbox: netbox:
image: ${IMAGE-netboxcommunity/netbox:latest} image: ${IMAGE-netboxcommunity/netbox:latest}
depends_on: depends_on:
postgres: - postgres
condition: service_healthy - redis
redis:
condition: service_started
redis-cache:
condition: service_started
env_file: env/netbox.env env_file: env/netbox.env
user: 'unit:root' user: '101'
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-nginx-config:/etc/netbox-nginx:z
- netbox-static-files:/opt/netbox/netbox/static:z
- netbox-media-files:/opt/netbox/netbox/media:z - netbox-media-files:/opt/netbox/netbox/media:z
nginx:
command: nginx -c /etc/netbox-nginx/nginx.conf
image: nginx:1.17-alpine
depends_on:
- netbox
ports:
- 8080
volumes:
- netbox-static-files:/opt/netbox/netbox/static:ro
- netbox-nginx-config:/etc/netbox-nginx/:ro
postgres: postgres:
image: postgres:15-alpine image: postgres:11-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:5-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:7-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.env
volumes: volumes:
netbox-static-files:
driver: local
netbox-nginx-config:
driver: local
netbox-media-files: netbox-media-files:
driver: local driver: local

View File

@ -1,60 +1,49 @@
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-latest}
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: '101'
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
- netbox-nginx-config:/etc/netbox-nginx:z
- netbox-static-files:/opt/netbox/netbox/static:z
- netbox-media-files:/opt/netbox/netbox/media:z - netbox-media-files:/opt/netbox/netbox/media:z
netbox-worker: netbox-worker:
<<: *netbox <<: *netbox
depends_on: depends_on:
netbox: - redis
condition: service_healthy entrypoint:
command: - python3
- /opt/netbox/venv/bin/python
- /opt/netbox/netbox/manage.py - /opt/netbox/netbox/manage.py
- rqworker
healthcheck:
start_period: 20s
timeout: 3s
interval: 15s
test: "ps -aux | grep -v grep | grep -q rqworker || exit 1"
netbox-housekeeping:
<<: *netbox
depends_on:
netbox:
condition: service_healthy
command: command:
- /opt/netbox/housekeeping.sh - rqworker
healthcheck: nginx:
start_period: 20s command: nginx -c /etc/netbox-nginx/nginx.conf
timeout: 3s image: nginx:1.17-alpine
interval: 15s depends_on:
test: "ps -aux | grep -v grep | grep -q housekeeping || exit 1" - netbox
ports:
# postgres - 8080
volumes:
- netbox-static-files:/opt/netbox/netbox/static:ro
- netbox-nginx-config:/etc/netbox-nginx/:ro
postgres: postgres:
image: postgres:15-alpine image: postgres:11-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:
image: redis:7-alpine image: redis:5-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,21 +52,20 @@ services:
volumes: volumes:
- netbox-redis-data:/data - netbox-redis-data:/data
redis-cache: redis-cache:
image: redis:7-alpine image: redis:5-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.env
volumes:
- netbox-redis-cache-data:/data
volumes: volumes:
netbox-static-files:
driver: local
netbox-nginx-config:
driver: local
netbox-media-files: netbox-media-files:
driver: local driver: local
netbox-postgres-data: netbox-postgres-data:
driver: local driver: local
netbox-redis-data: netbox-redis-data:
driver: local driver: local
netbox-redis-cache-data:
driver: local

View File

@ -1,91 +1,10 @@
## 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).
import importlib.util import importlib.util
import sys import sys
from os import scandir
from os.path import abspath, isfile
try:
def _filename(f): spec = importlib.util.spec_from_file_location('configuration', '/etc/netbox/config/configuration.py')
return f.name module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
sys.modules['netbox.configuration'] = module
def _import(module_name, path, loaded_configurations): except:
spec = importlib.util.spec_from_file_location("", path) raise ImportError('')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
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"{main_config}.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
def __dir__():
names = []
for config in _loaded_configurations:
names.extend(config.__dir__())
return names

View File

@ -1,52 +1,25 @@
#!/bin/bash #!/bin/bash
# Runs on every start of the NetBox Docker container # Runs on every start of the Netbox Docker container
# Stop when an error occures # Stop when an error occures
set -e set -e
# Allows NetBox to be run as non-root users # Allows Netbox to be run as non-root users
umask 002 umask 002
# Load correct Python3 env
# shellcheck disable=SC1091
source /opt/netbox/venv/bin/activate
# Try to connect to the DB # Try to connect to the DB
DB_WAIT_TIMEOUT=${DB_WAIT_TIMEOUT-3} DB_WAIT_TIMEOUT=${DB_WAIT_TIMEOUT-3}
MAX_DB_WAIT_TIME=${MAX_DB_WAIT_TIME-30} MAX_DB_WAIT_TIME=${MAX_DB_WAIT_TIME-30}
CUR_DB_WAIT_TIME=0 CUR_DB_WAIT_TIME=0
while [ "${CUR_DB_WAIT_TIME}" -lt "${MAX_DB_WAIT_TIME}" ]; do while ! ./manage.py migrate 2>&1 && [ "${CUR_DB_WAIT_TIME}" -lt "${MAX_DB_WAIT_TIME}" ]; do
# Read and truncate connection error tracebacks to last line by default
exec {psfd}< <(./manage.py showmigrations 2>&1)
read -rd '' DB_ERR <&$psfd || :
exec {psfd}<&-
wait $! && break
if [ -n "$DB_WAIT_DEBUG" ]; then
echo "$DB_ERR"
else
readarray -tn 0 DB_ERR_LINES <<<"$DB_ERR"
echo "${DB_ERR_LINES[@]: -1}"
echo "[ Use DB_WAIT_DEBUG=1 in netbox.env to print full traceback for errors here ]"
fi
echo "⏳ Waiting on DB... (${CUR_DB_WAIT_TIME}s / ${MAX_DB_WAIT_TIME}s)" echo "⏳ Waiting on DB... (${CUR_DB_WAIT_TIME}s / ${MAX_DB_WAIT_TIME}s)"
sleep "${DB_WAIT_TIMEOUT}" sleep "${DB_WAIT_TIMEOUT}"
CUR_DB_WAIT_TIME=$((CUR_DB_WAIT_TIME + DB_WAIT_TIMEOUT)) CUR_DB_WAIT_TIME=$(( CUR_DB_WAIT_TIME + DB_WAIT_TIMEOUT ))
done done
if [ "${CUR_DB_WAIT_TIME}" -ge "${MAX_DB_WAIT_TIME}" ]; then if [ "${CUR_DB_WAIT_TIME}" -ge "${MAX_DB_WAIT_TIME}" ]; then
echo "❌ Waited ${MAX_DB_WAIT_TIME}s or more for the DB to become ready." echo "❌ Waited ${MAX_DB_WAIT_TIME}s or more for the DB to become ready."
exit 1 exit 1
fi fi
# Check if update is needed
if ! ./manage.py migrate --check >/dev/null 2>&1; then
echo "⚙️ Applying database migrations"
./manage.py migrate --no-input
echo "⚙️ Running trace_paths"
./manage.py trace_paths --no-input
echo "⚙️ Removing stale content types"
./manage.py remove_stale_contenttypes --no-input
echo "⚙️ Removing expired user sessions"
./manage.py clearsessions
fi
# Create Superuser if required # Create Superuser if required
if [ "$SKIP_SUPERUSER" == "true" ]; then if [ "$SKIP_SUPERUSER" == "true" ]; then
@ -58,18 +31,22 @@ else
if [ -z ${SUPERUSER_EMAIL+x} ]; then if [ -z ${SUPERUSER_EMAIL+x} ]; then
SUPERUSER_EMAIL='admin@example.com' SUPERUSER_EMAIL='admin@example.com'
fi fi
if [ -f "/run/secrets/superuser_password" ]; then if [ -z ${SUPERUSER_PASSWORD+x} ]; then
SUPERUSER_PASSWORD="$(</run/secrets/superuser_password)" if [ -f "/run/secrets/superuser_password" ]; then
elif [ -z ${SUPERUSER_PASSWORD+x} ]; then SUPERUSER_PASSWORD="$(< /run/secrets/superuser_password)"
SUPERUSER_PASSWORD='admin' else
SUPERUSER_PASSWORD='admin'
fi
fi fi
if [ -f "/run/secrets/superuser_api_token" ]; then if [ -z ${SUPERUSER_API_TOKEN+x} ]; then
SUPERUSER_API_TOKEN="$(</run/secrets/superuser_api_token)" if [ -f "/run/secrets/superuser_api_token" ]; then
elif [ -z ${SUPERUSER_API_TOKEN+x} ]; then SUPERUSER_API_TOKEN="$(< /run/secrets/superuser_api_token)"
SUPERUSER_API_TOKEN='0123456789abcdef0123456789abcdef01234567' else
SUPERUSER_API_TOKEN='0123456789abcdef0123456789abcdef01234567'
fi
fi fi
./manage.py shell --interface python <<END ./manage.py shell --interface python << END
from django.contrib.auth.models import User from django.contrib.auth.models import User
from users.models import Token from users.models import Token
if not User.objects.filter(username='${SUPERUSER_NAME}'): if not User.objects.filter(username='${SUPERUSER_NAME}'):
@ -80,8 +57,20 @@ 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
# Copy static files
./manage.py collectstatic --no-input
echo "✅ Initialisation is done." echo "✅ Initialisation is done."
# Launch whatever is passed by docker # Launch whatever is passed by docker
# (i.e. the RUN instruction in the Dockerfile) # (i.e. the RUN instruction in the Dockerfile)
exec "$@" #
# shellcheck disable=SC2068
exec $@

View File

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

View File

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

View File

@ -1,23 +1,10 @@
from .configuration import read_configurations import importlib.util
import sys
_loaded_configurations = read_configurations( try:
config_dir="/etc/netbox/config/ldap/", spec = importlib.util.spec_from_file_location('ldap_config', '/etc/netbox/config/ldap_config.py')
config_module="netbox.configuration.ldap", module = importlib.util.module_from_spec(spec)
main_config="ldap_config", spec.loader.exec_module(module)
) sys.modules['netbox.ldap_config'] = module
except:
raise ImportError('')
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

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

36
docker/nginx.conf Normal file
View File

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

42
env/netbox.env vendored
View File

@ -1,38 +1,32 @@
CORS_ORIGIN_ALLOW_ALL=True CORS_ORIGIN_ALLOW_ALL=True
DB_HOST=postgres
DB_NAME=netbox DB_NAME=netbox
DB_PASSWORD=J5brHrAXFLQSif0K
DB_USER=netbox DB_USER=netbox
EMAIL_FROM=netbox@bar.com DB_PASSWORD=J5brHrAXFLQSif0K
EMAIL_PASSWORD= DB_HOST=postgres
EMAIL_PORT=25
EMAIL_SERVER=localhost EMAIL_SERVER=localhost
EMAIL_SSL_CERTFILE= EMAIL_PORT=25
EMAIL_SSL_KEYFILE=
EMAIL_TIMEOUT=5
EMAIL_USERNAME=netbox EMAIL_USERNAME=netbox
# EMAIL_USE_SSL and EMAIL_USE_TLS are mutually exclusive, i.e. they can't both be `true`! EMAIL_PASSWORD=
EMAIL_USE_SSL=false EMAIL_TIMEOUT=5
EMAIL_USE_TLS=false EMAIL_FROM=netbox@bar.com
GRAPHQL_ENABLED=true
HOUSEKEEPING_INTERVAL=86400
MEDIA_ROOT=/opt/netbox/netbox/media MEDIA_ROOT=/opt/netbox/netbox/media
METRICS_ENABLED=false NAPALM_USERNAME=
REDIS_CACHE_DATABASE=1 NAPALM_PASSWORD=
REDIS_CACHE_HOST=redis-cache NAPALM_TIMEOUT=10
REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY=false MAX_PAGE_SIZE=1000
REDIS_CACHE_PASSWORD=t4Ph722qJ5QHeQ1qfu36
REDIS_CACHE_SSL=false
REDIS_DATABASE=0
REDIS_HOST=redis REDIS_HOST=redis
REDIS_INSECURE_SKIP_TLS_VERIFY=false
REDIS_PASSWORD=H733Kdjndks81 REDIS_PASSWORD=H733Kdjndks81
REDIS_DATABASE=0
REDIS_SSL=false REDIS_SSL=false
RELEASE_CHECK_URL=https://api.github.com/repos/netbox-community/netbox/releases REDIS_CACHE_HOST=redis-cache
REDIS_CACHE_PASSWORD=t4Ph722qJ5QHeQ1qfu36
REDIS_CACHE_DATABASE=0
REDIS_CACHE_SSL=false
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_EMAIL=admin@example.com
SUPERUSER_NAME=admin SUPERUSER_NAME=admin
SUPERUSER_EMAIL=admin@example.com
SUPERUSER_PASSWORD=admin SUPERUSER_PASSWORD=admin
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
WEBHOOKS_ENABLED=true WEBHOOKS_ENABLED=true

4
env/postgres.env vendored
View File

@ -1,3 +1,3 @@
POSTGRES_DB=netbox
POSTGRES_PASSWORD=J5brHrAXFLQSif0K
POSTGRES_USER=netbox POSTGRES_USER=netbox
POSTGRES_PASSWORD=J5brHrAXFLQSif0K
POSTGRES_DB=netbox

View File

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

View File

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

View File

@ -0,0 +1,5 @@
# - name: cluster1
# type: Hyper-V
# - name: cluster2
# type: Hyper-V
# site: SING 1

View File

@ -0,0 +1,97 @@
## 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
# on_objects:
# - dcim.models.Device
# choices:
# - value: First Item
# weight: 10
# - value: Second Item
# weight: 20
# - value: Third Item
# weight: 30
# - value: Fifth Item
# weight: 50
# - value: Fourth Item
# weight: 40
# select_field_auto_weight:
# type: select
# label: Choose between items
# required: false
# filter_logic: loose
# weight: 30
# on_objects:
# - dcim.models.Device
# choices:
# - value: A
# - value: B
# - value: C
# - value: "D like deprecated"
# weight: 999
# - value: E
# boolean_field:
# type: boolean
# label: Yes Or No?
# required: true
# filter_logic: loose
# default: "false" # important: but "false" in quotes!
# weight: 90
# on_objects:
# - dcim.models.Device
# url_field:
# type: url
# label: Hyperlink
# description: Link to something nice.
# required: true
# 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,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_fields:
# text_field: Description
# - model: Model 2
# manufacturer: Manufacturer 1
# slug: model-2
# custom_fields:
# text_field: Description
# - model: Model 3
# manufacturer: Manufacturer 1
# slug: model-3
# is_full_depth: false
# u_height: 0
# custom_fields:
# text_field: Description
# - model: Other
# manufacturer: No Name
# slug: other
# custom_fields:
# text_field: Description

42
initializers/devices.yml Normal file
View File

@ -0,0 +1,42 @@
## 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_fields:
# text_field: Description
# - name: server02
# device_role: server
# device_type: Other
# site: AMS 2
# rack: rack-02
# face: front
# position: 2
# custom_fields:
# text_field: Description
# - name: server03
# device_role: server
# device_type: Other
# site: SING 1
# rack: rack-03
# face: front
# position: 3
# custom_fields:
# text_field: Description

16
initializers/groups.yml Normal file
View File

@ -0,0 +1,16 @@
# applications:
# users:
# - technical_user
# readers:
# users:
# - reader
# writers:
# users:
# - writer
# permissions:
# - add_device
# - change_device
# - delete_device
# - add_virtualmachine
# - change_virtualmachine
# - delete_virtualmachine

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,6 @@
# - name: Manufacturer 1
# slug: manufacturer-1
# - name: Manufacturer 2
# slug: manufacturer-2
# - name: No Name
# slug: no-name

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,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,3 @@
# - name: cage 101
# slug: cage-101
# site: SING 1

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_fields:
# text_field: Description
# - site: AMS 2
# name: rack-02
# role: Role 2
# type: 4-post-cabinet
# width: 19
# u_height: 47
# custom_fields:
# text_field: Description
# - site: SING 1
# name: rack-03
# group: cage 101
# role: Role 3
# type: 4-post-cabinet
# width: 19
# u_height: 47
# custom_fields:
# 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

32
initializers/sites.yml Normal file
View File

@ -0,0 +1,32 @@
# - name: AMS 1
# slug: ams1
# region: Downtown
# status: 1
# facility: Amsterdam 1
# asn: 12345
# custom_fields:
# text_field: Description
# - name: AMS 2
# slug: ams2
# region: Downtown
# status: 1
# facility: Amsterdam 2
# asn: 54321
# custom_fields:
# text_field: Description
# - name: AMS 3
# slug: ams3
# region: Suburbs
# status: 1
# facility: Amsterdam 3
# asn: 67890
# custom_fields:
# text_field: Description
# - name: SING 1
# slug: sing1
# region: Singapore
# status: 1
# facility: Singapore 1
# asn: 09876
# custom_fields:
# text_field: Description

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

13
initializers/users.yml Normal file
View File

@ -0,0 +1,13 @@
# technical_user:
# api_token: 0123456789technicaluser789abcdef01234567 # must be looooong!
# reader:
# password: reader
# writer:
# password: writer
# permissions:
# - add_device
# - change_device
# - delete_device
# - add_virtualmachine
# - change_virtualmachine
# - delete_virtualmachine

View File

@ -0,0 +1,26 @@
## 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
# 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,6 @@
# - name: VLAN group 1
# site: AMS 1
# slug: vlan-group-1
# - name: VLAN group 2
# site: AMS 1
# slug: vlan-group-2

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

View File

@ -1,26 +0,0 @@
[tool.black]
line_length = 100
target-version = ['py38']
include = '\.pyi?$'
exclude = '''
(
/(
\.git
| \.venv
| \.netbox
| \.vscode
| configuration
)/
)
'''
[tool.isort]
profile = "black"
multi_line_output = 3
line_length = 100
[tool.pylint.messages_control]
disable = "C0330, C0326"
[tool.pylint.format]
max-line-length = "100"

View File

@ -1,188 +0,0 @@
#!/bin/bash
DEFAULT_REPO=netbox-community/netbox-docker
REPO="${REPO-${DEFAULT_REPO}}"
echomoji() {
EMOJI=${1}
TEXT=${2}
shift 2
if [ -z "$DISABLE_EMOJI" ]; then
echo "${EMOJI}" "${@}"
else
echo "${TEXT}" "${@}"
fi
}
echo_nok() {
echomoji "❌" "!" "${@}"
}
echo_ok() {
echomoji "✅" "-" "${@}"
}
echo_hint() {
echomoji "👉" ">" "${@}"
}
# check errors shall exit with code 1
check_clean_repo() {
changes=$(git status --porcelain 2>/dev/null)
if [ ${?} ] && [ -n "$changes" ]; then
echo_nok "There are git changes pending:"
echo "$changes"
echo_hint "Please clean the repository before continueing: git stash --include-untracked"
exit 1
fi
echo_ok "Repository has no pending changes."
}
check_branch() {
expected_branch="${1}"
actual_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
if [ ${?} ] && [ "${actual_branch}" != "${expected_branch}" ]; then
echo_nok "Current branch should be '${expected_branch}', but is '${actual_branch}'."
echo_hint "Please change to the '${expected_branch}' branch: git checkout ${expected_branch}"
exit 1
fi
echo_ok "The current branch is '${actual_branch}'."
}
check_upstream() {
expected_upstream_branch="origin/${1}"
actual_upstream_branch=$(git rev-parse --abbrev-ref '@{upstream}' 2>/dev/null)
if [ ${?} ] && [ "${actual_upstream_branch}" != "${expected_upstream_branch}" ]; then
echo_nok "Current upstream branch should be '${expected_upstream_branch}', but is '${actual_upstream_branch}'."
echo_hint "Please set '${expected_upstream_branch}' as the upstream branch: git branch --set-upstream-to=${expected_upstream_branch}"
exit 1
fi
echo_ok "The current upstream branch is '${actual_upstream_branch}'."
}
check_origin() {
expected_origin="git@github.com:${REPO}.git"
actual_origin=$(git remote get-url origin 2>/dev/null)
if [ ${?} ] && [ "${actual_origin}" != "${expected_origin}" ]; then
echo_nok "The url of origin is '${actual_origin}', but '${expected_origin}' is expected."
echo_hint "Please set '${expected_origin}' as the url for origin: git origin set-url '${expected_origin}'"
exit 1
fi
echo_ok "The current origin url is '${actual_origin}'."
}
check_latest() {
git fetch --tags origin
local_head_commit=$(git rev-parse HEAD 2>/dev/null)
remote_head_commit=$(git rev-parse FETCH_HEAD 2>/dev/null)
if [ "${local_head_commit}" != "${remote_head_commit}" ]; then
echo_nok "HEAD is at '${local_head_commit}', but FETCH_HEAD is at '${remote_head_commit}'."
echo_hint "Please ensure that you have pushed and pulled all the latest chanegs: git pull --prune --rebase origin; git push origin"
exit 1
fi
echo_ok "HEAD and FETCH_HEAD both point to '${local_head_commit}'."
}
check_tag() {
local tag
tag=$(<VERSION)
if git rev-parse "${tag}" 2>/dev/null >/dev/null; then
echo_nok "The tag '${tag}' already points to '$(git rev-parse "${tag}" 2>/dev/null)'."
echo_hint "Please ensure that the 'VERSION' file has been updated before trying to release: echo X.Y.Z > VERSION"
exit 1
fi
echo_ok "The tag '${tag}' does not exist yet."
}
check_develop() {
echomoji 📋 "?" "Checking 'develop' branch"
check_branch develop
check_upstream develop
check_clean_repo
check_latest
}
check_release() {
echomoji 📋 "?" "Checking 'release' branch"
check_upstream release
check_clean_repo
check_latest
}
# git errors shall exit with code 2
git_switch() {
echomoji 🔀 "≈" "Switching to '${1}' branch…"
if ! git checkout "${1}" >/dev/null; then
echo_nok "It was not possible to switch to the branch '${1}'."
exit 2
fi
echo_ok "The branch is now '${1}'."
}
git_tag() {
echomoji 🏷 "X" "Tagging version '${1}'…"
if ! git tag "${1}"; then
echo_nok "The tag '${1}' was not created because of an error."
exit 2
fi
echo_ok "The tag '$(<VERSION)' was created."
}
git_push() {
echomoji ⏩ "»" "Pushing the tag '${2}' to '${1}'…"
if ! git push "${1}" "${2}"; then
echo_nok "The tag '${2}' could not be pushed to '${1}'."
exit 2
fi
echo_ok "The tag '${2}' was pushed."
}
git_merge() {
echomoji ⏩ "»" "Merging '${1}'…"
if ! git merge --no-ff "${1}"; then
echo_nok "The branch '${1}' could not be merged."
exit 2
fi
echo_ok "The branch '${2}' was merged."
}
git_merge() {
echomoji ⏩ "»" "Rebasing onto '${1}'…"
if ! git rebase "${1}"; then
echo_nok "Could not rebase onto '${1}'."
exit 2
fi
echo_ok "Rebased onto '${2}'."
}
###
# MAIN
###
echomoji 📋 "▶︎" "Checking pre-requisites for releasing '$(<VERSION)'"
check_origin
check_develop
check_tag
git_switch release
check_release
echomoji 📋 "▶︎" "Releasing '$(<VERSION)'"
git_merge develop
check_tag
git_tag "$(<VERSION)"
git_push "origin" release
git_push "origin" "$(<VERSION)"
git_switch develop
git_rebase release
echomoji ✅ "◼︎" "The release of '$(<VERSION)' is complete."

View File

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

View File

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

View File

@ -1,6 +0,0 @@
django-auth-ldap==4.1.0
django-storages[azure,boto3,dropbox,google,libcloud,sftp]==1.13.1
napalm==4.0.0
psycopg2==2.9.5
python3-saml==1.14.0
social-auth-core[all]==4.3.0

View File

@ -0,0 +1,34 @@
from django.contrib.auth.models import Permission, Group, User
from users.models import Token
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/users.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml=YAML(typ='safe')
users = yaml.load(stream)
if users is not None:
for username, user_details in users.items():
if not User.objects.filter(username=username):
user = User.objects.create_user(
username = username,
password = user_details.get('password', 0) or User.objects.make_random_password)
print("👤 Created user ",username)
if user_details.get('api_token', 0):
Token.objects.create(user=user, key=user_details['api_token'])
user_permissions = user_details.get('permissions', [])
if user_permissions:
user.user_permissions.clear()
for permission_codename in user_details.get('permissions', []):
for permission in Permission.objects.filter(codename=permission_codename):
user.user_permissions.add(permission)
user.save()

View File

@ -0,0 +1,32 @@
from django.contrib.auth.models import Permission, Group, User
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/groups.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml=YAML(typ='safe')
groups = yaml.load(stream)
if groups is not None:
for groupname, group_details in groups.items():
group, created = Group.objects.get_or_create(name=groupname)
if created:
print("👥 Created group", groupname)
for username in group_details.get('users', []):
user = User.objects.get(username=username)
if user:
user.groups.add(group)
group_permissions = group_details.get('permissions', [])
if group_permissions:
group.permissions.clear()
for permission_codename in group_details.get('permissions', []):
for permission in Permission.objects.filter(codename=permission_codename):
group.permissions.add(permission)

View File

@ -0,0 +1,59 @@
from extras.models import CustomField, CustomFieldChoice
from ruamel.yaml import YAML
from pathlib import Path
import sys
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)
file = Path('/opt/netbox/initializers/custom_fields.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
customfields = yaml.load(stream)
if customfields is not None:
for cf_name, cf_details in customfields.items():
custom_field, created = CustomField.objects.get_or_create(name = cf_name)
if created:
if cf_details.get('default', 0):
custom_field.default = cf_details['default']
if cf_details.get('description', 0):
custom_field.description = cf_details['description']
if cf_details.get('label', 0):
custom_field.label = cf_details['label']
for object_type in cf_details.get('on_objects', []):
custom_field.obj_type.add(get_class_for_class_path(object_type))
if cf_details.get('required', 0):
custom_field.required = cf_details['required']
if cf_details.get('type', 0):
custom_field.type = cf_details['type']
if cf_details.get('weight', 0):
custom_field.weight = cf_details['weight']
custom_field.save()
for idx, choice_details in enumerate(cf_details.get('choices', [])):
choice, _ = CustomFieldChoice.objects.get_or_create(
field=custom_field,
value=choice_details['value'],
defaults={'weight': idx * 10}
)
print("🔧 Created custom field", cf_name)

View File

@ -0,0 +1,31 @@
from dcim.models import Region
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/regions.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml=YAML(typ='safe')
regions = yaml.load(stream)
optional_assocs = {
'parent': (Region, 'name')
}
if regions is not None:
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,46 @@
from dcim.models import Region, Site
from extras.models import CustomField, CustomFieldValue
from tenancy.models import Tenant
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/sites.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
sites = yaml.load(stream)
optional_assocs = {
'region': (Region, 'name'),
'tenant': (Tenant, 'name')
}
if sites is not None:
for params in sites:
custom_fields = params.pop('custom_fields', None)
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:
if custom_fields is not None:
for cf_name, cf_value in custom_fields.items():
custom_field = CustomField.objects.get(name=cf_name)
custom_field_value = CustomFieldValue.objects.create(
field=custom_field,
obj=site,
value=cf_value
)
site.custom_field_values.add(custom_field_value)
print("📍 Created site", site.name)

View File

@ -0,0 +1,19 @@
from dcim.models import Manufacturer
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/manufacturers.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
manufacturers = yaml.load(stream)
if manufacturers is not None:
for params in manufacturers:
manufacturer, created = Manufacturer.objects.get_or_create(**params)
if created:
print("🏭 Created Manufacturer", manufacturer.name)

View File

@ -0,0 +1,56 @@
from dcim.models import DeviceType, Manufacturer, Region
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/device_types.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
device_types = yaml.load(stream)
required_assocs = {
'manufacturer': (Manufacturer, 'name')
}
optional_assocs = {
'region': (Region, 'name'),
'tenant': (Tenant, 'name')
}
if device_types is not None:
for params in device_types:
custom_fields = params.pop('custom_fields', 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_type, created = DeviceType.objects.get_or_create(**params)
if created:
if custom_fields is not None:
for cf_name, cf_value in custom_fields.items():
custom_field = CustomField.objects.get(name=cf_name)
custom_field_value = CustomFieldValue.objects.create(
field=custom_field,
obj=device_type,
value=cf_value
)
device_type.custom_field_values.add(custom_field_value)
print("🔡 Created device type", device_type.manufacturer, device_type.model)

View File

@ -0,0 +1,28 @@
from dcim.models import RackRole
from ruamel.yaml import YAML
from utilities.forms import COLOR_CHOICES
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/rack_roles.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml=YAML(typ='safe')
rack_roles = yaml.load(stream)
if rack_roles is not None:
for params in rack_roles:
if 'color' in params:
color = params.pop('color')
for color_tpl in COLOR_CHOICES:
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,31 @@
from dcim.models import Site,RackGroup
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/rack_groups.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml=YAML(typ='safe')
rack_groups= yaml.load(stream)
required_assocs = {
'site': (Site, 'name')
}
if rack_groups is not None:
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)
rack_group, created = RackGroup.objects.get_or_create(**params)
if created:
print("🎨 Created rack group", rack_group.name)

View File

@ -0,0 +1,57 @@
from dcim.models import Site, RackRole, Rack, RackGroup
from tenancy.models import Tenant
from extras.models import CustomField, CustomFieldValue
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/racks.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
racks = yaml.load(stream)
required_assocs = {
'site': (Site, 'name')
}
optional_assocs = {
'role': (RackRole, 'name'),
'tenant': (Tenant, 'name'),
'group': (RackGroup, 'name')
}
if racks is not None:
for params in racks:
custom_fields = params.pop('custom_fields', 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)
rack, created = Rack.objects.get_or_create(**params)
if created:
if custom_fields is not None:
for cf_name, cf_value in custom_fields.items():
custom_field = CustomField.objects.get(name=cf_name)
custom_field_value = CustomFieldValue.objects.create(
field=custom_field,
obj=rack,
value=cf_value
)
rack.custom_field_values.add(custom_field_value)
print("🔳 Created rack", rack.site, rack.name)

View File

@ -0,0 +1,29 @@
from dcim.models import DeviceRole
from ruamel.yaml import YAML
from utilities.forms import COLOR_CHOICES
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/device_roles.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml=YAML(typ='safe')
device_roles = yaml.load(stream)
if device_roles is not None:
for params in device_roles:
if 'color' in params:
color = params.pop('color')
for color_tpl in COLOR_CHOICES:
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,32 @@
from dcim.models import Manufacturer, Platform
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/platforms.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
platforms = yaml.load(stream)
optional_assocs = {
'manufacturer': (Manufacturer, 'name'),
}
if platforms is not None:
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,19 @@
from tenancy.models import TenantGroup
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/tenant_groups.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
tenant_groups = yaml.load(stream)
if tenant_groups is not None:
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,45 @@
from tenancy.models import Tenant, TenantGroup
from extras.models import CustomField, CustomFieldValue
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/tenants.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
tenants = yaml.load(stream)
optional_assocs = {
'group': (TenantGroup, 'name')
}
if tenants is not None:
for params in tenants:
custom_fields = params.pop('custom_fields', None)
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:
if custom_fields is not None:
for cf_name, cf_value in custom_fields.items():
custom_field = CustomField.objects.get(name=cf_name)
custom_field_value = CustomFieldValue.objects.create(
field=custom_field,
obj=tenant,
value=cf_value
)
tenant.custom_field_values.add(custom_field_value)
print("👩‍💻 Created Tenant", tenant.name)

View File

@ -0,0 +1,65 @@
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
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/devices.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
devices = yaml.load(stream)
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'),
'primary_ip4': (IPAddress, 'address'),
'primary_ip6': (IPAddress, 'address')
}
if devices is not None:
for params in devices:
custom_fields = params.pop('custom_fields', 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:
if custom_fields is not None:
for cf_name, cf_value in custom_fields.items():
custom_field = CustomField.objects.get(name=cf_name)
custom_field_value = CustomFieldValue.objects.create(
field=custom_field,
obj=device,
value=cf_value
)
device.custom_field_values.add(custom_field_value)
print("🖥️ Created device", device.name)

View File

@ -0,0 +1,19 @@
from virtualization.models import ClusterType
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/cluster_types.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
cluster_types = yaml.load(stream)
if cluster_types is not None:
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,19 @@
from ipam.models import RIR
from ruamel.yaml import YAML
from pathlib import Path
import sys
file = Path('/opt/netbox/initializers/rirs.yml')
if not file.is_file():
sys.exit()
with file.open('r') as stream:
yaml = YAML(typ='safe')
rirs = yaml.load(stream)
if rirs is not None:
for params in rirs:
rir, created = RIR.objects.get_or_create(**params)
if created:
print("🗺️ Created RIR", rir.name)

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