Compare commits
162 Commits
Author | SHA1 | Date | |
---|---|---|---|
00022e7d79 | |||
dd490605ca | |||
e2711ca205 | |||
a87f2b3331 | |||
f174749f98 | |||
b02a93904e | |||
3ace32dfc2 | |||
9fae2b0f74 | |||
23c3240863 | |||
3f9e874d77 | |||
4b0f158852 | |||
51490d0039 | |||
688672de26 | |||
5624ecc65e | |||
7f93b328af | |||
ccad15ecf7 | |||
e9a9d9b70b | |||
03b36058cd | |||
9e06a38d41 | |||
b7ad18afb0 | |||
93a3784295 | |||
45f7823a17 | |||
a6584d2874 | |||
fd87c7cb98 | |||
1f38ca0a86 | |||
efec435ba0 | |||
ab8ff04852 | |||
77feec30a0 | |||
bed40b0d05 | |||
24f0545bc6 | |||
1bc1ab2a0a | |||
bfa69dc0e9 | |||
6c8042b63d | |||
795e82be46 | |||
d8a6c321a1 | |||
1ecf4853b8 | |||
5cb8e97e65 | |||
98e131fa30 | |||
d6323ce40f | |||
1c65f7af10 | |||
7fb78b3fd2 | |||
aa9f01a778 | |||
786f9b50d2 | |||
5909670690 | |||
231de236e0 | |||
9ea95950b9 | |||
d84c399c00 | |||
a217ce8ffd | |||
794fb45e0e | |||
8e34f46bad | |||
ed0d099df7 | |||
26d08302e3 | |||
64b763429f | |||
5769684c98 | |||
23e2da52f8 | |||
edba1a22fc | |||
f87ffe7c33 | |||
8bc77c1bc0 | |||
7662d81efe | |||
7f489aa11d | |||
5c3eff0823 | |||
5f90ad8c56 | |||
5f1c241145 | |||
20c7461c7b | |||
b9c44b85cc | |||
355ebadd10 | |||
880628876f | |||
8d3bd48c7a | |||
882f7bcaf2 | |||
50ade7bce1 | |||
80f514fa90 | |||
c5822b9cec | |||
e99a222a70 | |||
3717b7469a | |||
3d80cc5a72 | |||
69ef7b7827 | |||
ba3176f140 | |||
a2c06026d5 | |||
f4e243d5ad | |||
aa0d2a6e01 | |||
cce4370d41 | |||
927a545f41 | |||
0574ffc571 | |||
ce74e94cbb | |||
778f7546b8 | |||
74eaae6bc8 | |||
00986573d9 | |||
b0b20aa6ba | |||
f3a858811a | |||
1eb40d1774 | |||
1f25fba671 | |||
f525351cfe | |||
0e625a3b5c | |||
f0b00ee104 | |||
1c8d695fc2 | |||
653321994a | |||
132ab6fcca | |||
9229b45dc2 | |||
d058b7bc93 | |||
a0f5a11ff1 | |||
05b9431ecb | |||
e179616db4 | |||
b92fbdc50d | |||
f26df57bfd | |||
8892ea9936 | |||
1bad9b4fa8 | |||
2b21e14c2c | |||
8caf755914 | |||
27f671e41a | |||
74a0e2cf6e | |||
f3403cd0f5 | |||
8d8b9a157e | |||
4a58676647 | |||
7b914d31d6 | |||
c001626b85 | |||
355f9d4cf7 | |||
40ef427336 | |||
e16b009a7f | |||
25671d42a5 | |||
dd0aee081a | |||
cfbd037f79 | |||
3b56c827f9 | |||
870b44fdbc | |||
79a10dd445 | |||
57afeec94f | |||
b118cd5812 | |||
7863e5902e | |||
0a9991de96 | |||
28c786c2a6 | |||
1c899b55eb | |||
7af88388bb | |||
51df2cbbaa | |||
b48de9f87e | |||
85065005d4 | |||
0696ecb037 | |||
05d32ae705 | |||
fd955544af | |||
0a77c3d81e | |||
66e90428b5 | |||
310cda1f18 | |||
daaf72962f | |||
93dee74459 | |||
1064696c96 | |||
7df5da38bf | |||
e4e0a63e17 | |||
bf2a21ddea | |||
6a07527632 | |||
0f7675c792 | |||
f3bbfdc34c | |||
a4186c1031 | |||
809570f4bc | |||
135199e597 | |||
8a92640d10 | |||
788804627e | |||
150f35ea3b | |||
1ce99cf02f | |||
5e92352b0a | |||
8664e42233 | |||
7942e9edbe | |||
01c4137dc9 | |||
f2fd7dbbe7 | |||
6568dff8e1 |
@ -5,3 +5,6 @@
|
|||||||
env
|
env
|
||||||
build*
|
build*
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
|
.netbox/.git*
|
||||||
|
.netbox/.travis.yml
|
||||||
|
.netbox/scripts
|
||||||
|
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
4
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -25,6 +25,8 @@ Please try this means to get help before opening an issue here:
|
|||||||
* On the networktocode Slack in the #netbox 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
|
* 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
|
## Current Behavior
|
||||||
@ -46,7 +48,7 @@ The output of `git rev-parse HEAD`: `XXXXX`
|
|||||||
The command you used to start the project: `XXXXX`
|
The command you used to start the project: `XXXXX`
|
||||||
|
|
||||||
<!-- adjust the `latest` tag to the version you're using -->
|
<!-- adjust the `latest` tag to the version you're using -->
|
||||||
The output of `docker inspect netboxcommunity/netbox:latest --format "{{json .ContainerConfig.Labels}}"`:
|
The output of `docker inspect netboxcommunity/netbox:latest --format "{{json .Config.Labels}}"`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -24,6 +24,8 @@ Please try this means to get help before opening an issue here:
|
|||||||
* On the networktocode Slack in the #netbox 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
|
* 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
|
## Desired Behavior
|
||||||
@ -33,7 +35,7 @@ Please try this means to get help before opening an issue here:
|
|||||||
|
|
||||||
## Contrast to Current Behavior
|
## Contrast to Current Behavior
|
||||||
|
|
||||||
<!-- please describe how the desired behavior is different to the current behavior -->
|
<!-- please describe how the desired behavior is different from the current behavior -->
|
||||||
...
|
...
|
||||||
|
|
||||||
## Changes Required
|
## Changes Required
|
||||||
|
10
.github/no-response.yml
vendored
Normal file
10
.github/no-response.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# Configuration for probot-no-response - https://github.com/probot/no-response
|
||||||
|
|
||||||
|
# Number of days of inactivity before an Issue is closed for lack of response
|
||||||
|
daysUntilClose: 30
|
||||||
|
# Label requiring a response
|
||||||
|
responseRequiredLabel: awaiting answer
|
||||||
|
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
|
||||||
|
closeComment: >
|
||||||
|
This issue has been automatically closed because there has been no response
|
||||||
|
to our request for more information from the original author.
|
85
.github/pull_request_template.md
vendored
Normal file
85
.github/pull_request_template.md
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<!--
|
||||||
|
#########################################################################
|
||||||
|
|
||||||
|
Thank you for sharing your work and for opening a PR.
|
||||||
|
|
||||||
|
(!) IMPORTANT (!):
|
||||||
|
First make sure that you point your PR to the `develop` branch!
|
||||||
|
|
||||||
|
Now please read the comments carefully and try to provide information
|
||||||
|
on all relevant titles.
|
||||||
|
|
||||||
|
#########################################################################
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please don't open an extra issue when submitting a PR.
|
||||||
|
|
||||||
|
But if there is already a related issue, please put it's number here.
|
||||||
|
|
||||||
|
E.g. #123 or N/A
|
||||||
|
-->
|
||||||
|
|
||||||
|
Related Issue:
|
||||||
|
|
||||||
|
## New Behavior
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please describe in a few words the intentions of your PR.
|
||||||
|
-->
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## Contrast to Current Behavior
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please describe in a few words how the new behavior is different
|
||||||
|
from the current behavior.
|
||||||
|
-->
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## Discussion: Benefits and Drawbacks
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please make your case here:
|
||||||
|
|
||||||
|
- Why do you think this project and the community will benefit from your
|
||||||
|
proposed change?
|
||||||
|
- What are the drawbacks of this change?
|
||||||
|
- Is it backwards-compatible?
|
||||||
|
- Anything else that you think is relevant to the discussion of this PR.
|
||||||
|
|
||||||
|
(No need to write a huge article here. Just a few sentences that give some
|
||||||
|
additional context about the motivations for the change.)
|
||||||
|
-->
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## Changes to the Wiki
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If the README.md must be updated, please include the changes in the PR.
|
||||||
|
If the Wiki must be updated, please make a suggestion below.
|
||||||
|
-->
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## Proposed Release Note Entry
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please provide a short summary of your PR that we can copy & paste
|
||||||
|
into the release notes.
|
||||||
|
-->
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## Double Check
|
||||||
|
|
||||||
|
<!--
|
||||||
|
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 explained my PR according to the information in the comments.
|
||||||
|
* [ ] My PR targets the `develop` branch.
|
41
.github/workflows/push.yml
vendored
Normal file
41
.github/workflows/push.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
name: push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- release
|
||||||
|
pull_request:
|
||||||
|
branches-ignore:
|
||||||
|
- release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build_cmd:
|
||||||
|
- ./build-latest.sh
|
||||||
|
- PRERELEASE=true ./build-latest.sh
|
||||||
|
- ./build-next.sh
|
||||||
|
- ./build.sh develop
|
||||||
|
docker_from:
|
||||||
|
- '' # use the default of the DOCKERFILE
|
||||||
|
- python:3.7-alpine
|
||||||
|
- python:3.8-alpine
|
||||||
|
# - python:3.9-rc-alpine # disable until Netbox's unit tests work
|
||||||
|
fail-fast: false
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Builds new Netbox Docker Images
|
||||||
|
steps:
|
||||||
|
- id: git-checkout
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
- id: docker-build
|
||||||
|
name: Build the image from '${{ matrix.docker_from }}' with '${{ matrix.build_cmd }}'
|
||||||
|
run: ${{ matrix.build_cmd }}
|
||||||
|
env:
|
||||||
|
DOCKER_FROM: ${{ matrix.docker_from }}
|
||||||
|
GH_ACTION: enable
|
||||||
|
- id: docker-test
|
||||||
|
name: Test the image
|
||||||
|
run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh
|
||||||
|
if: steps.docker-build.outputs.skipped != 'true'
|
52
.github/workflows/release.yml
vendored
Normal file
52
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- release
|
||||||
|
schedule:
|
||||||
|
- cron: '45 5 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
build_cmd:
|
||||||
|
- ./build-latest.sh
|
||||||
|
- PRERELEASE=true ./build-latest.sh
|
||||||
|
- ./build-next.sh
|
||||||
|
- ./build.sh develop
|
||||||
|
fail-fast: false
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Builds new Netbox Docker Images
|
||||||
|
steps:
|
||||||
|
- id: git-checkout
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- id: docker-build
|
||||||
|
name: Build the image with '${{ matrix.build_cmd }}'
|
||||||
|
run: ${{ matrix.build_cmd }}
|
||||||
|
env:
|
||||||
|
GH_ACTION: enable
|
||||||
|
- id: docker-test
|
||||||
|
name: Test the image
|
||||||
|
run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh
|
||||||
|
if: steps.docker-build.outputs.skipped != 'true'
|
||||||
|
- id: registry-login
|
||||||
|
name: Login to the Docker Registry
|
||||||
|
run: |
|
||||||
|
echo "::add-mask::$DOCKERHUB_USERNAME"
|
||||||
|
echo "::add-mask::$DOCKERHUB_PASSWORD"
|
||||||
|
docker login -u "$DOCKERHUB_USERNAME" --password "${DOCKERHUB_PASSWORD}" "${DOCKER_REGISTRY}"
|
||||||
|
env:
|
||||||
|
DOCKERHUB_USERNAME: ${{ secrets.dockerhub_username }}
|
||||||
|
DOCKERHUB_PASSWORD: ${{ secrets.dockerhub_password }}
|
||||||
|
if: steps.docker-build.outputs.skipped != 'true'
|
||||||
|
- id: registry-push
|
||||||
|
name: Push the image
|
||||||
|
run: ${{ matrix.build_cmd }} --push-only
|
||||||
|
if: steps.docker-build.outputs.skipped != 'true'
|
||||||
|
- id: registry-logout
|
||||||
|
name: Logout of the Docker Registry
|
||||||
|
run: docker logout "${DOCKER_REGISTRY}"
|
||||||
|
if: steps.docker-build.outputs.skipped != 'true'
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
*.sql.gz
|
*.sql.gz
|
||||||
.netbox
|
.netbox
|
||||||
|
.initializers
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
|
*.pem
|
||||||
|
32
.travis.yml
32
.travis.yml
@ -1,32 +0,0 @@
|
|||||||
sudo: required
|
|
||||||
language: python
|
|
||||||
|
|
||||||
env:
|
|
||||||
- BUILD=release
|
|
||||||
- BUILD=prerelease
|
|
||||||
- BUILD=branches
|
|
||||||
- BUILD=special
|
|
||||||
|
|
||||||
git:
|
|
||||||
depth: 5
|
|
||||||
|
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
install:
|
|
||||||
- docker-compose pull --parallel
|
|
||||||
- docker-compose build
|
|
||||||
|
|
||||||
script:
|
|
||||||
- docker-compose run netbox ./manage.py test
|
|
||||||
|
|
||||||
after_script:
|
|
||||||
- docker-compose down
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
|
|
||||||
- ./build-all.sh --push
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
slack:
|
|
||||||
secure: F3VsWcvU/XYyjGjU8ZAVGpREe7F1NjKq6LuMRzhQORbXUvanxDQtLzEe0Y5zm/6+gHkx6t8cX/v2PiCI+v46pkapYMUimd+QEOL1WxbUdnw2kQxcgw/R3wX34l2FHXbG3/a+TmH3euqbSCTIrPy9ufju948i+Q0E0u0fyInmozl8qOT23C4joQOpVAq7y+wHxTxsEg46ZzL2Ties+dmqjMsvHocv7mPI2IWzAWA8SJZxS82Amoapww++QjgEmoY+tMimLkdeXCRgeoj41UGHDg54rbEXh/PTaWiuzyzTr1WLmsGRScC57fDRivp3mSM37/MlNxsRj1z+j4zrvWFQgNfJ2yMjBHroc1jOX/uCY4dwbpSPqUCpc4idMGCGZFItgzTQ3lAPYAsom0C6n8C08Xk8EsNKWwXrDSd4ZUhIwptkNPCFK+kXbLFsMzSApnaBYW0T+wba57nZdiWjOPYmvJr49MDm5NHv2KaRBX2gpw7t7ZLhTgwGEWcZvcDebiLneXcXY5hZ7v2NHJkx/2x1yNXo85xZDy0wK1FGoOOHwPhvqOB+pcQZ/pUOSPTKqGw5l/CexoRm1shFsK+19FnSgimqTHjcuCo4lFW3JlEvlFhtfFXIte2Wjp1ALZgTrSq8zSD5rRxYCUKmM7b3EJwdaIgbvKWPdS4sCXlXU1bHx0g=
|
|
@ -1,63 +0,0 @@
|
|||||||
# 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: master
|
|
||||||
Docker Tag: branches
|
|
||||||
Dockerfile location: Dockerfile
|
|
||||||
Build Context: /
|
|
||||||
Autobuild: on
|
|
||||||
Build Caching: on
|
|
||||||
- Source Type: Branch
|
|
||||||
Source: master
|
|
||||||
Docker Tag: prerelease
|
|
||||||
Dockerfile location: Dockerfile
|
|
||||||
Build Context: /
|
|
||||||
Autobuild: on
|
|
||||||
Build Caching: on
|
|
||||||
- Source Type: Branch
|
|
||||||
Source: master
|
|
||||||
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
|
|
15
Dockerfile
15
Dockerfile
@ -1,4 +1,4 @@
|
|||||||
ARG FROM=python:3.7-alpine
|
ARG FROM
|
||||||
FROM ${FROM} as builder
|
FROM ${FROM} as builder
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
@ -18,7 +18,7 @@ WORKDIR /install
|
|||||||
|
|
||||||
RUN pip install --prefix="/install" --no-warn-script-location \
|
RUN pip install --prefix="/install" --no-warn-script-location \
|
||||||
# gunicorn is used for launching netbox
|
# gunicorn is used for launching netbox
|
||||||
'gunicorn<20.0.0' \
|
gunicorn \
|
||||||
greenlet \
|
greenlet \
|
||||||
eventlet \
|
eventlet \
|
||||||
# napalm is used for gathering information from network devices
|
# napalm is used for gathering information from network devices
|
||||||
@ -26,7 +26,9 @@ RUN pip install --prefix="/install" --no-warn-script-location \
|
|||||||
# ruamel is used in startup_scripts
|
# ruamel is used in startup_scripts
|
||||||
'ruamel.yaml>=0.15,<0.16' \
|
'ruamel.yaml>=0.15,<0.16' \
|
||||||
# django_auth_ldap is required for ldap
|
# django_auth_ldap is required for ldap
|
||||||
django_auth_ldap
|
django_auth_ldap \
|
||||||
|
# django-storages was introduced in 2.7 and is optional
|
||||||
|
django-storages
|
||||||
|
|
||||||
ARG NETBOX_PATH
|
ARG NETBOX_PATH
|
||||||
COPY ${NETBOX_PATH}/requirements.txt /
|
COPY ${NETBOX_PATH}/requirements.txt /
|
||||||
@ -68,6 +70,13 @@ COPY configuration/configuration.py /etc/netbox/config/configuration.py
|
|||||||
|
|
||||||
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
|
||||||
|
# to g+w so that pictures can be uploaded to netbox.
|
||||||
|
RUN mkdir static && chmod -R g+w static media
|
||||||
|
|
||||||
ENTRYPOINT [ "/opt/netbox/docker-entrypoint.sh" ]
|
ENTRYPOINT [ "/opt/netbox/docker-entrypoint.sh" ]
|
||||||
|
|
||||||
CMD ["gunicorn", "-c /etc/netbox/config/gunicorn_config.py", "netbox.wsgi"]
|
CMD ["gunicorn", "-c /etc/netbox/config/gunicorn_config.py", "netbox.wsgi"]
|
||||||
|
110
README.md
110
README.md
@ -3,7 +3,7 @@
|
|||||||
[][github-release]
|
[][github-release]
|
||||||
[][github-stargazers]
|
[][github-stargazers]
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
[][netbox-docker-microbadger]
|
[][netbox-docker-microbadger]
|
||||||
[][netbox-docker-microbadger]
|
[][netbox-docker-microbadger]
|
||||||
@ -12,7 +12,8 @@
|
|||||||
[The Github repository](netbox-docker-github) houses the components needed to build Netbox as a Docker container.
|
[The Github repository](netbox-docker-github) houses the components needed to build Netbox as a Docker container.
|
||||||
Images are built using this code and are released to [Docker Hub][netbox-dockerhub] once a day.
|
Images are built using this code and are released to [Docker Hub][netbox-dockerhub] once a day.
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
[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
|
||||||
@ -21,20 +22,22 @@ Do you have any questions? Before opening an issue on Github, please join the [N
|
|||||||
[netbox-docker-github]: https://github.com/netbox-community/netbox-docker/
|
[netbox-docker-github]: https://github.com/netbox-community/netbox-docker/
|
||||||
[ntc-slack]: http://slack.networktocode.com/
|
[ntc-slack]: http://slack.networktocode.com/
|
||||||
[netbox-docker-slack]: https://slack.com/app_redirect?channel=netbox-docker&team=T09LQ7E9E
|
[netbox-docker-slack]: https://slack.com/app_redirect?channel=netbox-docker&team=T09LQ7E9E
|
||||||
[netbox-docker-license]: https://github.com/netbox-community/netbox-docker/blob/master/LICENSE
|
[netbox-docker-license]: https://github.com/netbox-community/netbox-docker/blob/release/LICENSE
|
||||||
|
|
||||||
## Docker Tags
|
## Docker Tags
|
||||||
|
|
||||||
* `vX.Y.Z`: Release builds, built from [releases of Netbox][netbox-releases].
|
* `vX.Y.Z`: These are release builds, automatically built from [the corresponding releases of Netbox][netbox-releases].
|
||||||
* `latest`: Release builds, built from [`master` branch of Netbox][netbox-master].
|
* `latest`: These are release builds, automatically built from [the `master` branch of Netbox][netbox-master].
|
||||||
* `snapshot`: Pre-release builds, built from the [`develop` branch of Netbox][netbox-develop].
|
* `snapshot`: These are pre-release builds, automatically built from the [`develop` branch of Netbox][netbox-develop].
|
||||||
* `develop-X.Y`: Pre-release builds, built from the corresponding [branch of Netbox][netbox-branches].
|
* `develop-X.Y`: These are pre-release builds, automatically built from the corresponding [branch of Netbox][netbox-branches].
|
||||||
|
|
||||||
Then there is currently one extra tags for each of the above labels:
|
Then there is currently one extra tags for each of the above tags:
|
||||||
|
|
||||||
* `-ldap`: Contains additional dependencies and configurations for connecting Netbox to an LDAP directroy.
|
* `-ldap`: Contains additional dependencies and configurations for connecting Netbox to an LDAP directory.
|
||||||
[Learn more about that in our wiki][netbox-docker-ldap].
|
[Learn more about that in our wiki][netbox-docker-ldap].
|
||||||
|
|
||||||
|
New images are built and published automatically every ~24h.
|
||||||
|
|
||||||
[netbox-releases]: https://github.com/netbox-community/netbox/releases
|
[netbox-releases]: https://github.com/netbox-community/netbox/releases
|
||||||
[netbox-master]: https://github.com/netbox-community/netbox/tree/master
|
[netbox-master]: https://github.com/netbox-community/netbox/tree/master
|
||||||
[netbox-develop]: https://github.com/netbox-community/netbox/tree/develop
|
[netbox-develop]: https://github.com/netbox-community/netbox/tree/develop
|
||||||
@ -43,48 +46,36 @@ Then there is currently one extra tags for each of the above labels:
|
|||||||
|
|
||||||
## Quickstart
|
## Quickstart
|
||||||
|
|
||||||
To get Netbox up and running:
|
To get Netbox Docker up and running run the following commands.
|
||||||
|
There is a more complete [_Getting Started_ guide on our wiki][wiki-getting-started] which explains every step.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone -b master 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:
|
||||||
|
nginx:
|
||||||
|
ports:
|
||||||
|
- 8000:8080
|
||||||
|
EOF
|
||||||
docker-compose pull
|
docker-compose pull
|
||||||
docker-compose up -d
|
docker-compose up
|
||||||
```
|
```
|
||||||
|
|
||||||
The application will be available after a few minutes.
|
The whole application will be available after a few minutes.
|
||||||
Use `docker-compose port nginx 8080` to find out where to connect to.
|
Open the URL `http://0.0.0.0:8000/` in a web-browser.
|
||||||
|
You should see the Netbox homepage.
|
||||||
```bash
|
In the top-right corner you can login.
|
||||||
$ echo "http://$(docker-compose port nginx 8080)/"
|
The default credentials are:
|
||||||
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.
|
|
||||||
|
|
||||||
Default credentials:
|
|
||||||
|
|
||||||
* Username: **admin**
|
* Username: **admin**
|
||||||
* Password: **admin**
|
* Password: **admin**
|
||||||
* API Token: **0123456789abcdef0123456789abcdef01234567**
|
* API Token: **0123456789abcdef0123456789abcdef01234567**
|
||||||
|
|
||||||
|
[wiki-getting-started]: https://github.com/netbox-community/netbox-docker/wiki/Getting-Started
|
||||||
[docker-reception]: https://github.com/nxt-engineering/reception
|
[docker-reception]: https://github.com/nxt-engineering/reception
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
This project relies only on *Docker* and *docker-compose* meeting this requirements:
|
|
||||||
|
|
||||||
* The *Docker version* must be at least `17.05`.
|
|
||||||
* The *docker-compose version* must be at least `1.17.0`.
|
|
||||||
|
|
||||||
To ensure this, compare the output of `docker --version` and `docker-compose --version` with the requirements above.
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Please refer [to our wiki on Github][netbox-docker-wiki] for further information on how to use this Netbox Docker image properly.
|
Please refer [to our wiki on Github][netbox-docker-wiki] for further information on how to use this Netbox Docker image properly.
|
||||||
@ -92,15 +83,31 @@ It covers advanced topics such as using secret files, deployment to Kubernetes a
|
|||||||
|
|
||||||
[netbox-docker-wiki]: https://github.com/netbox-community/netbox-docker/wiki/
|
[netbox-docker-wiki]: https://github.com/netbox-community/netbox-docker/wiki/
|
||||||
|
|
||||||
## Netbox Version
|
## Getting Help
|
||||||
|
|
||||||
The `docker-compose.yml` file is prepared to run a specific version of Netbox.
|
Please join [our Slack channel `#netbox-docker`][netbox-docker-slack] on the [Network To Code Slack][ntc-slack].
|
||||||
To use this feature, set the environment-variable `VERSION` before launching `docker-compose`, as shown below.
|
It's free to use and there are almost always people online that can help.
|
||||||
|
|
||||||
|
If you need help with using Netbox or developing for it or against it's API you may find the `#netbox` channel on the same Slack instance very helpful.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
This project relies only on *Docker* and *docker-compose* meeting these requirements:
|
||||||
|
|
||||||
|
* The *Docker version* must be at least `17.05`.
|
||||||
|
* The *docker-compose version* must be at least `1.17.0`.
|
||||||
|
|
||||||
|
To check the version installed on your system run `docker --version` and `docker-compose --version`.
|
||||||
|
|
||||||
|
## Use a Specific Netbox Version
|
||||||
|
|
||||||
|
The `docker-compose.yml` file is prepared to run a specific version of Netbox, instead of `latest`.
|
||||||
|
To use this feature, set and export the environment-variable `VERSION` before launching `docker-compose`, as shown below.
|
||||||
`VERSION` may be set to the name of
|
`VERSION` may be set to the name of
|
||||||
[any tag of the `netboxcommunity/netbox` Docker image on Docker Hub][netbox-dockerhub].
|
[any tag of the `netboxcommunity/netbox` Docker image on Docker Hub][netbox-dockerhub].
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export VERSION=v2.6.7
|
export VERSION=v2.7.1
|
||||||
docker-compose pull netbox
|
docker-compose pull netbox
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
@ -109,7 +116,7 @@ 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.
|
`VERSION` can be any valid [git ref][git-ref] in that case.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export VERSION=v2.6.7
|
export VERSION=v2.7.1
|
||||||
./build.sh $VERSION
|
./build.sh $VERSION
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
@ -121,31 +128,30 @@ docker-compose up -d
|
|||||||
|
|
||||||
From time to time it might become necessary to re-engineer the structure of this setup.
|
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.
|
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.
|
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.)
|
(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.6.7 --format "{{json .ContainerConfig.Labels}}"`.
|
You can check the label of your local image by running `docker inspect netboxcommunity/netbox:v2.7.1 --format "{{json .Config.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.
|
||||||
|
|
||||||
[releases]: https://github.com/netbox-community/netbox-docker/releases
|
[releases]: https://github.com/netbox-community/netbox-docker/releases
|
||||||
|
|
||||||
## Rebuilding & Publishing images
|
## Rebuilding the Image
|
||||||
|
|
||||||
`./build.sh` can be used to rebuild the Docker 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.
|
||||||
|
|
||||||
### Publishing Docker Images
|
For more details on custom builds [consult our wiki][netbox-docker-wiki-build].
|
||||||
|
|
||||||
New Docker images are built and published every 24h on the [Docker Build Infrastructure][docker-build-infra].
|
[netbox-docker-wiki-build]: https://github.com/netbox-community/netbox-docker/wiki/Build
|
||||||
`DOCKER_HUB.md` contains more information about the build infrastructure.
|
|
||||||
|
|
||||||
[docker-build-infra]: https://hub.docker.com/r/netboxcommunity/netbox/builds/
|
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
To run the tests coming with Netbox, use the `docker-compose.yml` file as such:
|
We have a test script.
|
||||||
|
It runs Netbox's own unit tests and ensures that all initializers work:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose run netbox ./manage.py test
|
IMAGE=netboxcommunity/netbox:latest ./test.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
51
build-all.sh
51
build-all.sh
@ -1,51 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Builds all Docker images this project provides
|
|
||||||
# Arguments:
|
|
||||||
# BUILD: The release to build.
|
|
||||||
# Allowed: release, prerelease, branches, special
|
|
||||||
# Default: undefined
|
|
||||||
|
|
||||||
echo "▶️ $0 $*"
|
|
||||||
|
|
||||||
ALL_BUILDS=("release" "prerelease" "branches" "special")
|
|
||||||
BUILDS=("${BUILD:-"${ALL_BUILDS[@]}"}")
|
|
||||||
|
|
||||||
echo "⚙️ Configured builds: ${BUILDS[*]}"
|
|
||||||
|
|
||||||
if [ -n "${DEBUG}" ]; then
|
|
||||||
export DEBUG
|
|
||||||
fi
|
|
||||||
|
|
||||||
ERROR=0
|
|
||||||
|
|
||||||
for BUILD in "${BUILDS[@]}"; do
|
|
||||||
echo "🛠 Building '$BUILD' from '$DOCKERFILE'"
|
|
||||||
case $BUILD in
|
|
||||||
release)
|
|
||||||
# build the latest release
|
|
||||||
# shellcheck disable=SC2068
|
|
||||||
./build-latest.sh $@ || ERROR=1
|
|
||||||
;;
|
|
||||||
prerelease)
|
|
||||||
# build the latest pre-release
|
|
||||||
# shellcheck disable=SC2068
|
|
||||||
PRERELEASE=true ./build-latest.sh $@ || ERROR=1
|
|
||||||
;;
|
|
||||||
branches)
|
|
||||||
# build all branches
|
|
||||||
# shellcheck disable=SC2068
|
|
||||||
./build-branches.sh $@ || ERROR=1
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "🚨 Unrecognized build '$BUILD'."
|
|
||||||
|
|
||||||
if [ -z "$DEBUG" ]; then
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "⚠️ Would exit here with code '1', but DEBUG is enabled."
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
exit $ERROR
|
|
8
build-functions/docker-functions.sh
Normal file
8
build-functions/docker-functions.sh
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
push_image_to_registry() {
|
||||||
|
local target_tag=$1
|
||||||
|
echo "⏫ Pushing '${target_tag}'"
|
||||||
|
$DRY docker push "${target_tag}"
|
||||||
|
echo "✅ Finished pushing the Docker image '${target_tag}'."
|
||||||
|
}
|
82
build-functions/get-public-image-config.sh
Normal file
82
build-functions/get-public-image-config.sh
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Retrieves image configuration from public images in DockerHub
|
||||||
|
# Functions from https://gist.github.com/cirocosta/17ea17be7ac11594cb0f290b0a3ac0d1
|
||||||
|
# Optimised for our use case
|
||||||
|
|
||||||
|
get_image_label() {
|
||||||
|
local label=$1
|
||||||
|
local image=$2
|
||||||
|
local tag=$3
|
||||||
|
local token
|
||||||
|
token=$(_get_token "$image")
|
||||||
|
local digest
|
||||||
|
digest=$(_get_digest "$image" "$tag" "$token")
|
||||||
|
local retval="null"
|
||||||
|
if [ "$digest" != "null" ]; then
|
||||||
|
retval=$(_get_image_configuration "$image" "$token" "$digest" "$label")
|
||||||
|
fi
|
||||||
|
echo "$retval"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_image_layers() {
|
||||||
|
local image=$1
|
||||||
|
local tag=$2
|
||||||
|
local token
|
||||||
|
token=$(_get_token "$image")
|
||||||
|
_get_layers "$image" "$tag" "$token"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_image_last_layer() {
|
||||||
|
local image=$1
|
||||||
|
local tag=$2
|
||||||
|
local token
|
||||||
|
token=$(_get_token "$image")
|
||||||
|
local layers
|
||||||
|
mapfile -t layers < <(_get_layers "$image" "$tag" "$token")
|
||||||
|
echo "${layers[-1]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_image_configuration() {
|
||||||
|
local image=$1
|
||||||
|
local token=$2
|
||||||
|
local digest=$3
|
||||||
|
local label=$4
|
||||||
|
curl \
|
||||||
|
--silent \
|
||||||
|
--location \
|
||||||
|
--header "Authorization: Bearer $token" \
|
||||||
|
"https://registry-1.docker.io/v2/$image/blobs/$digest" \
|
||||||
|
| jq -r ".config.Labels.\"$label\""
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_token() {
|
||||||
|
local image=$1
|
||||||
|
curl \
|
||||||
|
--silent \
|
||||||
|
"https://auth.docker.io/token?scope=repository:$image:pull&service=registry.docker.io" \
|
||||||
|
| jq -r '.token'
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_digest() {
|
||||||
|
local image=$1
|
||||||
|
local tag=$2
|
||||||
|
local token=$3
|
||||||
|
curl \
|
||||||
|
--silent \
|
||||||
|
--header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
|
||||||
|
--header "Authorization: Bearer $token" \
|
||||||
|
"https://registry-1.docker.io/v2/$image/manifests/$tag" \
|
||||||
|
| jq -r '.config.digest'
|
||||||
|
}
|
||||||
|
|
||||||
|
_get_layers() {
|
||||||
|
local image=$1
|
||||||
|
local tag=$2
|
||||||
|
local token=$3
|
||||||
|
curl \
|
||||||
|
--silent \
|
||||||
|
--header "Accept: application/vnd.docker.distribution.manifest.v2+json" \
|
||||||
|
--header "Authorization: Bearer $token" \
|
||||||
|
"https://registry-1.docker.io/v2/$image/manifests/$tag" \
|
||||||
|
| jq -r '.layers[].digest'
|
||||||
|
}
|
@ -66,6 +66,10 @@ if [ "${PRERELEASE}" == "true" ]; 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
|
||||||
|
if [ -n "${GH_ACTION}" ]; then
|
||||||
|
echo "::set-output name=skipped::true"
|
||||||
|
fi
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
else
|
else
|
||||||
echo "⚠️ Would exit here with code '0', but DEBUG is enabled."
|
echo "⚠️ Would exit here with code '0', but DEBUG is enabled."
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Builds develop, develop-* and master branches
|
# Builds develop, develop-* and master branches of Netbox
|
||||||
|
|
||||||
echo "▶️ $0 $*"
|
echo "▶️ $0 $*"
|
||||||
|
|
||||||
@ -23,25 +23,17 @@ GITHUB_REPO="${GITHUB_REPO-$ORIGINAL_GITHUB_REPO}"
|
|||||||
URL_RELEASES="https://api.github.com/repos/${GITHUB_REPO}/branches?${GITHUB_OAUTH_PARAMS}"
|
URL_RELEASES="https://api.github.com/repos/${GITHUB_REPO}/branches?${GITHUB_OAUTH_PARAMS}"
|
||||||
|
|
||||||
# Composing the JQ commans to extract the most recent version number
|
# Composing the JQ commans to extract the most recent version number
|
||||||
JQ_BRANCHES='map(.name) | .[] | scan("^[^v].+") | match("^(master|develop).*") | .string'
|
JQ_NEXT='map(.name) | .[] | scan("^[^v].+") | match("^(develop-).*") | .string'
|
||||||
|
|
||||||
CURL="curl -sS"
|
CURL="curl -sS"
|
||||||
|
|
||||||
# Querying the Github API to fetch all branches
|
# Querying the Github API to fetch all branches
|
||||||
BRANCHES=$($CURL "${URL_RELEASES}" | jq -r "$JQ_BRANCHES")
|
NEXT=$($CURL "${URL_RELEASES}" | jq -r "$JQ_NEXT")
|
||||||
|
|
||||||
###
|
if [ -n "$NEXT" ]; then
|
||||||
# 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
|
# shellcheck disable=SC2068
|
||||||
./build.sh "${BRANCH}" $@ || ERROR=1
|
./build.sh "${NEXT}" $@
|
||||||
done
|
else
|
||||||
|
echo "No branch matching 'develop-*' found"
|
||||||
# returning whether an error occured
|
echo "::set-output name=skipped::true"
|
||||||
exit $ERROR
|
fi
|
124
build.sh
124
build.sh
@ -27,9 +27,9 @@ if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then
|
|||||||
echo " Default: undefined"
|
echo " Default: undefined"
|
||||||
echo " TAG The version part of the docker tag."
|
echo " TAG The version part of the docker tag."
|
||||||
echo " Default:"
|
echo " Default:"
|
||||||
echo " When \${BRANCH}=master: latest"
|
echo " When <branch>=master: latest"
|
||||||
echo " When \${BRANCH}=develop: snapshot"
|
echo " When <branch>=develop: snapshot"
|
||||||
echo " Else: same as \${BRANCH}"
|
echo " Else: same as <branch>"
|
||||||
echo " DOCKER_REGISTRY The Docker repository's registry (i.e. '\${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}'')"
|
echo " DOCKER_REGISTRY The Docker repository's registry (i.e. '\${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}'')"
|
||||||
echo " Used for tagging the image."
|
echo " Used for tagging the image."
|
||||||
echo " Default: docker.io"
|
echo " Default: docker.io"
|
||||||
@ -49,7 +49,7 @@ if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then
|
|||||||
echo " DOCKERFILE The name of Dockerfile to use."
|
echo " DOCKERFILE The name of Dockerfile to use."
|
||||||
echo " Default: Dockerfile"
|
echo " Default: Dockerfile"
|
||||||
echo " DOCKER_FROM The base image to use."
|
echo " DOCKER_FROM The base image to use."
|
||||||
echo " Default: Whatever is defined as default in the Dockerfile."
|
echo " Default: 'python:3.7-alpine'"
|
||||||
echo " DOCKER_TARGET A specific target to build."
|
echo " DOCKER_TARGET A specific target to build."
|
||||||
echo " It's currently not possible to pass multiple targets."
|
echo " It's currently not possible to pass multiple targets."
|
||||||
echo " Default: main ldap"
|
echo " Default: main ldap"
|
||||||
@ -63,6 +63,10 @@ if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then
|
|||||||
echo " Default: undefined"
|
echo " Default: undefined"
|
||||||
echo " DRY_RUN Prints all build statements instead of running them."
|
echo " DRY_RUN Prints all build statements instead of running them."
|
||||||
echo " Default: undefined"
|
echo " Default: undefined"
|
||||||
|
echo " GH_ACTION If defined, special 'echo' statements are enabled that set the"
|
||||||
|
echo " following environment variables in Github Actions:"
|
||||||
|
echo " - FINAL_DOCKER_TAG: The final value of the DOCKER_TAG env variable"
|
||||||
|
echo " Default: undefined"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Examples:"
|
echo "Examples:"
|
||||||
echo " ${0} master"
|
echo " ${0} master"
|
||||||
@ -92,7 +96,7 @@ if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
###
|
###
|
||||||
# Determining the build command to use
|
# Enabling dry-run mode
|
||||||
###
|
###
|
||||||
if [ -z "${DRY_RUN}" ]; then
|
if [ -z "${DRY_RUN}" ]; then
|
||||||
DRY=""
|
DRY=""
|
||||||
@ -102,21 +106,21 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
###
|
###
|
||||||
# variables for fetching the 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}"
|
||||||
BRANCH="${1}"
|
NETBOX_BRANCH="${1}"
|
||||||
URL="${URL-https://github.com/${SRC_ORG}/${SRC_REPO}.git}"
|
URL="${URL-https://github.com/${SRC_ORG}/${SRC_REPO}.git}"
|
||||||
NETBOX_PATH="${NETBOX_PATH-.netbox}"
|
NETBOX_PATH="${NETBOX_PATH-.netbox}"
|
||||||
|
|
||||||
###
|
###
|
||||||
# fetching the source
|
# Fetching the source
|
||||||
###
|
###
|
||||||
if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ] ; then
|
if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ] ; then
|
||||||
echo "🌐 Checking out '${BRANCH}' of netbox from the url '${URL}' into '${NETBOX_PATH}'"
|
echo "🌐 Checking out '${NETBOX_BRANCH}' of netbox from the url '${URL}' into '${NETBOX_PATH}'"
|
||||||
if [ ! -d "${NETBOX_PATH}" ]; then
|
if [ ! -d "${NETBOX_PATH}" ]; then
|
||||||
$DRY git clone -q --depth 10 -b "${BRANCH}" "${URL}" "${NETBOX_PATH}"
|
$DRY git clone -q --depth 10 -b "${NETBOX_BRANCH}" "${URL}" "${NETBOX_PATH}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
(
|
(
|
||||||
@ -127,7 +131,7 @@ if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ] ; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
$DRY git remote set-url origin "${URL}"
|
$DRY git remote set-url origin "${URL}"
|
||||||
$DRY git fetch -qp --depth 10 origin "${BRANCH}"
|
$DRY git fetch -qp --depth 10 origin "${NETBOX_BRANCH}"
|
||||||
$DRY git checkout -qf FETCH_HEAD
|
$DRY git checkout -qf FETCH_HEAD
|
||||||
$DRY git prune
|
$DRY git prune
|
||||||
)
|
)
|
||||||
@ -150,37 +154,44 @@ if [ ! -f "${DOCKERFILE}" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
###
|
###
|
||||||
# variables for labelling the docker image
|
# Determining the value for DOCKER_FROM
|
||||||
###
|
###
|
||||||
BUILD_DATE="$(date --utc --iso-8601=minutes)"
|
if [ -z "$DOCKER_FROM" ]; then
|
||||||
|
DOCKER_FROM="python:3.7-alpine"
|
||||||
|
fi
|
||||||
|
|
||||||
|
###
|
||||||
|
# Variables for labelling the docker image
|
||||||
|
###
|
||||||
|
BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M+00:00')"
|
||||||
|
|
||||||
if [ -d ".git" ]; then
|
if [ -d ".git" ]; then
|
||||||
GIT_REF="$(git rev-parse HEAD)"
|
GIT_REF="$(git rev-parse HEAD)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# read the project version from the `VERSION` file and trim it, see https://stackoverflow.com/a/3232433/172132
|
# Read the project version from the `VERSION` file and trim it, see https://stackoverflow.com/a/3232433/172132
|
||||||
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" ]; then
|
if [ -d "${NETBOX_PATH}/.git" ]; then
|
||||||
NETBOX_GIT_REF=$(cd ${NETBOX_PATH}; git rev-parse HEAD)
|
NETBOX_GIT_REF=$(cd "${NETBOX_PATH}"; git rev-parse HEAD)
|
||||||
NETBOX_GIT_BRANCH=$(cd ${NETBOX_PATH}; git rev-parse --abbrev-ref HEAD)
|
NETBOX_GIT_BRANCH=$(cd "${NETBOX_PATH}"; git rev-parse --abbrev-ref HEAD)
|
||||||
NETBOX_GIT_URL=$(cd ${NETBOX_PATH}; git remote get-url origin)
|
NETBOX_GIT_URL=$(cd "${NETBOX_PATH}"; git remote get-url origin)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
###
|
###
|
||||||
# variables for tagging the docker image
|
# Variables for tagging the docker image
|
||||||
###
|
###
|
||||||
DOCKER_REGISTRY="${DOCKER_REGISTRY-docker.io}"
|
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 "${BRANCH}" in
|
case "${NETBOX_BRANCH}" in
|
||||||
master)
|
master)
|
||||||
TAG="${TAG-latest}";;
|
TAG="${TAG-latest}";;
|
||||||
develop)
|
develop)
|
||||||
TAG="${TAG-snapshot}";;
|
TAG="${TAG-snapshot}";;
|
||||||
*)
|
*)
|
||||||
TAG="${TAG-$BRANCH}";;
|
TAG="${TAG-$NETBOX_BRANCH}";;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
###
|
###
|
||||||
@ -193,6 +204,7 @@ echo "🏭 Building the following targets:" "${DOCKER_TARGETS[@]}"
|
|||||||
###
|
###
|
||||||
# Build each target
|
# Build each target
|
||||||
###
|
###
|
||||||
|
export DOCKER_BUILDKIT=${DOCKER_BUILDKIT-1}
|
||||||
for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
|
for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
|
||||||
echo "🏗 Building the target '${DOCKER_TARGET}'"
|
echo "🏗 Building the target '${DOCKER_TARGET}'"
|
||||||
|
|
||||||
@ -203,20 +215,27 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
|
|||||||
if [ "${DOCKER_TARGET}" != "main" ]; then
|
if [ "${DOCKER_TARGET}" != "main" ]; then
|
||||||
TARGET_DOCKER_TAG="${TARGET_DOCKER_TAG}-${DOCKER_TARGET}"
|
TARGET_DOCKER_TAG="${TARGET_DOCKER_TAG}-${DOCKER_TARGET}"
|
||||||
fi
|
fi
|
||||||
|
if [ -n "${GH_ACTION}" ]; then
|
||||||
|
echo "::set-env name=FINAL_DOCKER_TAG::${TARGET_DOCKER_TAG}"
|
||||||
|
echo "::set-output name=skipped::false"
|
||||||
|
fi
|
||||||
|
|
||||||
###
|
###
|
||||||
# composing the additional DOCKER_SHORT_TAG,
|
# composing the additional DOCKER_SHORT_TAG,
|
||||||
# i.e. "v2.6.1" becomes "v2.6",
|
# i.e. "v2.6.1" becomes "v2.6",
|
||||||
# which is only relevant for version tags
|
# which is only relevant for version tags
|
||||||
|
# Also let "latest" follow the highest version
|
||||||
###
|
###
|
||||||
if [[ "${TAG}" =~ ^v([0-9]+)\.([0-9]+)\.[0-9]+$ ]]; then
|
if [[ "${TAG}" =~ ^v([0-9]+)\.([0-9]+)\.[0-9]+$ ]]; then
|
||||||
MAJOR=${BASH_REMATCH[1]}
|
MAJOR=${BASH_REMATCH[1]}
|
||||||
MINOR=${BASH_REMATCH[2]}
|
MINOR=${BASH_REMATCH[2]}
|
||||||
|
|
||||||
TARGET_DOCKER_SHORT_TAG="${DOCKER_SHORT_TAG-${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_REPO}:v${MAJOR}.${MINOR}}"
|
TARGET_DOCKER_SHORT_TAG="${DOCKER_SHORT_TAG-${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_REPO}:v${MAJOR}.${MINOR}}"
|
||||||
|
TARGET_DOCKER_LATEST_TAG="${DOCKER_REGISTRY}/${DOCKER_ORG}/${DOCKER_REPO}:latest"
|
||||||
|
|
||||||
if [ "${DOCKER_TARGET}" != "main" ]; then
|
if [ "${DOCKER_TARGET}" != "main" ]; then
|
||||||
TARGET_DOCKER_SHORT_TAG="${TARGET_DOCKER_SHORT_TAG}-${DOCKER_TARGET}"
|
TARGET_DOCKER_SHORT_TAG="${TARGET_DOCKER_SHORT_TAG}-${DOCKER_TARGET}"
|
||||||
|
TARGET_DOCKER_LATEST_TAG="${TARGET_DOCKER_LATEST_TAG}-${DOCKER_TARGET}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -224,6 +243,48 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
|
|||||||
# Proceeding to buils stage, except if `--push-only` is passed
|
# Proceeding to buils stage, except if `--push-only` is passed
|
||||||
###
|
###
|
||||||
if [ "${2}" != "--push-only" ] ; then
|
if [ "${2}" != "--push-only" ] ; then
|
||||||
|
###
|
||||||
|
# Checking if the build is necessary,
|
||||||
|
# meaning build only if one of those values changed:
|
||||||
|
# - Python base image digest (Label: PYTHON_BASE_DIGEST)
|
||||||
|
# - netbox git ref (Label: NETBOX_GIT_REF)
|
||||||
|
# - netbox-docker git ref (Label: org.label-schema.vcs-ref)
|
||||||
|
###
|
||||||
|
# Load information from registry (only for docker.io)
|
||||||
|
SHOULD_BUILD="false"
|
||||||
|
BUILD_REASON=""
|
||||||
|
if [ -z "${GH_ACTION}" ]; then
|
||||||
|
# Asuming non Github builds should always proceed
|
||||||
|
SHOULD_BUILD="true"
|
||||||
|
BUILD_REASON="${BUILD_REASON} interactive"
|
||||||
|
elif [ "$DOCKER_REGISTRY" = "docker.io" ]; then
|
||||||
|
source ./build-functions/get-public-image-config.sh
|
||||||
|
IFS=':' read -ra DOCKER_FROM_SPLIT <<< "${DOCKER_FROM}"
|
||||||
|
if ! [[ ${DOCKER_FROM_SPLIT[0]} =~ .*/.* ]]; then
|
||||||
|
# Need to use "library/..." for images the have no two part name
|
||||||
|
DOCKER_FROM_SPLIT[0]="library/${DOCKER_FROM_SPLIT[0]}"
|
||||||
|
fi
|
||||||
|
PYTHON_LAST_LAYER=$(get_image_last_layer "${DOCKER_FROM_SPLIT[0]}" "${DOCKER_FROM_SPLIT[1]}")
|
||||||
|
mapfile -t IMAGES_LAYERS_OLD < <(get_image_layers "${DOCKER_ORG}"/"${DOCKER_REPO}" "${TAG}")
|
||||||
|
NETBOX_GIT_REF_OLD=$(get_image_label NETBOX_GIT_REF "${DOCKER_ORG}"/"${DOCKER_REPO}" "${TAG}")
|
||||||
|
GIT_REF_OLD=$(get_image_label org.label-schema.vcs-ref "${DOCKER_ORG}"/"${DOCKER_REPO}" "${TAG}")
|
||||||
|
|
||||||
|
if ! printf '%s\n' "${IMAGES_LAYERS_OLD[@]}" | grep -q -P "^${PYTHON_LAST_LAYER}\$"; then
|
||||||
|
SHOULD_BUILD="true"
|
||||||
|
BUILD_REASON="${BUILD_REASON} python"
|
||||||
|
fi
|
||||||
|
if [ "${NETBOX_GIT_REF}" != "${NETBOX_GIT_REF_OLD}" ]; then
|
||||||
|
SHOULD_BUILD="true"
|
||||||
|
BUILD_REASON="${BUILD_REASON} netbox"
|
||||||
|
fi
|
||||||
|
if [ "${GIT_REF}" != "${GIT_REF_OLD}" ]; then
|
||||||
|
SHOULD_BUILD="true"
|
||||||
|
BUILD_REASON="${BUILD_REASON} netbox-docker"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
SHOULD_BUILD="true"
|
||||||
|
BUILD_REASON="${BUILD_REASON} no-check"
|
||||||
|
fi
|
||||||
###
|
###
|
||||||
# Composing all arguments for `docker build`
|
# Composing all arguments for `docker build`
|
||||||
###
|
###
|
||||||
@ -235,10 +296,10 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
|
|||||||
)
|
)
|
||||||
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
|
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
|
||||||
DOCKER_BUILD_ARGS+=( -t "${TARGET_DOCKER_SHORT_TAG}" )
|
DOCKER_BUILD_ARGS+=( -t "${TARGET_DOCKER_SHORT_TAG}" )
|
||||||
|
DOCKER_BUILD_ARGS+=( -t "${TARGET_DOCKER_LATEST_TAG}" )
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --label
|
# --label
|
||||||
if [ "${DOCKER_TARGET}" == "main" ]; then
|
|
||||||
DOCKER_BUILD_ARGS+=(
|
DOCKER_BUILD_ARGS+=(
|
||||||
--label "ORIGINAL_TAG=${TARGET_DOCKER_TAG}"
|
--label "ORIGINAL_TAG=${TARGET_DOCKER_TAG}"
|
||||||
|
|
||||||
@ -261,6 +322,9 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
|
|||||||
--label "NETBOX_GIT_URL=${NETBOX_GIT_URL}"
|
--label "NETBOX_GIT_URL=${NETBOX_GIT_URL}"
|
||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
|
if [ -n "${BUILD_REASON}" ]; then
|
||||||
|
BUILD_REASON=$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<< "$BUILD_REASON")
|
||||||
|
DOCKER_BUILD_ARGS+=( --label "BUILD_REASON=${BUILD_REASON}" )
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# --build-arg
|
# --build-arg
|
||||||
@ -280,23 +344,29 @@ for DOCKER_TARGET in "${DOCKER_TARGETS[@]}"; do
|
|||||||
###
|
###
|
||||||
# Building the docker image
|
# Building the docker image
|
||||||
###
|
###
|
||||||
|
if [ "${SHOULD_BUILD}" == "true" ]; then
|
||||||
echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG}'."
|
echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG}'."
|
||||||
|
echo " Build reason set to: ${BUILD_REASON}"
|
||||||
$DRY docker build "${DOCKER_BUILD_ARGS[@]}" .
|
$DRY docker build "${DOCKER_BUILD_ARGS[@]}" .
|
||||||
echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG}'"
|
echo "✅ Finished building the Docker images '${TARGET_DOCKER_TAG}'"
|
||||||
|
echo "🔎 Inspecting labels on '${TARGET_DOCKER_TAG}'"
|
||||||
|
$DRY docker inspect "${TARGET_DOCKER_TAG}" --format "{{json .Config.Labels}}"
|
||||||
|
else
|
||||||
|
echo "Build skipped because sources didn't change"
|
||||||
|
echo "::set-output name=skipped::true"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
###
|
###
|
||||||
# Pushing the docker images if either `--push` or `--push-only` are passed
|
# Pushing the docker images if either `--push` or `--push-only` are passed
|
||||||
###
|
###
|
||||||
if [ "${2}" == "--push" ] || [ "${2}" == "--push-only" ] ; then
|
if [ "${2}" == "--push" ] || [ "${2}" == "--push-only" ] ; then
|
||||||
echo "⏫ Pushing '${TARGET_DOCKER_TAG}"
|
source ./build-functions/docker-functions.sh
|
||||||
$DRY docker push "${TARGET_DOCKER_TAG}"
|
push_image_to_registry "${TARGET_DOCKER_TAG}"
|
||||||
echo "✅ Finished pushing the Docker image '${TARGET_DOCKER_TAG}'."
|
|
||||||
|
|
||||||
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
|
if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then
|
||||||
echo "⏫ Pushing '${TARGET_DOCKER_SHORT_TAG}'"
|
push_image_to_registry "${TARGET_DOCKER_SHORT_TAG}"
|
||||||
$DRY docker push "${TARGET_DOCKER_SHORT_TAG}"
|
push_image_to_registry "${TARGET_DOCKER_LATEST_TAG}"
|
||||||
echo "✅ Finished pushing the Docker image '${TARGET_DOCKER_SHORT_TAG}'."
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
@ -37,6 +37,8 @@ DATABASE = {
|
|||||||
# PostgreSQL password
|
# PostgreSQL password
|
||||||
'HOST': os.environ.get('DB_HOST', 'localhost'), # Database server
|
'HOST': os.environ.get('DB_HOST', 'localhost'), # Database server
|
||||||
'PORT': os.environ.get('DB_PORT', ''), # Database port (leave blank for default)
|
'PORT': os.environ.get('DB_PORT', ''), # Database port (leave blank for default)
|
||||||
|
'OPTIONS': {'sslmode': os.environ.get('DB_SSLMODE', 'prefer')},
|
||||||
|
# Database connection SSLMODE
|
||||||
'CONN_MAX_AGE': int(os.environ.get('DB_CONN_MAX_AGE', '300')),
|
'CONN_MAX_AGE': int(os.environ.get('DB_CONN_MAX_AGE', '300')),
|
||||||
# Database connection persistence
|
# Database connection persistence
|
||||||
}
|
}
|
||||||
@ -49,13 +51,30 @@ 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 database settings. The Redis database is used for caching and background processing such as webhooks
|
||||||
REDIS = {
|
REDIS = {
|
||||||
|
'tasks': {
|
||||||
'HOST': os.environ.get('REDIS_HOST', 'localhost'),
|
'HOST': os.environ.get('REDIS_HOST', 'localhost'),
|
||||||
'PORT': int(os.environ.get('REDIS_PORT', 6379)),
|
'PORT': int(os.environ.get('REDIS_PORT', 6379)),
|
||||||
'PASSWORD': os.environ.get('REDIS_PASSWORD', read_secret('redis_password')),
|
'PASSWORD': os.environ.get('REDIS_PASSWORD', read_secret('redis_password')),
|
||||||
'DATABASE': os.environ.get('REDIS_DATABASE', '0'),
|
'DATABASE': int(os.environ.get('REDIS_DATABASE', 0)),
|
||||||
'CACHE_DATABASE': os.environ.get('REDIS_CACHE_DATABASE', '1'),
|
'DEFAULT_TIMEOUT': int(os.environ.get('REDIS_TIMEOUT', 300)),
|
||||||
'DEFAULT_TIMEOUT': os.environ.get('REDIS_TIMEOUT', '300'),
|
|
||||||
'SSL': os.environ.get('REDIS_SSL', 'False').lower() == 'true',
|
'SSL': os.environ.get('REDIS_SSL', 'False').lower() == 'true',
|
||||||
|
},
|
||||||
|
'webhooks': { # legacy setting, can be removed after Netbox seizes support for it
|
||||||
|
'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',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
@ -108,6 +127,10 @@ EMAIL = {
|
|||||||
'PASSWORD': os.environ.get('EMAIL_PASSWORD', read_secret('email_password')),
|
'PASSWORD': os.environ.get('EMAIL_PASSWORD', read_secret('email_password')),
|
||||||
'TIMEOUT': int(os.environ.get('EMAIL_TIMEOUT', 10)), # seconds
|
'TIMEOUT': int(os.environ.get('EMAIL_TIMEOUT', 10)), # seconds
|
||||||
'FROM_EMAIL': os.environ.get('EMAIL_FROM', ''),
|
'FROM_EMAIL': os.environ.get('EMAIL_FROM', ''),
|
||||||
|
'USE_SSL': os.environ.get('EMAIL_USE_SSL', 'False').lower() == 'true',
|
||||||
|
'USE_TLS': os.environ.get('EMAIL_USE_TLS', 'False').lower() == 'true',
|
||||||
|
'SSL_CERTFILE': os.environ.get('EMAIL_SSL_CERTFILE', ''),
|
||||||
|
'SSL_KEYFILE': os.environ.get('EMAIL_SSL_KEYFILE', ''),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Enforcement of unique IP space can be toggled on a per-VRF basis.
|
# Enforcement of unique IP space can be toggled on a per-VRF basis.
|
||||||
@ -127,6 +150,10 @@ LOGGING = {}
|
|||||||
# 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 = os.environ.get('LOGIN_REQUIRED', 'False').lower() == 'true'
|
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 = os.environ.get('LOGIN_TIMEOUT', None)
|
||||||
|
|
||||||
# Setting this to True will display a "maintenance mode" banner at the top of every page.
|
# Setting this to True will display a "maintenance mode" banner at the top of every page.
|
||||||
MAINTENANCE_MODE = os.environ.get('MAINTENANCE_MODE', 'False').lower() == 'true'
|
MAINTENANCE_MODE = os.environ.get('MAINTENANCE_MODE', 'False').lower() == 'true'
|
||||||
|
|
||||||
@ -159,6 +186,15 @@ PAGINATE_COUNT = int(os.environ.get('PAGINATE_COUNT', 50))
|
|||||||
# 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.
|
||||||
PREFER_IPV4 = os.environ.get('PREFER_IPV4', 'False').lower() == 'true'
|
PREFER_IPV4 = os.environ.get('PREFER_IPV4', 'False').lower() == 'true'
|
||||||
|
|
||||||
|
# This determines how often the GitHub API is called to check the latest release of NetBox in seconds. Must be at least 1 hour.
|
||||||
|
RELEASE_CHECK_TIMEOUT = os.environ.get('RELEASE_CHECK_TIMEOUT', 24 * 3600)
|
||||||
|
|
||||||
|
# This repository is used to check whether there is a new release of NetBox available. Set to None to disable the
|
||||||
|
# version check or use the URL below to check for release in the official NetBox repository.
|
||||||
|
# https://api.github.com/repos/netbox-community/netbox/releases
|
||||||
|
RELEASE_CHECK_URL = os.environ.get('RELEASE_CHECK_URL', None)
|
||||||
|
|
||||||
# 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 = os.environ.get('REPORTS_ROOT', '/etc/netbox/reports')
|
REPORTS_ROOT = os.environ.get('REPORTS_ROOT', '/etc/netbox/reports')
|
||||||
@ -170,10 +206,6 @@ SCRIPTS_ROOT = os.environ.get('SCRIPTS_ROOT', '/etc/netbox/scripts')
|
|||||||
# Time zone (default: UTC)
|
# Time zone (default: UTC)
|
||||||
TIME_ZONE = os.environ.get('TIME_ZONE', 'UTC')
|
TIME_ZONE = os.environ.get('TIME_ZONE', 'UTC')
|
||||||
|
|
||||||
# The Webhook event backend is disabled by default. Set this to True to enable it. Note that this requires a Redis
|
|
||||||
# database be configured and accessible by NetBox (see `REDIS` below).
|
|
||||||
WEBHOOKS_ENABLED = os.environ.get('WEBHOOKS_ENABLED', 'False').lower() == 'true'
|
|
||||||
|
|
||||||
# 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/dev/ref/templates/builtins/#date
|
# https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
|
||||||
DATE_FORMAT = os.environ.get('DATE_FORMAT', 'N j, Y')
|
DATE_FORMAT = os.environ.get('DATE_FORMAT', 'N j, Y')
|
||||||
|
@ -37,6 +37,9 @@ AUTH_LDAP_BIND_PASSWORD = os.environ.get('AUTH_LDAP_BIND_PASSWORD', read_secret(
|
|||||||
# Set a string template that describes any user’s distinguished name based on the username.
|
# Set a string template that describes any user’s distinguished name based on the username.
|
||||||
AUTH_LDAP_USER_DN_TEMPLATE = os.environ.get('AUTH_LDAP_USER_DN_TEMPLATE', None)
|
AUTH_LDAP_USER_DN_TEMPLATE = os.environ.get('AUTH_LDAP_USER_DN_TEMPLATE', None)
|
||||||
|
|
||||||
|
# Enable STARTTLS for ldap authentication.
|
||||||
|
AUTH_LDAP_START_TLS = os.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.
|
# 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:
|
# 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.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
||||||
@ -70,8 +73,7 @@ AUTH_LDAP_USER_FLAGS_BY_GROUP = {
|
|||||||
AUTH_LDAP_FIND_GROUP_PERMS = os.environ.get('AUTH_LDAP_FIND_GROUP_PERMS', 'True').lower() == 'true'
|
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
|
# 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_CACHE_TIMEOUT = int(os.environ.get('AUTH_LDAP_CACHE_TIMEOUT', 3600))
|
||||||
AUTH_LDAP_GROUP_CACHE_TIMEOUT = int(os.environ.get('AUTH_LDAP_GROUP_CACHE_TIMEOUT', 3600))
|
|
||||||
|
|
||||||
# Populate the Django user from the LDAP directory.
|
# Populate the Django user from the LDAP directory.
|
||||||
AUTH_LDAP_USER_ATTR_MAP = {
|
AUTH_LDAP_USER_ATTR_MAP = {
|
||||||
|
53
docker-compose.test.yml
Normal file
53
docker-compose.test.yml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
version: '3.4'
|
||||||
|
services:
|
||||||
|
netbox:
|
||||||
|
image: ${IMAGE-netboxcommunity/netbox:latest}
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
- redis-cache
|
||||||
|
env_file: env/netbox.env
|
||||||
|
user: '101'
|
||||||
|
volumes:
|
||||||
|
- ./startup_scripts:/opt/netbox/startup_scripts:z,ro
|
||||||
|
- ./${INITIALIZERS_DIR-initializers}:/opt/netbox/initializers:z,ro
|
||||||
|
- ./configuration:/etc/netbox/config:z,ro
|
||||||
|
- ./reports:/etc/netbox/reports: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
|
||||||
|
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:
|
||||||
|
image: postgres:11-alpine
|
||||||
|
env_file: env/postgres.env
|
||||||
|
redis:
|
||||||
|
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:5-alpine
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c # this is to evaluate the $REDIS_PASSWORD from the env
|
||||||
|
- redis-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
|
||||||
|
env_file: env/redis-cache.env
|
||||||
|
volumes:
|
||||||
|
netbox-static-files:
|
||||||
|
driver: local
|
||||||
|
netbox-nginx-config:
|
||||||
|
driver: local
|
||||||
|
netbox-media-files:
|
||||||
|
driver: local
|
@ -5,8 +5,10 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
|
- redis-cache
|
||||||
- netbox-worker
|
- netbox-worker
|
||||||
env_file: env/netbox.env
|
env_file: env/netbox.env
|
||||||
|
user: '101'
|
||||||
volumes:
|
volumes:
|
||||||
- ./startup_scripts:/opt/netbox/startup_scripts:z,ro
|
- ./startup_scripts:/opt/netbox/startup_scripts:z,ro
|
||||||
- ./initializers:/opt/netbox/initializers:z,ro
|
- ./initializers:/opt/netbox/initializers:z,ro
|
||||||
@ -36,12 +38,12 @@ services:
|
|||||||
- netbox-static-files:/opt/netbox/netbox/static:ro
|
- netbox-static-files:/opt/netbox/netbox/static:ro
|
||||||
- netbox-nginx-config:/etc/netbox-nginx/:ro
|
- netbox-nginx-config:/etc/netbox-nginx/:ro
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:10.4-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:
|
||||||
image: redis:4-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
|
||||||
@ -49,6 +51,13 @@ services:
|
|||||||
env_file: env/redis.env
|
env_file: env/redis.env
|
||||||
volumes:
|
volumes:
|
||||||
- netbox-redis-data:/data
|
- netbox-redis-data:/data
|
||||||
|
redis-cache:
|
||||||
|
image: redis:5-alpine
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c # this is to evaluate the $REDIS_PASSWORD from the env
|
||||||
|
- redis-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose
|
||||||
|
env_file: env/redis-cache.env
|
||||||
volumes:
|
volumes:
|
||||||
netbox-static-files:
|
netbox-static-files:
|
||||||
driver: local
|
driver: local
|
||||||
@ -56,8 +65,6 @@ volumes:
|
|||||||
driver: local
|
driver: local
|
||||||
netbox-media-files:
|
netbox-media-files:
|
||||||
driver: local
|
driver: local
|
||||||
netbox-report-files:
|
|
||||||
driver: local
|
|
||||||
netbox-postgres-data:
|
netbox-postgres-data:
|
||||||
driver: local
|
driver: local
|
||||||
netbox-redis-data:
|
netbox-redis-data:
|
||||||
|
@ -1,12 +1,27 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
# Runs on every start of the Netbox Docker container
|
||||||
|
|
||||||
|
# Stop when an error occures
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
# wait shortly and then run db migrations (retry on error)
|
# Allows Netbox to be run as non-root users
|
||||||
while ! ./manage.py migrate 2>&1; do
|
umask 002
|
||||||
echo "⏳ Waiting on DB..."
|
|
||||||
sleep 3
|
|
||||||
done
|
|
||||||
|
|
||||||
|
# Try to connect to the DB
|
||||||
|
DB_WAIT_TIMEOUT=${DB_WAIT_TIMEOUT-3}
|
||||||
|
MAX_DB_WAIT_TIME=${MAX_DB_WAIT_TIME-30}
|
||||||
|
CUR_DB_WAIT_TIME=0
|
||||||
|
while ! ./manage.py migrate 2>&1 && [ "${CUR_DB_WAIT_TIME}" -lt "${MAX_DB_WAIT_TIME}" ]; do
|
||||||
|
echo "⏳ Waiting on DB... (${CUR_DB_WAIT_TIME}s / ${MAX_DB_WAIT_TIME}s)"
|
||||||
|
sleep "${DB_WAIT_TIMEOUT}"
|
||||||
|
CUR_DB_WAIT_TIME=$(( CUR_DB_WAIT_TIME + DB_WAIT_TIMEOUT ))
|
||||||
|
done
|
||||||
|
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."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create Superuser if required
|
||||||
if [ "$SKIP_SUPERUSER" == "true" ]; then
|
if [ "$SKIP_SUPERUSER" == "true" ]; then
|
||||||
echo "↩️ Skip creating the superuser"
|
echo "↩️ Skip creating the superuser"
|
||||||
else
|
else
|
||||||
@ -42,21 +57,19 @@ 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
|
if [ "$SKIP_STARTUP_SCRIPTS" == "true" ]; then
|
||||||
echo "↩️ Skipping startup scripts"
|
echo "↩️ Skipping startup scripts"
|
||||||
else
|
else
|
||||||
for script in /opt/netbox/startup_scripts/*.py; do
|
echo "import runpy; runpy.run_path('../startup_scripts')" | ./manage.py shell --interface python
|
||||||
echo "⚙️ Executing '$script'"
|
|
||||||
./manage.py shell --interface python < "${script}"
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# copy static files
|
# Copy static files
|
||||||
./manage.py collectstatic --no-input
|
./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)
|
||||||
#
|
#
|
||||||
# shellcheck disable=SC2068
|
# shellcheck disable=SC2068
|
||||||
|
@ -29,7 +29,6 @@ http {
|
|||||||
proxy_pass http://netbox:8001;
|
proxy_pass http://netbox:8001;
|
||||||
proxy_set_header X-Forwarded-Host $http_host;
|
proxy_set_header X-Forwarded-Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
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"';
|
add_header P3P 'CP="ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV"';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
env/netbox.env
vendored
11
env/netbox.env
vendored
@ -9,6 +9,11 @@ EMAIL_USERNAME=netbox
|
|||||||
EMAIL_PASSWORD=
|
EMAIL_PASSWORD=
|
||||||
EMAIL_TIMEOUT=5
|
EMAIL_TIMEOUT=5
|
||||||
EMAIL_FROM=netbox@bar.com
|
EMAIL_FROM=netbox@bar.com
|
||||||
|
# EMAIL_USE_SSL and EMAIL_USE_TLS are mutually exclusive, i.e. they can't both be `true`!
|
||||||
|
EMAIL_USE_SSL=false
|
||||||
|
EMAIL_USE_TLS=false
|
||||||
|
EMAIL_SSL_CERTFILE=
|
||||||
|
EMAIL_SSL_KEYFILE=
|
||||||
MEDIA_ROOT=/opt/netbox/netbox/media
|
MEDIA_ROOT=/opt/netbox/netbox/media
|
||||||
NAPALM_USERNAME=
|
NAPALM_USERNAME=
|
||||||
NAPALM_PASSWORD=
|
NAPALM_PASSWORD=
|
||||||
@ -17,8 +22,11 @@ MAX_PAGE_SIZE=1000
|
|||||||
REDIS_HOST=redis
|
REDIS_HOST=redis
|
||||||
REDIS_PASSWORD=H733Kdjndks81
|
REDIS_PASSWORD=H733Kdjndks81
|
||||||
REDIS_DATABASE=0
|
REDIS_DATABASE=0
|
||||||
REDIS_CACHE_DATABASE=1
|
|
||||||
REDIS_SSL=false
|
REDIS_SSL=false
|
||||||
|
REDIS_CACHE_HOST=redis-cache
|
||||||
|
REDIS_CACHE_PASSWORD=t4Ph722qJ5QHeQ1qfu36
|
||||||
|
REDIS_CACHE_DATABASE=1
|
||||||
|
REDIS_CACHE_SSL=false
|
||||||
SECRET_KEY=r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj
|
SECRET_KEY=r8OwDznj!!dci#P9ghmRfdu1Ysxm0AiPeDCQhKE+N_rClfWNj
|
||||||
SKIP_STARTUP_SCRIPTS=false
|
SKIP_STARTUP_SCRIPTS=false
|
||||||
SKIP_SUPERUSER=false
|
SKIP_SUPERUSER=false
|
||||||
@ -27,3 +35,4 @@ SUPERUSER_EMAIL=admin@example.com
|
|||||||
SUPERUSER_PASSWORD=admin
|
SUPERUSER_PASSWORD=admin
|
||||||
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
|
SUPERUSER_API_TOKEN=0123456789abcdef0123456789abcdef01234567
|
||||||
WEBHOOKS_ENABLED=true
|
WEBHOOKS_ENABLED=true
|
||||||
|
RELEASE_CHECK_URL=https://api.github.com/repos/netbox-community/netbox/releases
|
||||||
|
1
env/redis-cache.env
vendored
Normal file
1
env/redis-cache.env
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
REDIS_PASSWORD=t4Ph722qJ5QHeQ1qfu36
|
@ -1,6 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
. hooks/common
|
|
||||||
|
|
||||||
# shellcheck disable=SC2119
|
|
||||||
run_build
|
|
60
hooks/common
60
hooks/common
@ -1,60 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
ensure_jq() {
|
|
||||||
if [ ! -x "$(command -v jq)" ]; then
|
|
||||||
if [ -x "$(command -v apt-get)" ]; then
|
|
||||||
echo "🛠🛠🛠 Installing 'jq' via 'apt-get'"
|
|
||||||
apt-get update && apt-get install -y jq
|
|
||||||
else
|
|
||||||
echo "⚠️⚠️⚠️ apt-get not found, unable to automatically install 'jq'."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Passes args to the scripts
|
|
||||||
run_build() {
|
|
||||||
echo "🐳🐳🐳 Building '${BUILD}' images"
|
|
||||||
case $BUILD in
|
|
||||||
release)
|
|
||||||
# build the latest release
|
|
||||||
# shellcheck disable=SC2068
|
|
||||||
./build-latest.sh $@
|
|
||||||
;;
|
|
||||||
prerelease)
|
|
||||||
# build the latest pre-release
|
|
||||||
# shellcheck disable=SC2068
|
|
||||||
PRERELEASE=true ./build-latest.sh $@
|
|
||||||
;;
|
|
||||||
branches)
|
|
||||||
# build all branches
|
|
||||||
# shellcheck disable=SC2068
|
|
||||||
./build-branches.sh $@
|
|
||||||
;;
|
|
||||||
this) # Pull Requests
|
|
||||||
# only build the 'master' branch
|
|
||||||
# (resulting in the 'latest' docker tag)
|
|
||||||
# and the 'main' target.
|
|
||||||
DOCKER_TARGET=main ./build.sh master
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "🚨 Unrecognized build '$BUILD'."
|
|
||||||
|
|
||||||
if [ -z "$DEBUG" ]; then
|
|
||||||
exit 1
|
|
||||||
else
|
|
||||||
echo "⚠️ Would exit here with code '1', but DEBUG is enabled."
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "🤖🤖🤖 Preparing build"
|
|
||||||
export DOCKER_ORG="index.docker.io/netboxcommunity"
|
|
||||||
export DOCKER_REPO=netbox
|
|
||||||
export DOCKERHUB_REPO=netboxcommunity/netbox
|
|
||||||
# shellcheck disable=SC2153
|
|
||||||
export BUILD="${DOCKER_TAG}"
|
|
||||||
|
|
||||||
unset DOCKER_TAG
|
|
||||||
|
|
||||||
ensure_jq
|
|
14
hooks/push
14
hooks/push
@ -1,14 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
. hooks/common
|
|
||||||
|
|
||||||
if [ "${SOURCE_BRANCH}" == "master" ] || [ "${DEBUG}" == "true" ]; then
|
|
||||||
if [ "${SOURCE_BRANCH}" != "master" ]; then
|
|
||||||
echo "⚠️⚠️⚠️ Would exit, but DEBUG is '${DEBUG}'".
|
|
||||||
fi
|
|
||||||
|
|
||||||
run_build --push-only
|
|
||||||
else
|
|
||||||
echo "⚠️⚠️⚠️ Only pushing on 'main' branch, but current branch is '${SOURCE_BRANCH}'"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
22
hooks/test
22
hooks/test
@ -1,22 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
. hooks/common
|
|
||||||
|
|
||||||
run_test() {
|
|
||||||
echo "🐳🐳🐳 Testing '${1}'"
|
|
||||||
VERSION="${1}" docker-compose run netbox ./manage.py test
|
|
||||||
docker-compose down -v
|
|
||||||
echo "🐳🐳🐳 Done testing '${1}'"
|
|
||||||
}
|
|
||||||
|
|
||||||
# test on builds of 'branches'
|
|
||||||
if [ "${BUILD}" == "branches" ] \
|
|
||||||
|| [ "${DEBUG}" == "true" ]; then
|
|
||||||
run_test latest
|
|
||||||
run_test snapshot
|
|
||||||
# test on bulds of 'this' (i.e. pull request)
|
|
||||||
elif [ "${BUILD}" == "this" ]; then
|
|
||||||
run_test latest
|
|
||||||
else
|
|
||||||
echo "🐳🐳🐳 No tests are implemented for build '${BUILD}'."
|
|
||||||
fi
|
|
@ -1,3 +1,18 @@
|
|||||||
|
## Possible Choices:
|
||||||
|
## type:
|
||||||
|
## - text
|
||||||
|
## - integer
|
||||||
|
## - boolean
|
||||||
|
## - date
|
||||||
|
## - url
|
||||||
|
## - select
|
||||||
|
## filter_logic:
|
||||||
|
## - disabled
|
||||||
|
## - loose
|
||||||
|
## - exact
|
||||||
|
##
|
||||||
|
## Examples:
|
||||||
|
|
||||||
# text_field:
|
# text_field:
|
||||||
# type: text
|
# type: text
|
||||||
# label: Custom Text
|
# label: Custom Text
|
||||||
@ -22,8 +37,8 @@
|
|||||||
# weight: 10
|
# weight: 10
|
||||||
# on_objects:
|
# on_objects:
|
||||||
# - tenancy.models.Tenant
|
# - tenancy.models.Tenant
|
||||||
# selection_field:
|
# select_field:
|
||||||
# type: selection
|
# type: select
|
||||||
# label: Choose between items
|
# label: Choose between items
|
||||||
# required: false
|
# required: false
|
||||||
# filter_logic: exact
|
# filter_logic: exact
|
||||||
@ -41,8 +56,8 @@
|
|||||||
# weight: 50
|
# weight: 50
|
||||||
# - value: Fourth Item
|
# - value: Fourth Item
|
||||||
# weight: 40
|
# weight: 40
|
||||||
# selection_field_auto_weight:
|
# select_field_auto_weight:
|
||||||
# type: selection
|
# type: select
|
||||||
# label: Choose between items
|
# label: Choose between items
|
||||||
# required: false
|
# required: false
|
||||||
# filter_logic: loose
|
# filter_logic: loose
|
||||||
|
@ -1,8 +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
|
# - device: server01
|
||||||
# enabled: true
|
# enabled: true
|
||||||
# type: Virtual
|
# type: virtual
|
||||||
# name: to-server02
|
# name: to-server02
|
||||||
# - device: server02
|
# - device: server02
|
||||||
# enabled: true
|
# enabled: true
|
||||||
# type: Virtual
|
# type: virtual
|
||||||
# name: to-server01
|
# name: to-server01
|
||||||
|
@ -1,9 +1,24 @@
|
|||||||
|
## Possible Choices:
|
||||||
|
## face:
|
||||||
|
## - front
|
||||||
|
## - rear
|
||||||
|
## status:
|
||||||
|
## - offline
|
||||||
|
## - active
|
||||||
|
## - planned
|
||||||
|
## - staged
|
||||||
|
## - failed
|
||||||
|
## - inventory
|
||||||
|
## - decommissioning
|
||||||
|
##
|
||||||
|
## Examples:
|
||||||
|
|
||||||
# - name: server01
|
# - name: server01
|
||||||
# device_role: server
|
# device_role: server
|
||||||
# device_type: Other
|
# device_type: Other
|
||||||
# site: AMS 1
|
# site: AMS 1
|
||||||
# rack: rack-01
|
# rack: rack-01
|
||||||
# face: Front
|
# face: front
|
||||||
# position: 1
|
# position: 1
|
||||||
# custom_fields:
|
# custom_fields:
|
||||||
# text_field: Description
|
# text_field: Description
|
||||||
@ -12,7 +27,7 @@
|
|||||||
# device_type: Other
|
# device_type: Other
|
||||||
# site: AMS 2
|
# site: AMS 2
|
||||||
# rack: rack-02
|
# rack: rack-02
|
||||||
# face: Front
|
# face: front
|
||||||
# position: 2
|
# position: 2
|
||||||
# custom_fields:
|
# custom_fields:
|
||||||
# text_field: Description
|
# text_field: Description
|
||||||
@ -21,7 +36,7 @@
|
|||||||
# device_type: Other
|
# device_type: Other
|
||||||
# site: SING 1
|
# site: SING 1
|
||||||
# rack: rack-03
|
# rack: rack-03
|
||||||
# face: Front
|
# face: front
|
||||||
# position: 3
|
# position: 3
|
||||||
# custom_fields:
|
# custom_fields:
|
||||||
# text_field: Description
|
# text_field: Description
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
## To list all permissions, run:
|
||||||
|
##
|
||||||
|
## docker-compose run --rm --entrypoint /bin/bash netbox
|
||||||
|
## $ ./manage.py migrate
|
||||||
|
## $ ./manage.py shell
|
||||||
|
## > from django.contrib.auth.models import Permission
|
||||||
|
## > print('\n'.join([p.codename for p in Permission.objects.all()]))
|
||||||
|
##
|
||||||
|
## Permission lists support wildcards. See the examples below.
|
||||||
|
##
|
||||||
|
## Examples:
|
||||||
|
|
||||||
# applications:
|
# applications:
|
||||||
# users:
|
# users:
|
||||||
# - technical_user
|
# - technical_user
|
||||||
@ -8,9 +20,16 @@
|
|||||||
# users:
|
# users:
|
||||||
# - writer
|
# - writer
|
||||||
# permissions:
|
# permissions:
|
||||||
# - add_device
|
|
||||||
# - change_device
|
|
||||||
# - delete_device
|
# - delete_device
|
||||||
# - add_virtualmachine
|
|
||||||
# - change_virtualmachine
|
|
||||||
# - delete_virtualmachine
|
# - delete_virtualmachine
|
||||||
|
# - add_*
|
||||||
|
# - change_*
|
||||||
|
# vm_managers:
|
||||||
|
# permissions:
|
||||||
|
# - '*_virtualmachine'
|
||||||
|
# device_managers:
|
||||||
|
# permissions:
|
||||||
|
# - '*device*'
|
||||||
|
# creators:
|
||||||
|
# permissions:
|
||||||
|
# - add_*
|
||||||
|
@ -1,26 +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
|
# - address: 10.1.1.1/24
|
||||||
# device: server01
|
# device: server01
|
||||||
# interface: to-server02
|
# interface: to-server02
|
||||||
# status: Active
|
# status: active
|
||||||
# vrf: vrf1
|
# vrf: vrf1
|
||||||
# - address: 2001:db8:a000:1::1/64
|
# - address: 2001:db8:a000:1::1/64
|
||||||
# device: server01
|
# device: server01
|
||||||
# interface: to-server02
|
# interface: to-server02
|
||||||
# status: Active
|
# status: active
|
||||||
# vrf: vrf1
|
# vrf: vrf1
|
||||||
# - address: 10.1.1.2/24
|
# - address: 10.1.1.2/24
|
||||||
# device: server02
|
# device: server02
|
||||||
# interface: to-server01
|
# interface: to-server01
|
||||||
# status: Active
|
# status: active
|
||||||
# - address: 2001:db8:a000:1::2/64
|
# - address: 2001:db8:a000:1::2/64
|
||||||
# device: server02
|
# device: server02
|
||||||
# interface: to-server01
|
# interface: to-server01
|
||||||
# status: Active
|
# status: active
|
||||||
# - address: 10.1.1.10/24
|
# - address: 10.1.1.10/24
|
||||||
# description: reserved IP
|
# description: reserved IP
|
||||||
# status: Reserved
|
# status: reserved
|
||||||
# tenant: tenant1
|
# tenant: tenant1
|
||||||
# - address: 2001:db8:a000:1::10/64
|
# - address: 2001:db8:a000:1::10/64
|
||||||
# description: reserved IP
|
# description: reserved IP
|
||||||
# status: Reserved
|
# status: reserved
|
||||||
# tenant: tenant1
|
# tenant: tenant1
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
|
## Possible Choices:
|
||||||
|
## status:
|
||||||
|
## - container
|
||||||
|
## - active
|
||||||
|
## - reserved
|
||||||
|
## - deprecated
|
||||||
|
##
|
||||||
|
## Examples:
|
||||||
|
|
||||||
# - description: prefix1
|
# - description: prefix1
|
||||||
# prefix: 10.1.1.0/24
|
# prefix: 10.1.1.0/24
|
||||||
# site: AMS 1
|
# site: AMS 1
|
||||||
# status: Active
|
# status: active
|
||||||
# tenant: tenant1
|
# tenant: tenant1
|
||||||
# vlan: vlan1
|
# vlan: vlan1
|
||||||
# - description: prefix2
|
# - description: prefix2
|
||||||
# prefix: 10.1.2.0/24
|
# prefix: 10.1.2.0/24
|
||||||
# site: AMS 2
|
# site: AMS 2
|
||||||
# status: Active
|
# status: active
|
||||||
# tenant: tenant2
|
# tenant: tenant2
|
||||||
# vlan: vlan2
|
# vlan: vlan2
|
||||||
# is_pool: true
|
# is_pool: true
|
||||||
@ -15,6 +24,6 @@
|
|||||||
# - description: ipv6 prefix1
|
# - description: ipv6 prefix1
|
||||||
# prefix: 2001:db8:a000:1::/64
|
# prefix: 2001:db8:a000:1::/64
|
||||||
# site: AMS 2
|
# site: AMS 2
|
||||||
# status: Active
|
# status: active
|
||||||
# tenant: tenant2
|
# tenant: tenant2
|
||||||
# vlan: vlan2
|
# vlan: vlan2
|
||||||
|
3
initializers/rack_groups.yml
Normal file
3
initializers/rack_groups.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# - name: cage 101
|
||||||
|
# slug: cage-101
|
||||||
|
# site: SING 1
|
@ -1,24 +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
|
# - site: AMS 1
|
||||||
# name: rack-01
|
# name: rack-01
|
||||||
# role: Role 1
|
# role: Role 1
|
||||||
# type: 4-post cabinet
|
# type: 4-post-cabinet
|
||||||
# width: 19 inches
|
# width: 19
|
||||||
# u_height: 47
|
# u_height: 47
|
||||||
# custom_fields:
|
# custom_fields:
|
||||||
# text_field: Description
|
# text_field: Description
|
||||||
# - site: AMS 2
|
# - site: AMS 2
|
||||||
# name: rack-02
|
# name: rack-02
|
||||||
# role: Role 2
|
# role: Role 2
|
||||||
# type: 4-post cabinet
|
# type: 4-post-cabinet
|
||||||
# width: 19 inches
|
# width: 19
|
||||||
# u_height: 47
|
# u_height: 47
|
||||||
# custom_fields:
|
# custom_fields:
|
||||||
# text_field: Description
|
# text_field: Description
|
||||||
# - site: SING 1
|
# - site: SING 1
|
||||||
# name: rack-03
|
# name: rack-03
|
||||||
|
# group: cage 101
|
||||||
# role: Role 3
|
# role: Role 3
|
||||||
# type: 4-post cabinet
|
# type: 4-post-cabinet
|
||||||
# width: 19 inches
|
# width: 19
|
||||||
# u_height: 47
|
# u_height: 47
|
||||||
# custom_fields:
|
# custom_fields:
|
||||||
# text_field: Description
|
# text_field: Description
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
|
## To list all permissions, run:
|
||||||
|
##
|
||||||
|
## docker-compose run --rm --entrypoint /bin/bash netbox
|
||||||
|
## $ ./manage.py migrate
|
||||||
|
## $ ./manage.py shell
|
||||||
|
## > from django.contrib.auth.models import Permission
|
||||||
|
## > print('\n'.join([p.codename for p in Permission.objects.all()]))
|
||||||
|
##
|
||||||
|
## Permission lists support wildcards. See the examples below.
|
||||||
|
##
|
||||||
|
## Examples:
|
||||||
|
|
||||||
# technical_user:
|
# technical_user:
|
||||||
# api_token: 0123456789technicaluser789abcdef01234567 # must be looooong!
|
# api_token: 0123456789technicaluser789abcdef01234567 # must be looooong!
|
||||||
# reader:
|
# reader:
|
||||||
@ -5,9 +17,7 @@
|
|||||||
# writer:
|
# writer:
|
||||||
# password: writer
|
# password: writer
|
||||||
# permissions:
|
# permissions:
|
||||||
# - add_device
|
|
||||||
# - change_device
|
|
||||||
# - delete_device
|
# - delete_device
|
||||||
# - add_virtualmachine
|
|
||||||
# - change_virtualmachine
|
|
||||||
# - delete_virtualmachine
|
# - delete_virtualmachine
|
||||||
|
# - add_*
|
||||||
|
# - change_*
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
|
## Possible Choices:
|
||||||
|
## status:
|
||||||
|
## - active
|
||||||
|
## - offline
|
||||||
|
## - staged
|
||||||
|
##
|
||||||
|
## Examples:
|
||||||
|
|
||||||
# - cluster: cluster1
|
# - cluster: cluster1
|
||||||
# comments: VM1
|
# comments: VM1
|
||||||
# disk: 200
|
# disk: 200
|
||||||
# memory: 4096
|
# memory: 4096
|
||||||
# name: virtual machine 1
|
# name: virtual machine 1
|
||||||
# platform: Platform 2
|
# platform: Platform 2
|
||||||
# status: Active
|
# status: active
|
||||||
# tenant: tenant1
|
# tenant: tenant1
|
||||||
# vcpus: 8
|
# vcpus: 8
|
||||||
# - cluster: cluster1
|
# - cluster: cluster1
|
||||||
@ -13,6 +21,6 @@
|
|||||||
# memory: 2048
|
# memory: 2048
|
||||||
# name: virtual machine 2
|
# name: virtual machine 2
|
||||||
# platform: Platform 2
|
# platform: Platform 2
|
||||||
# status: Active
|
# status: active
|
||||||
# tenant: tenant1
|
# tenant: tenant1
|
||||||
# vcpus: 8
|
# vcpus: 8
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
|
## Possible Choices:
|
||||||
|
## status:
|
||||||
|
## - active
|
||||||
|
## - reserved
|
||||||
|
## - deprecated
|
||||||
|
##
|
||||||
|
## Examples:
|
||||||
|
|
||||||
# - name: vlan1
|
# - name: vlan1
|
||||||
# site: AMS 1
|
# site: AMS 1
|
||||||
# status: Active
|
# status: active
|
||||||
# vid: 5
|
# vid: 5
|
||||||
# role: Main Management
|
# role: Main Management
|
||||||
# description: VLAN 5 for MGMT
|
# description: VLAN 5 for MGMT
|
||||||
# - group: VLAN group 2
|
# - group: VLAN group 2
|
||||||
# name: vlan2
|
# name: vlan2
|
||||||
# site: AMS 1
|
# site: AMS 1
|
||||||
# status: Active
|
# status: active
|
||||||
# vid: 1300
|
# vid: 1300
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from dcim.constants import CONNECTION_STATUS_PLANNED, DEVICE_STATUS_ACTIVE
|
from dcim.choices import DeviceStatusChoices
|
||||||
from dcim.models import ConsolePort, Device, PowerPort
|
from dcim.models import ConsolePort, Device, PowerPort
|
||||||
from extras.reports import Report
|
from extras.reports import Report
|
||||||
|
|
||||||
@ -9,13 +9,14 @@ 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.
|
||||||
for console_port in ConsolePort.objects.select_related('device').filter(device__status=DEVICE_STATUS_ACTIVE):
|
active = DeviceStatusChoices.STATUS_ACTIVE
|
||||||
|
for console_port in ConsolePort.objects.prefetch_related('device').filter(device__status=active):
|
||||||
if console_port.connected_endpoint is None:
|
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 console_port.connection_status == CONNECTION_STATUS_PLANNED:
|
elif not console_port.connection_status:
|
||||||
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)
|
||||||
@ -26,12 +27,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=DEVICE_STATUS_ACTIVE):
|
for device in Device.objects.filter(status=DeviceStatusChoices.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 power_port.connection_status == CONNECTION_STATUS_PLANNED:
|
if not power_port.connection_status:
|
||||||
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)
|
||||||
@ -43,4 +44,3 @@ class DeviceConnectionsReport(Report):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.log_success(device)
|
self.log_success(device)
|
||||||
|
|
||||||
|
@ -1,34 +1,23 @@
|
|||||||
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
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/users.yml')
|
from django.contrib.auth.models import Group, User
|
||||||
if not file.is_file():
|
from startup_script_utils import load_yaml, set_permissions
|
||||||
|
from users.models import Token
|
||||||
|
|
||||||
|
users = load_yaml('/opt/netbox/initializers/users.yml')
|
||||||
|
if users is None:
|
||||||
sys.exit()
|
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():
|
for username, user_details in users.items():
|
||||||
if not User.objects.filter(username=username):
|
if not User.objects.filter(username=username):
|
||||||
user = User.objects.create_user(
|
user = User.objects.create_user(
|
||||||
username = username,
|
username = username,
|
||||||
password = user_details.get('password', 0) or User.objects.make_random_password)
|
password = user_details.get('password', 0) or User.objects.make_random_password())
|
||||||
|
|
||||||
print("👤 Created user",username)
|
print("👤 Created user",username)
|
||||||
|
|
||||||
if user_details.get('api_token', 0):
|
if user_details.get('api_token', 0):
|
||||||
Token.objects.create(user=user, key=user_details['api_token'])
|
Token.objects.create(user=user, key=user_details['api_token'])
|
||||||
|
|
||||||
user_permissions = user_details.get('permissions', [])
|
yaml_permissions = user_details.get('permissions', [])
|
||||||
if user_permissions:
|
set_permissions(user.user_permissions, yaml_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()
|
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
from django.contrib.auth.models import Permission, Group, User
|
|
||||||
from ruamel.yaml import YAML
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/groups.yml')
|
from django.contrib.auth.models import Group, User
|
||||||
if not file.is_file():
|
from startup_script_utils import load_yaml, set_permissions
|
||||||
|
|
||||||
|
groups = load_yaml('/opt/netbox/initializers/groups.yml')
|
||||||
|
if groups is None:
|
||||||
sys.exit()
|
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():
|
for groupname, group_details in groups.items():
|
||||||
group, created = Group.objects.get_or_create(name=groupname)
|
group, created = Group.objects.get_or_create(name=groupname)
|
||||||
|
|
||||||
@ -24,9 +19,5 @@ with file.open('r') as stream:
|
|||||||
if user:
|
if user:
|
||||||
user.groups.add(group)
|
user.groups.add(group)
|
||||||
|
|
||||||
group_permissions = group_details.get('permissions', [])
|
yaml_permissions = group_details.get('permissions', [])
|
||||||
if group_permissions:
|
set_permissions(group.permissions, yaml_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)
|
|
||||||
|
@ -1,19 +1,8 @@
|
|||||||
from extras.constants import CF_TYPE_TEXT, CF_TYPE_INTEGER, CF_TYPE_BOOLEAN, CF_TYPE_DATE, CF_TYPE_URL, CF_TYPE_SELECT, CF_FILTER_CHOICES
|
|
||||||
from extras.models import CustomField, CustomFieldChoice
|
from extras.models import CustomField, CustomFieldChoice
|
||||||
|
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
text_to_fields = {
|
|
||||||
'boolean': CF_TYPE_BOOLEAN,
|
|
||||||
'date': CF_TYPE_DATE,
|
|
||||||
'integer': CF_TYPE_INTEGER,
|
|
||||||
'selection': CF_TYPE_SELECT,
|
|
||||||
'text': CF_TYPE_TEXT,
|
|
||||||
'url': CF_TYPE_URL,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_class_for_class_path(class_path):
|
def get_class_for_class_path(class_path):
|
||||||
import importlib
|
import importlib
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -23,15 +12,11 @@ def get_class_for_class_path(class_path):
|
|||||||
clazz = getattr(module, class_name)
|
clazz = getattr(module, class_name)
|
||||||
return ContentType.objects.get_for_model(clazz)
|
return ContentType.objects.get_for_model(clazz)
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/custom_fields.yml')
|
customfields = load_yaml('/opt/netbox/initializers/custom_fields.yml')
|
||||||
if not file.is_file():
|
|
||||||
|
if customfields is None:
|
||||||
sys.exit()
|
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():
|
for cf_name, cf_details in customfields.items():
|
||||||
custom_field, created = CustomField.objects.get_or_create(name = cf_name)
|
custom_field, created = CustomField.objects.get_or_create(name = cf_name)
|
||||||
|
|
||||||
@ -42,12 +27,6 @@ with file.open('r') as stream:
|
|||||||
if cf_details.get('description', 0):
|
if cf_details.get('description', 0):
|
||||||
custom_field.description = cf_details['description']
|
custom_field.description = cf_details['description']
|
||||||
|
|
||||||
# If no filter_logic is specified then it will default to 'Loose'
|
|
||||||
if cf_details.get('filter_logic', 0):
|
|
||||||
for choice_id, choice_text in CF_FILTER_CHOICES:
|
|
||||||
if choice_text.lower() == cf_details['filter_logic']:
|
|
||||||
custom_field.filter_logic = choice_id
|
|
||||||
|
|
||||||
if cf_details.get('label', 0):
|
if cf_details.get('label', 0):
|
||||||
custom_field.label = cf_details['label']
|
custom_field.label = cf_details['label']
|
||||||
|
|
||||||
@ -58,7 +37,7 @@ with file.open('r') as stream:
|
|||||||
custom_field.required = cf_details['required']
|
custom_field.required = cf_details['required']
|
||||||
|
|
||||||
if cf_details.get('type', 0):
|
if cf_details.get('type', 0):
|
||||||
custom_field.type = text_to_fields[cf_details['type']]
|
custom_field.type = cf_details['type']
|
||||||
|
|
||||||
if cf_details.get('weight', 0):
|
if cf_details.get('weight', 0):
|
||||||
custom_field.weight = cf_details['weight']
|
custom_field.weight = cf_details['weight']
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
from dcim.models import Region
|
from dcim.models import Region
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/regions.yml')
|
regions = load_yaml('/opt/netbox/initializers/regions.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if regions is None:
|
||||||
yaml=YAML(typ='safe')
|
sys.exit()
|
||||||
regions = yaml.load(stream)
|
|
||||||
|
|
||||||
optional_assocs = {
|
optional_assocs = {
|
||||||
'parent': (Region, 'name')
|
'parent': (Region, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if regions is not None:
|
|
||||||
for params in regions:
|
for params in regions:
|
||||||
|
|
||||||
for assoc, details in optional_assocs.items():
|
for assoc, details in optional_assocs.items():
|
||||||
|
@ -1,24 +1,19 @@
|
|||||||
from dcim.models import Region, Site
|
from dcim.models import Region, Site
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/sites.yml')
|
sites = load_yaml('/opt/netbox/initializers/sites.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if sites is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
sites = yaml.load(stream)
|
|
||||||
|
|
||||||
optional_assocs = {
|
optional_assocs = {
|
||||||
'region': (Region, 'name'),
|
'region': (Region, 'name'),
|
||||||
'tenant': (Tenant, 'name')
|
'tenant': (Tenant, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if sites is not None:
|
|
||||||
for params in sites:
|
for params in sites:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
|
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
from dcim.models import Manufacturer
|
from dcim.models import Manufacturer
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/manufacturers.yml')
|
manufacturers = load_yaml('/opt/netbox/initializers/manufacturers.yml')
|
||||||
if not file.is_file():
|
|
||||||
|
if manufacturers is None:
|
||||||
sys.exit()
|
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:
|
for params in manufacturers:
|
||||||
manufacturer, created = Manufacturer.objects.get_or_create(**params)
|
manufacturer, created = Manufacturer.objects.get_or_create(**params)
|
||||||
|
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
from dcim.models import DeviceType, Manufacturer, Region
|
from dcim.models import DeviceType, Manufacturer, Region
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/device_types.yml')
|
device_types = load_yaml('/opt/netbox/initializers/device_types.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if device_types is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
device_types = yaml.load(stream)
|
|
||||||
|
|
||||||
required_assocs = {
|
required_assocs = {
|
||||||
'manufacturer': (Manufacturer, 'name')
|
'manufacturer': (Manufacturer, 'name')
|
||||||
@ -22,7 +18,6 @@ with file.open('r') as stream:
|
|||||||
'tenant': (Tenant, 'name')
|
'tenant': (Tenant, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if device_types is not None:
|
|
||||||
for params in device_types:
|
for params in device_types:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
|
|
||||||
|
@ -1,24 +1,19 @@
|
|||||||
from dcim.models import RackRole
|
from dcim.models import RackRole
|
||||||
from ruamel.yaml import YAML
|
from utilities.choices import ColorChoices
|
||||||
from utilities.forms import COLOR_CHOICES
|
|
||||||
|
|
||||||
from pathlib import Path
|
from startup_script_utils import load_yaml
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/rack_roles.yml')
|
rack_roles = load_yaml('/opt/netbox/initializers/rack_roles.yml')
|
||||||
if not file.is_file():
|
|
||||||
|
if rack_roles is None:
|
||||||
sys.exit()
|
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:
|
for params in rack_roles:
|
||||||
if 'color' in params:
|
if 'color' in params:
|
||||||
color = params.pop('color')
|
color = params.pop('color')
|
||||||
|
|
||||||
for color_tpl in COLOR_CHOICES:
|
for color_tpl in ColorChoices:
|
||||||
if color in color_tpl:
|
if color in color_tpl:
|
||||||
params['color'] = color_tpl[0]
|
params['color'] = color_tpl[0]
|
||||||
|
|
||||||
|
25
startup_scripts/075_rack_groups.py
Normal file
25
startup_scripts/075_rack_groups.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from dcim.models import Site,RackGroup
|
||||||
|
from startup_script_utils import load_yaml
|
||||||
|
import sys
|
||||||
|
|
||||||
|
rack_groups = load_yaml('/opt/netbox/initializers/rack_groups.yml')
|
||||||
|
|
||||||
|
if rack_groups is None:
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
required_assocs = {
|
||||||
|
'site': (Site, 'name')
|
||||||
|
}
|
||||||
|
|
||||||
|
for params in rack_groups:
|
||||||
|
|
||||||
|
for assoc, details in required_assocs.items():
|
||||||
|
model, field = details
|
||||||
|
query = { field: params.pop(assoc) }
|
||||||
|
params[assoc] = model.objects.get(**query)
|
||||||
|
|
||||||
|
rack_group, created = RackGroup.objects.get_or_create(**params)
|
||||||
|
|
||||||
|
if created:
|
||||||
|
print("🎨 Created rack group", rack_group.name)
|
||||||
|
|
@ -1,18 +1,13 @@
|
|||||||
from dcim.models import Site, RackRole, Rack, RackGroup
|
from dcim.models import Site, RackRole, Rack, RackGroup
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
from dcim.constants import RACK_TYPE_CHOICES, RACK_WIDTH_CHOICES
|
from startup_script_utils import load_yaml
|
||||||
from ruamel.yaml import YAML
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/racks.yml')
|
racks = load_yaml('/opt/netbox/initializers/racks.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if racks is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
racks = yaml.load(stream)
|
|
||||||
|
|
||||||
required_assocs = {
|
required_assocs = {
|
||||||
'site': (Site, 'name')
|
'site': (Site, 'name')
|
||||||
@ -24,7 +19,6 @@ with file.open('r') as stream:
|
|||||||
'group': (RackGroup, 'name')
|
'group': (RackGroup, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if racks is not None:
|
|
||||||
for params in racks:
|
for params in racks:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
|
|
||||||
@ -41,14 +35,6 @@ with file.open('r') as stream:
|
|||||||
|
|
||||||
params[assoc] = model.objects.get(**query)
|
params[assoc] = model.objects.get(**query)
|
||||||
|
|
||||||
for rack_type in RACK_TYPE_CHOICES:
|
|
||||||
if params['type'] in rack_type:
|
|
||||||
params['type'] = rack_type[0]
|
|
||||||
|
|
||||||
for rack_width in RACK_WIDTH_CHOICES:
|
|
||||||
if params['width'] in rack_width:
|
|
||||||
params['width'] = rack_width[0]
|
|
||||||
|
|
||||||
rack, created = Rack.objects.get_or_create(**params)
|
rack, created = Rack.objects.get_or_create(**params)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
|
@ -1,25 +1,20 @@
|
|||||||
from dcim.models import DeviceRole
|
from dcim.models import DeviceRole
|
||||||
from ruamel.yaml import YAML
|
from utilities.choices import ColorChoices
|
||||||
from utilities.forms import COLOR_CHOICES
|
|
||||||
|
|
||||||
from pathlib import Path
|
from startup_script_utils import load_yaml
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/device_roles.yml')
|
device_roles = load_yaml('/opt/netbox/initializers/device_roles.yml')
|
||||||
if not file.is_file():
|
|
||||||
|
if device_roles is None:
|
||||||
sys.exit()
|
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:
|
for params in device_roles:
|
||||||
|
|
||||||
if 'color' in params:
|
if 'color' in params:
|
||||||
color = params.pop('color')
|
color = params.pop('color')
|
||||||
|
|
||||||
for color_tpl in COLOR_CHOICES:
|
for color_tpl in ColorChoices:
|
||||||
if color in color_tpl:
|
if color in color_tpl:
|
||||||
params['color'] = color_tpl[0]
|
params['color'] = color_tpl[0]
|
||||||
|
|
||||||
|
@ -1,22 +1,16 @@
|
|||||||
from dcim.models import Manufacturer, Platform
|
from dcim.models import Manufacturer, Platform
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/platforms.yml')
|
platforms = load_yaml('/opt/netbox/initializers/platforms.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if platforms is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
platforms = yaml.load(stream)
|
|
||||||
|
|
||||||
optional_assocs = {
|
optional_assocs = {
|
||||||
'manufacturer': (Manufacturer, 'name'),
|
'manufacturer': (Manufacturer, 'name'),
|
||||||
}
|
}
|
||||||
|
|
||||||
if platforms is not None:
|
|
||||||
for params in platforms:
|
for params in platforms:
|
||||||
|
|
||||||
for assoc, details in optional_assocs.items():
|
for assoc, details in optional_assocs.items():
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
from tenancy.models import TenantGroup
|
from tenancy.models import TenantGroup
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/tenant_groups.yml')
|
tenant_groups = load_yaml('/opt/netbox/initializers/tenant_groups.yml')
|
||||||
if not file.is_file():
|
|
||||||
|
if tenant_groups is None:
|
||||||
sys.exit()
|
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:
|
for params in tenant_groups:
|
||||||
tenant_group, created = TenantGroup.objects.get_or_create(**params)
|
tenant_group, created = TenantGroup.objects.get_or_create(**params)
|
||||||
|
|
||||||
|
@ -1,23 +1,17 @@
|
|||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/tenants.yml')
|
tenants = load_yaml('/opt/netbox/initializers/tenants.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if tenants is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
tenants = yaml.load(stream)
|
|
||||||
|
|
||||||
optional_assocs = {
|
optional_assocs = {
|
||||||
'group': (TenantGroup, 'name')
|
'group': (TenantGroup, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if tenants is not None:
|
|
||||||
for params in tenants:
|
for params in tenants:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
|
|
||||||
|
@ -1,21 +1,15 @@
|
|||||||
from dcim.models import Site, Rack, DeviceRole, DeviceType, Device, Platform
|
from dcim.models import Site, Rack, DeviceRole, DeviceType, Device, Platform
|
||||||
from dcim.constants import RACK_FACE_CHOICES
|
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/devices.yml')
|
devices = load_yaml('/opt/netbox/initializers/devices.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if devices is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
devices = yaml.load(stream)
|
|
||||||
|
|
||||||
required_assocs = {
|
required_assocs = {
|
||||||
'device_role': (DeviceRole, 'name'),
|
'device_role': (DeviceRole, 'name'),
|
||||||
@ -32,7 +26,6 @@ with file.open('r') as stream:
|
|||||||
'primary_ip6': (IPAddress, 'address')
|
'primary_ip6': (IPAddress, 'address')
|
||||||
}
|
}
|
||||||
|
|
||||||
if devices is not None:
|
|
||||||
for params in devices:
|
for params in devices:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
|
|
||||||
@ -49,12 +42,6 @@ with file.open('r') as stream:
|
|||||||
|
|
||||||
params[assoc] = model.objects.get(**query)
|
params[assoc] = model.objects.get(**query)
|
||||||
|
|
||||||
if 'face' in params:
|
|
||||||
for rack_face in RACK_FACE_CHOICES:
|
|
||||||
if params['face'] in rack_face:
|
|
||||||
params['face'] = rack_face[0]
|
|
||||||
break
|
|
||||||
|
|
||||||
device, created = Device.objects.get_or_create(**params)
|
device, created = Device.objects.get_or_create(**params)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
from virtualization.models import ClusterType
|
from virtualization.models import ClusterType
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/cluster_types.yml')
|
cluster_types = load_yaml('/opt/netbox/initializers/cluster_types.yml')
|
||||||
if not file.is_file():
|
|
||||||
|
if cluster_types is None:
|
||||||
sys.exit()
|
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:
|
for params in cluster_types:
|
||||||
cluster_type, created = ClusterType.objects.get_or_create(**params)
|
cluster_type, created = ClusterType.objects.get_or_create(**params)
|
||||||
|
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
from ipam.models import RIR
|
from ipam.models import RIR
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/rirs.yml')
|
rirs = load_yaml('/opt/netbox/initializers/rirs.yml')
|
||||||
if not file.is_file():
|
|
||||||
|
if rirs is None:
|
||||||
sys.exit()
|
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:
|
for params in rirs:
|
||||||
rir, created = RIR.objects.get_or_create(**params)
|
rir, created = RIR.objects.get_or_create(**params)
|
||||||
|
|
||||||
|
@ -1,24 +1,20 @@
|
|||||||
from ipam.models import Aggregate, RIR
|
from ipam.models import Aggregate, RIR
|
||||||
from ruamel.yaml import YAML
|
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
|
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
from pathlib import Path
|
from startup_script_utils import load_yaml
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/aggregates.yml')
|
aggregates = load_yaml('/opt/netbox/initializers/aggregates.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if aggregates is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
aggregates = yaml.load(stream)
|
|
||||||
|
|
||||||
required_assocs = {
|
required_assocs = {
|
||||||
'rir': (RIR, 'name')
|
'rir': (RIR, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if aggregates is not None:
|
|
||||||
for params in aggregates:
|
for params in aggregates:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
params['prefix'] = IPNetwork(params['prefix'])
|
params['prefix'] = IPNetwork(params['prefix'])
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from virtualization.models import Cluster, ClusterType, ClusterGroup
|
from virtualization.models import Cluster, ClusterType, ClusterGroup
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/clusters.yml')
|
clusters = load_yaml('/opt/netbox/initializers/clusters.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if clusters is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
clusters = yaml.load(stream)
|
|
||||||
|
|
||||||
required_assocs = {
|
required_assocs = {
|
||||||
'type': (ClusterType, 'name')
|
'type': (ClusterType, 'name')
|
||||||
@ -23,7 +18,6 @@ with file.open('r') as stream:
|
|||||||
'group': (ClusterGroup, 'name')
|
'group': (ClusterGroup, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if clusters is not None:
|
|
||||||
for params in clusters:
|
for params in clusters:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
|
|
||||||
|
@ -1,24 +1,20 @@
|
|||||||
from ipam.models import VRF
|
from ipam.models import VRF
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from ruamel.yaml import YAML
|
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
|
|
||||||
from pathlib import Path
|
from startup_script_utils import load_yaml
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/vrfs.yml')
|
vrfs = load_yaml('/opt/netbox/initializers/vrfs.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if vrfs is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
vrfs = yaml.load(stream)
|
|
||||||
|
|
||||||
optional_assocs = {
|
optional_assocs = {
|
||||||
'tenant': (Tenant, 'name')
|
'tenant': (Tenant, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if vrfs is not None:
|
|
||||||
for params in vrfs:
|
for params in vrfs:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
|
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
from ipam.models import Role
|
from ipam.models import Role
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/prefix_vlan_roles.yml')
|
roles = load_yaml('/opt/netbox/initializers/prefix_vlan_roles.yml')
|
||||||
if not file.is_file():
|
|
||||||
|
if roles is None:
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
with file.open('r') as stream:
|
|
||||||
yaml = YAML(typ='safe')
|
|
||||||
roles = yaml.load(stream)
|
|
||||||
|
|
||||||
if roles is not None:
|
|
||||||
for params in roles:
|
for params in roles:
|
||||||
role, created = Role.objects.get_or_create(**params)
|
role, created = Role.objects.get_or_create(**params)
|
||||||
|
|
||||||
|
@ -1,24 +1,18 @@
|
|||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from ipam.models import VLANGroup
|
from ipam.models import VLANGroup
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/vlan_groups.yml')
|
vlan_groups = load_yaml('/opt/netbox/initializers/vlan_groups.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if vlan_groups is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
vlan_groups = yaml.load(stream)
|
|
||||||
|
|
||||||
optional_assocs = {
|
optional_assocs = {
|
||||||
'site': (Site, 'name')
|
'site': (Site, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if vlan_groups is not None:
|
|
||||||
for params in vlan_groups:
|
for params in vlan_groups:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
|
|
||||||
|
@ -1,20 +1,14 @@
|
|||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from ipam.models import VLAN, VLANGroup, Role
|
from ipam.models import VLAN, VLANGroup, Role
|
||||||
from ipam.constants import VLAN_STATUS_CHOICES
|
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/vlans.yml')
|
vlans = load_yaml('/opt/netbox/initializers/vlans.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if vlans is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
vlans = yaml.load(stream)
|
|
||||||
|
|
||||||
optional_assocs = {
|
optional_assocs = {
|
||||||
'site': (Site, 'name'),
|
'site': (Site, 'name'),
|
||||||
@ -24,7 +18,6 @@ with file.open('r') as stream:
|
|||||||
'role': (Role, 'name')
|
'role': (Role, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if vlans is not None:
|
|
||||||
for params in vlans:
|
for params in vlans:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
|
|
||||||
@ -35,12 +28,6 @@ with file.open('r') as stream:
|
|||||||
|
|
||||||
params[assoc] = model.objects.get(**query)
|
params[assoc] = model.objects.get(**query)
|
||||||
|
|
||||||
if 'status' in params:
|
|
||||||
for vlan_status in VLAN_STATUS_CHOICES:
|
|
||||||
if params['status'] in vlan_status:
|
|
||||||
params['status'] = vlan_status[0]
|
|
||||||
break
|
|
||||||
|
|
||||||
vlan, created = VLAN.objects.get_or_create(**params)
|
vlan, created = VLAN.objects.get_or_create(**params)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
|
@ -1,21 +1,15 @@
|
|||||||
from dcim.models import Site
|
from dcim.models import Site
|
||||||
from ipam.models import Prefix, VLAN, Role, VRF
|
from ipam.models import Prefix, VLAN, Role, VRF
|
||||||
from ipam.constants import PREFIX_STATUS_CHOICES
|
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
from ruamel.yaml import YAML
|
|
||||||
|
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
from pathlib import Path
|
from startup_script_utils import load_yaml
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/prefixes.yml')
|
prefixes = load_yaml('/opt/netbox/initializers/prefixes.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if prefixes is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
prefixes = yaml.load(stream)
|
|
||||||
|
|
||||||
optional_assocs = {
|
optional_assocs = {
|
||||||
'site': (Site, 'name'),
|
'site': (Site, 'name'),
|
||||||
@ -26,7 +20,6 @@ with file.open('r') as stream:
|
|||||||
'vrf': (VRF, 'name')
|
'vrf': (VRF, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if prefixes is not None:
|
|
||||||
for params in prefixes:
|
for params in prefixes:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
params['prefix'] = IPNetwork(params['prefix'])
|
params['prefix'] = IPNetwork(params['prefix'])
|
||||||
@ -35,15 +28,8 @@ with file.open('r') as stream:
|
|||||||
if assoc in params:
|
if assoc in params:
|
||||||
model, field = details
|
model, field = details
|
||||||
query = { field: params.pop(assoc) }
|
query = { field: params.pop(assoc) }
|
||||||
|
|
||||||
params[assoc] = model.objects.get(**query)
|
params[assoc] = model.objects.get(**query)
|
||||||
|
|
||||||
if 'status' in params:
|
|
||||||
for prefix_status in PREFIX_STATUS_CHOICES:
|
|
||||||
if params['status'] in prefix_status:
|
|
||||||
params['status'] = prefix_status[0]
|
|
||||||
break
|
|
||||||
|
|
||||||
prefix, created = Prefix.objects.get_or_create(**params)
|
prefix, created = Prefix.objects.get_or_create(**params)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
@ -55,7 +41,6 @@ with file.open('r') as stream:
|
|||||||
obj=prefix,
|
obj=prefix,
|
||||||
value=cf_value
|
value=cf_value
|
||||||
)
|
)
|
||||||
|
|
||||||
prefix.custom_field_values.add(custom_field_value)
|
prefix.custom_field_values.add(custom_field_value)
|
||||||
|
|
||||||
print("📌 Created Prefix", prefix.prefix)
|
print("📌 Created Prefix", prefix.prefix)
|
||||||
|
@ -1,20 +1,14 @@
|
|||||||
from dcim.models import Site, Platform, DeviceRole
|
from dcim.models import Site, Platform, DeviceRole
|
||||||
from virtualization.models import Cluster, VirtualMachine
|
from virtualization.models import Cluster, VirtualMachine
|
||||||
from virtualization.constants import VM_STATUS_CHOICES
|
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/virtual_machines.yml')
|
virtual_machines = load_yaml('/opt/netbox/initializers/virtual_machines.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if virtual_machines is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
virtual_machines = yaml.load(stream)
|
|
||||||
|
|
||||||
required_assocs = {
|
required_assocs = {
|
||||||
'cluster': (Cluster, 'name')
|
'cluster': (Cluster, 'name')
|
||||||
@ -26,7 +20,6 @@ with file.open('r') as stream:
|
|||||||
'role': (DeviceRole, 'name')
|
'role': (DeviceRole, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if virtual_machines is not None:
|
|
||||||
for params in virtual_machines:
|
for params in virtual_machines:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
|
|
||||||
@ -43,12 +36,6 @@ with file.open('r') as stream:
|
|||||||
|
|
||||||
params[assoc] = model.objects.get(**query)
|
params[assoc] = model.objects.get(**query)
|
||||||
|
|
||||||
if 'status' in params:
|
|
||||||
for vm_status in VM_STATUS_CHOICES:
|
|
||||||
if params['status'] in vm_status:
|
|
||||||
params['status'] = vm_status[0]
|
|
||||||
break
|
|
||||||
|
|
||||||
virtual_machine, created = VirtualMachine.objects.get_or_create(**params)
|
virtual_machine, created = VirtualMachine.objects.get_or_create(**params)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
|
@ -1,24 +1,17 @@
|
|||||||
from dcim.models import Interface
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
from virtualization.models import VirtualMachine
|
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/virtualization_interfaces.yml')
|
interfaces = load_yaml('/opt/netbox/initializers/virtualization_interfaces.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if interfaces is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
interfaces = yaml.load(stream)
|
|
||||||
|
|
||||||
required_assocs = {
|
required_assocs = {
|
||||||
'virtual_machine': (VirtualMachine, 'name')
|
'virtual_machine': (VirtualMachine, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if interfaces is not None:
|
|
||||||
for params in interfaces:
|
for params in interfaces:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
|
|
||||||
@ -28,7 +21,7 @@ with file.open('r') as stream:
|
|||||||
|
|
||||||
params[assoc] = model.objects.get(**query)
|
params[assoc] = model.objects.get(**query)
|
||||||
|
|
||||||
interface, created = Interface.objects.get_or_create(**params)
|
interface, created = VMInterface.objects.get_or_create(**params)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
if custom_fields is not None:
|
if custom_fields is not None:
|
||||||
|
@ -1,24 +1,17 @@
|
|||||||
from dcim.models import Interface, Device
|
from dcim.models import Interface, Device
|
||||||
from dcim.constants import IFACE_TYPE_CHOICES
|
|
||||||
from extras.models import CustomField, CustomFieldValue
|
from extras.models import CustomField, CustomFieldValue
|
||||||
from ruamel.yaml import YAML
|
from startup_script_utils import load_yaml
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/dcim_interfaces.yml')
|
interfaces= load_yaml('/opt/netbox/initializers/dcim_interfaces.yml')
|
||||||
if not file.is_file():
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
with file.open('r') as stream:
|
if interfaces is None:
|
||||||
yaml = YAML(typ='safe')
|
sys.exit()
|
||||||
interfaces = yaml.load(stream)
|
|
||||||
|
|
||||||
required_assocs = {
|
required_assocs = {
|
||||||
'device': (Device, 'name')
|
'device': (Device, 'name')
|
||||||
}
|
}
|
||||||
|
|
||||||
if interfaces is not None:
|
|
||||||
for params in interfaces:
|
for params in interfaces:
|
||||||
custom_fields = params.pop('custom_fields', None)
|
custom_fields = params.pop('custom_fields', None)
|
||||||
|
|
||||||
@ -28,16 +21,6 @@ with file.open('r') as stream:
|
|||||||
|
|
||||||
params[assoc] = model.objects.get(**query)
|
params[assoc] = model.objects.get(**query)
|
||||||
|
|
||||||
if 'type' in params:
|
|
||||||
for outer_list in IFACE_TYPE_CHOICES:
|
|
||||||
for type_choices in outer_list[1]:
|
|
||||||
if params['type'] in type_choices:
|
|
||||||
params['type'] = type_choices[0]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
|
|
||||||
interface, created = Interface.objects.get_or_create(**params)
|
interface, created = Interface.objects.get_or_create(**params)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
|
@ -1,30 +1,29 @@
|
|||||||
from ipam.models import IPAddress, VRF
|
|
||||||
from ipam.constants import IPADDRESS_STATUS_CHOICES
|
|
||||||
from dcim.models import Device, Interface
|
|
||||||
from virtualization.models import VirtualMachine
|
|
||||||
from tenancy.models import Tenant
|
|
||||||
from extras.models import CustomField, CustomFieldValue
|
|
||||||
from ruamel.yaml import YAML
|
|
||||||
|
|
||||||
from netaddr import IPNetwork
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
file = Path('/opt/netbox/initializers/ip_addresses.yml')
|
from dcim.models import Device, Interface
|
||||||
if not file.is_file():
|
from django.contrib.contenttypes.models import ContentType
|
||||||
sys.exit()
|
from django.db.models import Q
|
||||||
|
from extras.models import CustomField, CustomFieldValue
|
||||||
|
from ipam.models import VRF, IPAddress
|
||||||
|
from netaddr import IPNetwork
|
||||||
|
from startup_script_utils import load_yaml
|
||||||
|
from tenancy.models import Tenant
|
||||||
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
|
|
||||||
with file.open('r') as stream:
|
ip_addresses = load_yaml('/opt/netbox/initializers/ip_addresses.yml')
|
||||||
yaml = YAML(typ='safe')
|
|
||||||
ip_addresses = yaml.load(stream)
|
if ip_addresses is None:
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
optional_assocs = {
|
optional_assocs = {
|
||||||
'tenant': (Tenant, 'name'),
|
'tenant': (Tenant, 'name'),
|
||||||
'vrf': (VRF, 'name'),
|
'vrf': (VRF, 'name'),
|
||||||
'interface': (Interface, 'name')
|
'interface': (None, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip_addresses is not None:
|
vm_interface_ct = ContentType.objects.filter(Q(app_label='virtualization', model='vminterface')).first()
|
||||||
|
interface_ct = ContentType.objects.filter(Q(app_label='dcim', model='interface')).first()
|
||||||
|
|
||||||
for params in ip_addresses:
|
for params in ip_addresses:
|
||||||
vm = params.pop('virtual_machine', None)
|
vm = params.pop('virtual_machine', None)
|
||||||
device = params.pop('device', None)
|
device = params.pop('device', None)
|
||||||
@ -41,20 +40,18 @@ with file.open('r') as stream:
|
|||||||
if assoc == 'interface':
|
if assoc == 'interface':
|
||||||
if vm:
|
if vm:
|
||||||
vm_id = VirtualMachine.objects.get(name=vm).id
|
vm_id = VirtualMachine.objects.get(name=vm).id
|
||||||
query = { field: params.pop(assoc), "virtual_machine_id": vm_id }
|
query = { 'name': params.pop(assoc), "virtual_machine_id": vm_id }
|
||||||
|
params['assigned_object_type'] = vm_interface_ct
|
||||||
|
params['assigned_object_id'] = VMInterface.objects.get(**query).id
|
||||||
elif device:
|
elif device:
|
||||||
dev_id = Device.objects.get(name=device).id
|
dev_id = Device.objects.get(name=device).id
|
||||||
query = { field: params.pop(assoc), "device_id": dev_id }
|
query = { 'name': params.pop(assoc), "device_id": dev_id }
|
||||||
|
params['assigned_object_type'] = interface_ct
|
||||||
|
params['assigned_object_id'] = Interface.objects.get(**query).id
|
||||||
else:
|
else:
|
||||||
query = { field: params.pop(assoc) }
|
query = { field: params.pop(assoc) }
|
||||||
params[assoc] = model.objects.get(**query)
|
params[assoc] = model.objects.get(**query)
|
||||||
|
|
||||||
if 'status' in params:
|
|
||||||
for ip_status in IPADDRESS_STATUS_CHOICES:
|
|
||||||
if params['status'] in ip_status:
|
|
||||||
params['status'] = ip_status[0]
|
|
||||||
break
|
|
||||||
|
|
||||||
ip_address, created = IPAddress.objects.get_or_create(**params)
|
ip_address, created = IPAddress.objects.get_or_create(**params)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
|
29
startup_scripts/__main__.py
Normal file
29
startup_scripts/__main__.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import runpy
|
||||||
|
from os import scandir
|
||||||
|
from os.path import dirname, abspath
|
||||||
|
|
||||||
|
this_dir = dirname(abspath(__file__))
|
||||||
|
|
||||||
|
def filename(f):
|
||||||
|
return f.name
|
||||||
|
|
||||||
|
with scandir(dirname(abspath(__file__))) 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
|
||||||
|
|
||||||
|
print(f"▶️ Running the startup script {f.path}")
|
||||||
|
try:
|
||||||
|
runpy.run_path(f.path)
|
||||||
|
except SystemExit as e:
|
||||||
|
if e.code is not None and e.code != 0:
|
||||||
|
print(f"‼️ The startup script {f.path} returned with code {e.code}, exiting.")
|
||||||
|
raise
|
2
startup_scripts/startup_script_utils/__init__.py
Normal file
2
startup_scripts/startup_script_utils/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from .load_yaml import load_yaml
|
||||||
|
from .permissions import set_permissions
|
10
startup_scripts/startup_script_utils/load_yaml.py
Normal file
10
startup_scripts/startup_script_utils/load_yaml.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from ruamel.yaml import YAML
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def load_yaml(yaml_file: str):
|
||||||
|
yf = Path(yaml_file)
|
||||||
|
if not yf.is_file():
|
||||||
|
return None
|
||||||
|
with yf.open("r") as stream:
|
||||||
|
yaml = YAML(typ="safe")
|
||||||
|
return yaml.load(stream)
|
18
startup_scripts/startup_script_utils/permissions.py
Normal file
18
startup_scripts/startup_script_utils/permissions.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from django.contrib.auth.models import Permission
|
||||||
|
|
||||||
|
|
||||||
|
def set_permissions(subject, permission_filters):
|
||||||
|
if subject is None or permission_filters is None:
|
||||||
|
return
|
||||||
|
subject.clear()
|
||||||
|
for permission_filter in permission_filters:
|
||||||
|
if "*" in permission_filter:
|
||||||
|
permission_filter_regex = "^" + permission_filter.replace("*", ".*") + "$"
|
||||||
|
permissions = Permission.objects.filter(codename__iregex=permission_filter_regex)
|
||||||
|
print(" ⚿ Granting", permissions.count(), "permissions matching '" + permission_filter + "'")
|
||||||
|
else:
|
||||||
|
permissions = Permission.objects.filter(codename=permission_filter)
|
||||||
|
print(" ⚿ Granting permission", permission_filter)
|
||||||
|
|
||||||
|
for permission in permissions:
|
||||||
|
subject.add(permission)
|
69
test.sh
Executable file
69
test.sh
Executable file
@ -0,0 +1,69 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# exit when a command exits with an exit code != 0
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# version is used by `docker-compose.yml` do determine the tag
|
||||||
|
# of the Docker Image that is to be used
|
||||||
|
export IMAGE="${IMAGE-netboxcommunity/netbox:latest}"
|
||||||
|
|
||||||
|
if [ -z "${IMAGE}" ]; then
|
||||||
|
echo "⚠️ No image defined"
|
||||||
|
|
||||||
|
if [ -z "${DEBUG}" ]; then
|
||||||
|
exit 1;
|
||||||
|
else
|
||||||
|
echo "⚠️ Would 'exit 1' here, but DEBUG is '${DEBUG}'."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The docker compose command to use
|
||||||
|
doco="docker-compose -f docker-compose.test.yml"
|
||||||
|
|
||||||
|
INITIALIZERS_DIR=".initializers"
|
||||||
|
|
||||||
|
test_setup() {
|
||||||
|
echo "🏗 Setup up test environment"
|
||||||
|
if [ -d "${INITIALIZERS_DIR}" ]; then
|
||||||
|
rm -rf "${INITIALIZERS_DIR}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir "${INITIALIZERS_DIR}"
|
||||||
|
(
|
||||||
|
cd initializers
|
||||||
|
for script in *.yml; do
|
||||||
|
sed -E 's/^# //' "${script}" > "../${INITIALIZERS_DIR}/${script}"
|
||||||
|
done
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_netbox_unit_tests() {
|
||||||
|
echo "⏱ Running Netbox Unit Tests"
|
||||||
|
$doco run --rm netbox ./manage.py test
|
||||||
|
}
|
||||||
|
|
||||||
|
test_initializers() {
|
||||||
|
echo "🏭 Testing Initializers"
|
||||||
|
export INITIALIZERS_DIR
|
||||||
|
$doco run --rm netbox ./manage.py check
|
||||||
|
}
|
||||||
|
|
||||||
|
test_cleanup() {
|
||||||
|
echo "💣 Cleaning Up"
|
||||||
|
$doco down -v
|
||||||
|
|
||||||
|
if [ -d "${INITIALIZERS_DIR}" ]; then
|
||||||
|
rm -rf "${INITIALIZERS_DIR}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "🐳🐳🐳 Start testing '${IMAGE}'"
|
||||||
|
|
||||||
|
# Make sure the cleanup script is executed
|
||||||
|
trap test_cleanup EXIT ERR
|
||||||
|
test_setup
|
||||||
|
|
||||||
|
test_netbox_unit_tests
|
||||||
|
test_initializers
|
||||||
|
|
||||||
|
echo "🐳🐳🐳 Done testing '${IMAGE}'"
|
Reference in New Issue
Block a user