diff --git a/.dockerignore b/.dockerignore index 00fd8b8f2..ea4976e2a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,10 +1,13 @@ -.git -.github -.travis.yml +.git* *.md -env build* -docker-compose.override.yml +docker-compose* +env +test-configuration .netbox/.git* -.netbox/.travis.yml +.netbox/.pre-commit-config.yaml +.netbox/.readthedocs.yaml +.netbox/.tx +.netbox/contrib .netbox/scripts +.netbox/upgrade.sh diff --git a/.editorconfig b/.editorconfig index 7f9f55de0..4f5c931b8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,3 +9,6 @@ indent_size = 2 [*.py] indent_size = 4 + +[VERSION] +insert_final_newline = false diff --git a/.ecrc b/.editorconfig-checker.json similarity index 67% rename from .ecrc rename to .editorconfig-checker.json index f0c91f7c9..d36f06099 100644 --- a/.ecrc +++ b/.editorconfig-checker.json @@ -2,17 +2,12 @@ "Verbose": false, "Debug": false, "IgnoreDefaults": false, - "SpacesAftertabs": false, + "SpacesAfterTabs": false, "NoColor": false, - "Exclude": [ - "LICENSE", - "\\.initializers", - "\\.vscode" - ], + "Exclude": ["LICENSE", "\\.initializers", "\\.vscode"], "AllowedContentTypes": [], "PassedFiles": [], "Disable": { - // set these options to true to disable specific checks "EndOfLine": false, "Indentation": false, "InsertFinalNewline": false, diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index acdb4e09a..f76f0f48f 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,8 +1,8 @@ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -- cimnine -- tobiasge + - cimnine + - tobiasge patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b6f0479ed..bc64d3de9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,148 +1,147 @@ name: Bug report description: Create a report about a malfunction of the Docker setup body: -- type: markdown - attributes: - value: | - Please only raise an issue if you're certain that you've found a bug. - Else, see these other means to get help: + - type: markdown + attributes: + value: | + Please only raise an issue if you're certain that you've found a bug. + Else, see these other means to get help: - - See our troubleshooting section: - https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting - - Have a look at the rest of the wiki: - https://github.com/netbox-community/netbox-docker/wiki - - Check the release notes: - https://github.com/netbox-community/netbox-docker/releases - - Look through the issues already resolved: - https://github.com/netbox-community/netbox-docker/issues?q=is%3Aclosed + - See our troubleshooting section: + https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting + - Have a look at the rest of the wiki: + https://github.com/netbox-community/netbox-docker/wiki + - Check the release notes: + https://github.com/netbox-community/netbox-docker/releases + - Look through the issues already resolved: + https://github.com/netbox-community/netbox-docker/issues?q=is%3Aclosed - If you did not find what you're looking for, - try the help of our community: + If you did not find what you're looking for, + try the help of our community: - - Post to Github Discussions: - https://github.com/netbox-community/netbox-docker/discussions - - Join the `#netbox-docker` channel on our Slack: - https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ - - Ask on the NetBox mailing list: - https://groups.google.com/d/forum/netbox-discuss + - Post to Github Discussions: + https://github.com/netbox-community/netbox-docker/discussions + - Join the `#netbox-docker` channel on our Slack: + https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ + - Ask on the NetBox mailing list: + https://groups.google.com/d/forum/netbox-discuss - Please don't open an issue to open a PR. - Just submit the PR, that's good enough. -- type: textarea - id: current-behavior - attributes: - label: Current Behavior - description: Please describe what you did and how you think it misbehaved - placeholder: I tried to … by doing …, but it … - validations: - required: true -- type: textarea - id: expected-behavior - attributes: - label: Expected Behavior - description: Please describe what you expected instead - placeholder: I expected that … when I do … - validations: - required: true -- type: input - id: docker-compose-version - attributes: - label: Docker Compose Version - description: Please paste the output of `docker-compose version` - placeholder: Docker Compose version vX.Y.Z - validations: - required: true -- type: textarea - id: docker-version - attributes: - label: Docker Version - description: Please paste the output of `docker version` - render: text - placeholder: | - Client: - Cloud integration: 1.0.17 - Version: 20.10.8 - API version: 1.41 - Go version: go1.16.6 - Git commit: 3967b7d - Built: Fri Jul 30 19:55:20 2021 - OS/Arch: darwin/amd64 - Context: default - Experimental: true + Please don't open an issue to open a PR. + Just submit the PR, that's good enough. + - type: textarea + id: current-behavior + attributes: + label: Current Behavior + description: Please describe what you did and how you think it misbehaved + placeholder: I tried to … by doing …, but it … + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Expected Behavior + description: Please describe what you expected instead + placeholder: I expected that … when I do … + validations: + required: true + - type: input + id: docker-compose-version + attributes: + label: Docker Compose Version + description: Please paste the output of `docker-compose version` (or `docker compose version`) + placeholder: Docker Compose version vX.Y.Z + validations: + required: true + - type: textarea + id: docker-version + attributes: + label: Docker Version + description: Please paste the output of `docker version` + render: text + placeholder: | + Client: + Cloud integration: 1.0.17 + Version: 20.10.8 + API version: 1.41 + Go version: go1.16.6 + Git commit: 3967b7d + Built: Fri Jul 30 19:55:20 2021 + OS/Arch: darwin/amd64 + Context: default + Experimental: true - Server: Docker Engine - Community - Engine: - Version: 20.10.8 - API version: 1.41 (minimum version 1.12) - Go version: go1.16.6 - Git commit: 75249d8 - Built: Fri Jul 30 19:52:10 2021 - OS/Arch: linux/amd64 - Experimental: false - containerd: - Version: 1.4.9 - GitCommit: e25210fe30a0a703442421b0f60afac609f950a3 - runc: - Version: 1.0.1 - GitCommit: v1.0.1-0-g4144b63 - docker-init: - Version: 0.19.0 - GitCommit: de40ad0 - validations: - required: true -- type: input - id: git-rev - attributes: - label: The git Revision - description: Please paste the output of `git rev-parse HEAD` - validations: - required: true -- type: textarea - id: git-status - attributes: - label: The git Status - description: Please paste the output of `git status` - render: text - placeholder: | - On branch main - nothing to commit, working tree clean - validations: - required: true -- type: input - id: run-command - attributes: - label: Startup Command - description: Please specify the command you used to start the project - placeholder: docker compose up - validations: - required: true -- type: textarea - id: netbox-logs - attributes: - label: NetBox Logs - description: Please paste the output of `docker-compose logs netbox` (or `docker compose logs netbox`) - render: text - placeholder: | - netbox_1 | ⚙️ Applying database migrations - netbox_1 | 🧬 loaded config '/etc/netbox/config/configuration.py' - netbox_1 | 🧬 loaded config '/etc/netbox/config/a.py' - netbox_1 | 🧬 loaded config '/etc/netbox/config/extra.py' - netbox_1 | 🧬 loaded config '/etc/netbox/config/logging.py' - netbox_1 | 🧬 loaded config '/etc/netbox/config/plugins.py' - ... - validations: - required: true -- type: textarea - id: docker-compose-override-yml - attributes: - label: Content of docker-compose.override.yml - description: Please paste the output of `cat docker-compose.override.yml` - render: yaml - placeholder: | - version: '3.4' - services: - netbox: - ports: - - '8080:8080' - validations: - required: true + Server: Docker Engine - Community + Engine: + Version: 20.10.8 + API version: 1.41 (minimum version 1.12) + Go version: go1.16.6 + Git commit: 75249d8 + Built: Fri Jul 30 19:52:10 2021 + OS/Arch: linux/amd64 + Experimental: false + containerd: + Version: 1.4.9 + GitCommit: e25210fe30a0a703442421b0f60afac609f950a3 + runc: + Version: 1.0.1 + GitCommit: v1.0.1-0-g4144b63 + docker-init: + Version: 0.19.0 + GitCommit: de40ad0 + validations: + required: true + - type: input + id: git-rev + attributes: + label: The git Revision + description: Please paste the output of `git rev-parse HEAD` + validations: + required: true + - type: textarea + id: git-status + attributes: + label: The git Status + description: Please paste the output of `git status` + render: text + placeholder: | + On branch main + nothing to commit, working tree clean + validations: + required: true + - type: input + id: run-command + attributes: + label: Startup Command + description: Please specify the command you used to start the project + placeholder: docker compose up + validations: + required: true + - type: textarea + id: netbox-logs + attributes: + label: NetBox Logs + description: Please paste the output of `docker-compose logs netbox` (or `docker compose logs netbox`) + render: text + placeholder: | + netbox_1 | ⚙️ Applying database migrations + netbox_1 | 🧬 loaded config '/etc/netbox/config/configuration.py' + netbox_1 | 🧬 loaded config '/etc/netbox/config/a.py' + netbox_1 | 🧬 loaded config '/etc/netbox/config/extra.py' + netbox_1 | 🧬 loaded config '/etc/netbox/config/logging.py' + netbox_1 | 🧬 loaded config '/etc/netbox/config/plugins.py' + ... + validations: + required: true + - type: textarea + id: docker-compose-override-yml + attributes: + label: Content of docker-compose.override.yml + description: Please paste the output of `cat docker-compose.override.yml` + render: yaml + placeholder: | + services: + netbox: + ports: + - '8080:8080' + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index a584cc2eb..3e3d62a5b 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -6,7 +6,7 @@ contact_links: - name: Chat url: https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ - about: 'Usually the quickest way to seek help with small issues is to join our #netbox-docker Slack channel.' + about: "Usually the quickest way to seek help with small issues is to join our #netbox-docker Slack channel." - name: Community Wiki url: https://github.com/netbox-community/netbox-docker/wiki diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index e8f2a55ba..88d46fdcd 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,68 +1,68 @@ name: Feature or Change Request description: Request a new feature or a change of the current behavior body: -- type: markdown - attributes: - value: | - This issue type is to propose new features for the Docker setup. - To just spin an idea, see the Github Discussions section, please. + - type: markdown + attributes: + value: | + This issue type is to propose new features for the Docker setup. + To just spin an idea, see the Github Discussions section, please. - Before asking for help, see these links first: + Before asking for help, see these links first: - - See our troubleshooting section: - https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting - - Have a look at the rest of the wiki: - https://github.com/netbox-community/netbox-docker/wiki - - Check the release notes: - https://github.com/netbox-community/netbox-docker/releases - - Look through the issues already resolved: - https://github.com/netbox-community/netbox-docker/issues?q=is%3Aclosed + - See our troubleshooting section: + https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting + - Have a look at the rest of the wiki: + https://github.com/netbox-community/netbox-docker/wiki + - Check the release notes: + https://github.com/netbox-community/netbox-docker/releases + - Look through the issues already resolved: + https://github.com/netbox-community/netbox-docker/issues?q=is%3Aclosed - If you did not find what you're looking for, - try the help of our community: + If you did not find what you're looking for, + try the help of our community: - - Post to Github Discussions: - https://github.com/netbox-community/netbox-docker/discussions - - Join the `#netbox-docker` channel on our Slack: - https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ - - Ask on the NetBox mailing list: - https://groups.google.com/d/forum/netbox-discuss + - Post to Github Discussions: + https://github.com/netbox-community/netbox-docker/discussions + - Join the `#netbox-docker` channel on our Slack: + https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ + - Ask on the NetBox mailing list: + https://groups.google.com/d/forum/netbox-discuss - Please don't open an issue to open a PR. - Just submit the PR, that's good enough. -- type: textarea - id: desired-behavior - attributes: - label: Desired Behavior - description: Please describe the desired behavior - placeholder: To me, it would be useful, if … because … - validations: - required: true -- type: textarea - id: contrast-to-current - attributes: - label: Contrast to Current Behavior - description: Please describe how the desired behavior is different from the current behavior - placeholder: The current behavior is …, but this lacks … - validations: - required: true -- type: textarea - id: required-changes - attributes: - label: Required Changes - description: If you can, please elaborate what changes will be required to implement the desired behavior - placeholder: I suggest to change the file … - validations: - required: false -- type: textarea - id: discussion - attributes: - label: 'Discussion: Benefits and Drawbacks' - description: | - Please make your case here: - - Why do you think this project and the community will benefit from your suggestion? - - What are the drawbacks of this change? Is it backwards-compatible? - - Anything else that you think is relevant to the discussion of this feature/change request. - placeholder: I suggest to change the file … - validations: - required: false + Please don't open an issue to open a PR. + Just submit the PR, that's good enough. + - type: textarea + id: desired-behavior + attributes: + label: Desired Behavior + description: Please describe the desired behavior + placeholder: To me, it would be useful, if … because … + validations: + required: true + - type: textarea + id: contrast-to-current + attributes: + label: Contrast to Current Behavior + description: Please describe how the desired behavior is different from the current behavior + placeholder: The current behavior is …, but this lacks … + validations: + required: true + - type: textarea + id: required-changes + attributes: + label: Required Changes + description: If you can, please elaborate what changes will be required to implement the desired behavior + placeholder: I suggest to change the file … + validations: + required: false + - type: textarea + id: discussion + attributes: + label: "Discussion: Benefits and Drawbacks" + description: | + Please make your case here: + - Why do you think this project and the community will benefit from your suggestion? + - What are the drawbacks of this change? Is it backwards-compatible? + - Anything else that you think is relevant to the discussion of this feature/change request. + placeholder: I suggest to change the file … + validations: + required: false diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 139ff9638..8c51bfdfc 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -80,6 +80,6 @@ into the release notes. 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. +- [ ] 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. diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 3dc25bab1..f4b630535 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -5,34 +5,46 @@ on: push: branches-ignore: - release + - renovate/** pull_request: branches-ignore: - release +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: lint: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 name: Checks syntax of our code + permissions: + contents: read + packages: read + statuses: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 with: # Full git history is needed to get a proper # list of changed files within `super-linter` fetch-depth: 0 - - uses: actions/setup-python@v4 - with: - python-version: '3.9' - name: Lint Code Base - uses: github/super-linter@v4 + uses: super-linter/super-linter@v8 env: DEFAULT_BRANCH: develop GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SUPPRESS_POSSUM: true LINTER_RULES_PATH: / VALIDATE_ALL_CODEBASE: false + VALIDATE_BIOME_FORMAT: false + VALIDATE_CHECKOV: false VALIDATE_DOCKERFILE: false + VALIDATE_GITHUB_ACTIONS_ZIZMOR: false + VALIDATE_GITLEAKS: false + VALIDATE_JSCPD: false + VALIDATE_TRIVY: false FILTER_REGEX_EXCLUDE: (.*/)?(LICENSE|configuration/.*) - EDITORCONFIG_FILE_NAME: .ecrc + EDITORCONFIG_FILE_NAME: .editorconfig-checker.json DOCKERFILE_HADOLINT_FILE_NAME: .hadolint.yaml MARKDOWN_CONFIG_FILE: .markdown-lint.yml PYTHON_BLACK_CONFIG_FILE: pyproject.toml @@ -47,32 +59,44 @@ jobs: - ./build-latest.sh - PRERELEASE=true ./build-latest.sh - ./build.sh feature - - ./build.sh develop - platform: - - linux/amd64 - - linux/arm64 + - ./build.sh main + os: + - ubuntu-24.04 + - ubuntu-24.04-arm fail-fast: false env: GH_ACTION: enable + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} IMAGE_NAMES: docker.io/netboxcommunity/netbox - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} name: Builds new NetBox Docker Images steps: - id: git-checkout name: Checkout - uses: actions/checkout@v3 - - id: qemu-setup - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: actions/checkout@v5 - id: buildx-setup name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 + - id: arm-install-skopeo + name: Install 'skopeo' on ARM64 + if: matrix.os == 'ubuntu-24.04-arm' + run: | + sudo apt-get install -y skopeo + - id: arm-buildx-platform + name: Set BUILDX_PLATFORM to ARM64 + if: matrix.os == 'ubuntu-24.04-arm' + run: | + echo "BUILDX_PLATFORM=linux/arm64" >>"${GITHUB_ENV}" - id: docker-build - name: Build the image for '${{ matrix.platform }}' with '${{ matrix.build_cmd }}' + name: Build the image for '${{ matrix.os }}' with '${{ matrix.build_cmd }}' run: ${{ matrix.build_cmd }} env: - BUILDX_PLATFORM: ${{ matrix.platform }} BUILDX_BUILDER_NAME: ${{ steps.buildx-setup.outputs.name }} + - id: arm-time-limit + name: Set Netbox container start_period higher on ARM64 + if: matrix.os == 'ubuntu-24.04-arm' + run: | + echo "NETBOX_START_PERIOD=240s" >>"${GITHUB_ENV}" - id: docker-test name: Test the image run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3b622ea22..2d83729c5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,78 +6,83 @@ on: types: - published schedule: - - cron: '45 5 * * *' + - cron: "45 5 * * *" workflow_dispatch: jobs: build: strategy: matrix: - build_cmd: - - ./build-latest.sh - - PRERELEASE=true ./build-latest.sh - - ./build.sh feature - - ./build.sh develop + build: + - { "cmd": "./build-latest.sh", "branch": "release" } + - { "cmd": "./build.sh main", "branch": "release" } + # Build pre release images from our develop branch + # This is used to test the latest changes before they are merged into the main branch + - { "cmd": "PRERELEASE=true ./build-latest.sh", "branch": "develop" } + - { "cmd": "./build.sh feature", "branch": "develop" } platform: - linux/amd64,linux/arm64 fail-fast: false - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 name: Builds new NetBox Docker Images env: GH_ACTION: enable + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} IMAGE_NAMES: docker.io/netboxcommunity/netbox quay.io/netboxcommunity/netbox ghcr.io/netbox-community/netbox steps: - id: source-checkout name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v5 + with: + ref: ${{ matrix.build.branch }} - id: set-netbox-docker-version name: Get Version of NetBox Docker - run: echo "::set-output name=version::$(cat VERSION)" + run: echo "version=$(cat VERSION)" >>"$GITHUB_OUTPUT" shell: bash - - id: qemu-setup - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - id: buildx-setup - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - id: docker-build - name: Build the image with '${{ matrix.build_cmd }}' - run: ${{ matrix.build_cmd }} - - id: test-image - name: Test the image - run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh - if: steps.docker-build.outputs.skipped != 'true' + - id: check-build-needed + name: Check if the build is needed for '${{ matrix.build.cmd }}' + env: + CHECK_ONLY: "true" + run: ${{ matrix.build.cmd }} # docker.io - id: docker-io-login name: Login to docker.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: docker.io username: ${{ secrets.dockerhub_username }} password: ${{ secrets.dockerhub_password }} - if: steps.docker-build.outputs.skipped != 'true' + if: steps.check-build-needed.outputs.skipped != 'true' + - id: buildx-setup + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + version: "lab:latest" + driver: cloud + endpoint: "netboxcommunity/netbox-default" + if: steps.check-build-needed.outputs.skipped != 'true' # quay.io - id: quay-io-login name: Login to Quay.io - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: quay.io username: ${{ secrets.quayio_username }} password: ${{ secrets.quayio_password }} - if: steps.docker-build.outputs.skipped != 'true' + if: steps.check-build-needed.outputs.skipped != 'true' # ghcr.io - id: ghcr-io-login name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - if: steps.docker-build.outputs.skipped != 'true' + if: steps.check-build-needed.outputs.skipped != 'true' - id: build-and-push name: Push the image - run: ${{ matrix.build_cmd }} --push - if: steps.docker-build.outputs.skipped != 'true' + run: ${{ matrix.build.cmd }} --push + if: steps.check-build-needed.outputs.skipped != 'true' env: BUILDX_PLATFORM: ${{ matrix.platform }} BUILDX_BUILDER_NAME: ${{ steps.buildx-setup.outputs.name }} diff --git a/.gitignore b/.gitignore index 9a5e13a4b..2fb516639 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ *.sql.gz .netbox -.initializers +.python-version docker-compose.override.yml *.pem configuration/* @@ -11,5 +11,4 @@ configuration/ldap/* !configuration/ldap/ldap_config.py !configuration/logging.py !configuration/plugins.py -prometheus.yml super-linter.log diff --git a/.yamllint.yaml b/.yamllint.yaml index 50d6af787..81a9e05e7 100644 --- a/.yamllint.yaml +++ b/.yamllint.yaml @@ -1,5 +1,4 @@ --- - rules: line-length: - max: 120 + max: 160 diff --git a/Dockerfile b/Dockerfile index 2bc5dff5a..b398e9c9b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ ARG FROM -FROM ${FROM} as builder +FROM ${FROM} AS builder +COPY --from=ghcr.io/astral-sh/uv:0.9 /uv /usr/local/bin/ RUN export DEBIAN_FRONTEND=noninteractive \ && apt-get update -qq \ && apt-get upgrade \ @@ -13,18 +14,28 @@ RUN export DEBIAN_FRONTEND=noninteractive \ libpq-dev \ libsasl2-dev \ libssl-dev \ + libxml2-dev \ + libxmlsec1 \ + libxmlsec1-dev \ + libxmlsec1-openssl \ + libxslt-dev \ + pkg-config \ python3-dev \ - python3-pip \ - python3-venv \ - && python3 -m venv /opt/netbox/venv \ - && /opt/netbox/venv/bin/python3 -m pip install --upgrade \ - pip \ - setuptools \ - wheel + && /usr/local/bin/uv venv /opt/netbox/venv ARG NETBOX_PATH COPY ${NETBOX_PATH}/requirements.txt requirements-container.txt / -RUN /opt/netbox/venv/bin/pip install \ +ENV VIRTUAL_ENV=/opt/netbox/venv +RUN \ + # Gunicorn is not needed because we use Nginx Unit + sed -i -e '/gunicorn/d' /requirements.txt && \ + # We need 'social-auth-core[all]' in the Docker image. But if we put it in our own requirements-container.txt + # we have potential version conflicts and the build will fail. + # That's why we just replace it in the original requirements.txt. + sed -i -e 's/social-auth-core/social-auth-core\[all\]/g' /requirements.txt && \ + # The same is true for 'django-storages' + sed -i -e 's/django-storages/django-storages\[azure,boto3,dropbox,google,libcloud,sftp\]/g' /requirements.txt && \ + /usr/local/bin/uv pip install \ -r /requirements.txt \ -r /requirements-container.txt @@ -33,8 +44,10 @@ RUN /opt/netbox/venv/bin/pip install \ ### ARG FROM -FROM ${FROM} as main +FROM ${FROM} AS main +COPY docker/unit.list /etc/apt/sources.list.d/unit.list +ADD --chmod=444 --chown=0:0 https://unit.nginx.org/keys/nginx-keyring.gpg /usr/share/keyrings/nginx-keyring.gpg RUN export DEBIAN_FRONTEND=noninteractive \ && apt-get update -qq \ && apt-get upgrade \ @@ -46,21 +59,18 @@ RUN export DEBIAN_FRONTEND=noninteractive \ curl \ libldap-common \ libpq5 \ + libxmlsec1-openssl \ + openssh-client \ openssl \ python3 \ - python3-distutils \ tini \ - && curl -sL https://nginx.org/keys/nginx_signing.key \ - > /etc/apt/trusted.gpg.d/nginx.asc && \ - echo "deb https://packages.nginx.org/unit/ubuntu/ jammy unit" \ - > /etc/apt/sources.list.d/unit.list \ - && apt-get update -qq \ - && apt-get install \ - --yes -qq --no-install-recommends \ - unit=1.27.0-1~jammy \ - unit-python3.10=1.27.0-1~jammy \ + unit-python3.12=1.34.2-1~noble \ + unit=1.34.2-1~noble \ && rm -rf /var/lib/apt/lists/* +# Copy the modified 'requirements*.txt' files, to have the files actually used during installation +COPY --from=builder /requirements.txt /requirements-container.txt /opt/netbox/ +COPY --from=builder /usr/local/bin/uv /usr/local/bin/ COPY --from=builder /opt/netbox/venv /opt/netbox/venv ARG NETBOX_PATH @@ -69,23 +79,25 @@ COPY ${NETBOX_PATH} /opt/netbox COPY docker/configuration.docker.py /opt/netbox/netbox/netbox/configuration.py COPY docker/ldap_config.docker.py /opt/netbox/netbox/netbox/ldap_config.py COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh -COPY docker/housekeeping.sh /opt/netbox/housekeeping.sh COPY docker/launch-netbox.sh /opt/netbox/launch-netbox.sh COPY configuration/ /etc/netbox/config/ COPY docker/nginx-unit.json /etc/unit/ +COPY VERSION /opt/netbox/VERSION WORKDIR /opt/netbox/netbox # Must set permissions for '/opt/netbox/netbox/media' directory # to g+w so that pictures can be uploaded to netbox. -RUN mkdir -p static /opt/unit/state/ /opt/unit/tmp/ \ - && chown -R unit:root media /opt/unit/ \ - && chmod -R g+w media /opt/unit/ \ - && cd /opt/netbox/ && SECRET_KEY="dummy" /opt/netbox/venv/bin/python -m mkdocs build \ +RUN mkdir -p static media /opt/unit/state/ /opt/unit/tmp/ \ + && chown -R unit:root /opt/unit/ media reports scripts \ + && chmod -R g+w /opt/unit/ media reports scripts \ + && cd /opt/netbox/ && SECRET_KEY="dummyKeyWithMinimumLength-------------------------" /opt/netbox/venv/bin/python -m mkdocs build \ --config-file /opt/netbox/mkdocs.yml --site-dir /opt/netbox/netbox/project-static/docs/ \ - && SECRET_KEY="dummy" /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input + && DEBUG="true" SECRET_KEY="dummyKeyWithMinimumLength-------------------------" /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input \ + && mkdir /opt/netbox/netbox/local \ + && echo "build: Docker-$(cat /opt/netbox/VERSION)" > /opt/netbox/netbox/local/release.yaml -ENV LANG=C.UTF-8 PATH=/opt/netbox/venv/bin:$PATH +ENV LANG=C.utf8 PATH=/opt/netbox/venv/bin:$PATH VIRTUAL_ENV=/opt/netbox/venv UV_NO_CACHE=1 ENTRYPOINT [ "/usr/bin/tini", "--" ] CMD [ "/opt/netbox/docker-entrypoint.sh", "/opt/netbox/launch-netbox.sh" ] diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 000000000..a3071eb0d --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,19 @@ +# Maintainers of _NetBox Docker_ + +This file lists all currently recognized maintainers of the _NetBox Docker_ project in alphabetical order: + +- @cimnine +- @tobiasge + +## Stepping Down + +Every maintainer is a volunteer and may step down as maintainer at any time without providing any reason. +To make this explicit, the maintainer is asked to update this file. + +The last maintainer stepping down is asked to archive the project on GitHub to indicate that the project is no longer maintained. + +## Signing up + +Everyone is welcome to sign up as maintainer by creating a PR and add their own username to the list. +The current maintainers shall discuss the application. +They may turn down an application if they don't feel confident that the new maintainer is a positive addition. diff --git a/PRINCIPALS.md b/PRINCIPALS.md new file mode 100644 index 000000000..f2d03554c --- /dev/null +++ b/PRINCIPALS.md @@ -0,0 +1,71 @@ +# Development, Maintenance and Community Principals for _NetBox Docker_ + +These principals shall guide the development and the maintenance of _NetBox Docker_. + +## Basic principals + +This project is maintained on voluntary basis. +Everyone is asked to respect that. + +This means, that … + +- … sometimes features are not implemented as fast as one might like -- or not at all. +- … sometimes nobody is looking at bugs, or they are not fixed as fast as one might like -- or not at all. +- … sometimes PRs are not reviewed for an extended period. + +Everyone is welcome to provide improvements and bugfixes to the benefit of everyone else. + +## Development Principals + +The goal of the _NetBox Docker_ project is to provide a container to run the basic NetBox project. +The container should feel like a native container -- as if it were provided by NetBox itself: + +- Configuration via environment variables where feasible. + - Except: Whenever a complex type such as a `dict` is required as value of a configuration setting, + then it shall not be provided through an environment variable. +- Configuration of secrets via secret files. +- Log output to standard out (STDOUT/`&1`) / standard error (STDERR/`&2`). +- Volumes for data and cache directories. + - Otherwise, no mounts shall be necessary. +- Runs a non-root user by default. +- One process / role for each instance. + +The container generally does not provide more features than the basic NetBox project itself provides. +It may provide additional Python dependencies than the upstream project, +so that all configurable features of NetBox can be used in the container without further modification. +The container may provide helpers, so that it feels and behaves like a native container. + +The container does not bundle any community plugins. + +## Maintenance Principals + +The main goals of maintaining _NetBox Docker_ are: + +- Keeping the project at a high quality level. +- Keeping the maintenance effort minimal. +- Coordinating development efforts. + +The following guidelines help us to achieve these goals: + +- As many maintenance tasks as possible shall be automated or scripted. +- All manual tasks must be documented. +- All changes are reviewed by at least one maintainer. + - Changes of maintainers are reviewed by at least one other maintainer. + (Except if there's only one maintainer left.) +- The infrastructure beyond what GitHub provides shall be kept to a minimum. + - On request, every maintainer shall get access to infrastructure that is beyond GitHub + (at the time of writing that's _Docker Hub_ and _Quay_ in particular). + +## Community Principals + +This project is developed by the NetBox community for the NetBox community. +We welcome contributions, as long as they are in line with the principals above. + +The maintainers of NetBox Docker are not the support team. +The community is expected to help each other out. + +Always remember: +Behind every screen (or screen-reader) on the other end is a fellow human. +Be nice and respectful, thankful for help, +and value ideas and contributions, +even when they don't fit the goals. diff --git a/README.md b/README.md index 18496a883..74b1ad774 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,21 @@ [![GitHub release (latest by date)](https://img.shields.io/github/v/release/netbox-community/netbox-docker)][github-release] [![GitHub stars](https://img.shields.io/github/stars/netbox-community/netbox-docker)][github-stargazers] ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed-raw/netbox-community/netbox-docker) -![Github release workflow](https://img.shields.io/github/workflow/status/netbox-community/netbox-docker/release) +![Github release workflow](https://img.shields.io/github/actions/workflow/status/netbox-community/netbox-docker/release.yml?branch=release) ![Docker Pulls](https://img.shields.io/docker/pulls/netboxcommunity/netbox) [![GitHub license](https://img.shields.io/github/license/netbox-community/netbox-docker)][netbox-docker-license] [The GitHub repository][netbox-docker-github] houses the components needed to build NetBox as a container. -Images are built regularly using the code in that repository and are pushed to [Docker Hub][netbox-dockerhub], [Quay.io][netbox-quayio] and [GitHub Container Registry][netbox-ghcr]. +Images are built regularly using the code in that repository +and are pushed to [Docker Hub][netbox-dockerhub], +[Quay.io][netbox-quayio] and [GitHub Container Registry][netbox-ghcr]. +_NetBox Docker_ is a project developed and maintained by the _NetBox_ community. Do you have any questions? -Before opening an issue on Github, -please join [our Slack][netbox-docker-slack] and ask for help in the [`#netbox-docker`][netbox-docker-slack-channel] channel. +Before opening an issue on GitHub, +please join [our Slack][netbox-docker-slack] +and ask for help in the [`#netbox-docker`][netbox-docker-slack-channel] channel, +or start a new [GitHub Discussion][github-discussions]. [github-stargazers]: https://github.com/netbox-community/netbox-docker/stargazers [github-release]: https://github.com/netbox-community/netbox-docker/releases @@ -24,6 +29,7 @@ please join [our Slack][netbox-docker-slack] and ask for help in the [`#netbox-d [netbox-docker-slack-channel]: https://netdev-community.slack.com/archives/C01P0GEVBU7 [netbox-slack-channel]: https://netdev-community.slack.com/archives/C01P0FRSXRV [netbox-docker-license]: https://github.com/netbox-community/netbox-docker/blob/release/LICENSE +[github-discussions]: https://github.com/netbox-community/netbox-docker/discussions ## Quickstart @@ -33,26 +39,25 @@ There is a more complete [_Getting Started_ guide on our wiki][wiki-getting-star ```bash git clone -b release https://github.com/netbox-community/netbox-docker.git cd netbox-docker -tee docker-compose.override.yml < We recommend to use either the `vX.Y.Z-a.b.c` tags or the `vX.Y-a.b.c` tags in production! -* `vX.Y.Z-a.b.c`, `vX.Y-a.b.c`: +- `vX.Y.Z-a.b.c`, `vX.Y-a.b.c`: These are release builds containing _NetBox version_ `vX.Y.Z`. They contain the support files of _NetBox Docker version_ `a.b.c`. You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility. These images are automatically built from [the corresponding releases of NetBox][netbox-releases]. -* `latest-a.b.c`: +- `latest-a.b.c`: These are release builds, containing the latest stable version of NetBox. They contain the support files of _NetBox Docker version_ `a.b.c`. You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility. - These images are automatically built from [the `master` branch of NetBox][netbox-master]. -* `snapshot-a.b.c`: +- `snapshot-a.b.c`: These are prerelease builds. They contain the support files of _NetBox Docker version_ `a.b.c`. You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility. - These images are automatically built from the [`develop` branch of NetBox][netbox-develop]. + These images are automatically built from the [`main` branch of NetBox][netbox-main]. For each of the above tag, there is an extra tag: -* `vX.Y.Z`, `vX.Y`: +- `vX.Y.Z`, `vX.Y`: This is the same version as `vX.Y.Z-a.b.c` (or `vX.Y-a.b.c`, respectively). - It always points to the latest version of _NetBox Docker_. -* `latest` +- `latest` This is the same version as `latest-a.b.c`. It always points to the latest version of _NetBox Docker_. -* `snapshot` +- `snapshot` This is the same version as `snapshot-a.b.c`. It always points to the latest version of _NetBox Docker_. [netbox-releases]: https://github.com/netbox-community/netbox/releases -[netbox-master]: https://github.com/netbox-community/netbox/tree/master -[netbox-develop]: https://github.com/netbox-community/netbox/tree/develop +[netbox-main]: https://github.com/netbox-community/netbox/tree/main ## Documentation Please refer [to our wiki on GitHub][netbox-docker-wiki] for further information on how to use the NetBox Docker image properly. -The wiki covers advanced topics such as using files for secrets, configuring TLS, deployment to Kubernetes, monitoring and configuring NAPALM and LDAP. +The wiki covers advanced topics such as using files for secrets, configuring TLS, deployment to Kubernetes, monitoring and configuring LDAP. Our wiki is a community effort. Feel free to correct errors, update outdated information or provide additional guides and insights. @@ -108,7 +110,7 @@ Feel free to correct errors, update outdated information or provide additional g Feel free to ask questions in our [GitHub Community][netbox-community] or [join our Slack][netbox-docker-slack] and ask [in our channel `#netbox-docker`][netbox-docker-slack-channel], -which is free to use and where there are almost always people online that can help you in the Slack channel. +which is free to use and where there are almost always people online that can help you. If you need help with using NetBox or developing for it or against it's API you may find [the `#netbox` channel][netbox-slack-channel] on the same Slack instance very helpful. @@ -119,16 +121,16 @@ you may find [the `#netbox` channel][netbox-slack-channel] on the same Slack ins This project relies only on _Docker_ and _docker-compose_ meeting these requirements: -* The _Docker version_ must be at least `20.10.10`. -* The _containerd version_ must be at least `1.5.6`. -* The _docker-compose version_ must be at least `1.28.0`. +- The _Docker version_ must be at least `20.10.10`. +- The _containerd version_ must be at least `1.5.6`. +- The _docker-compose version_ must be at least `1.28.0`. -To check the version installed on your system run `docker --version` and `docker-compose --version`. +To check the version installed on your system run `docker --version` and `docker compose version`. ## Updating Please read [the release notes][releases] carefully when updating to a new image version. -Note that the version of the NetBox Docker container image must stay in sync with the code. +Note that the version of the NetBox Docker container image must stay in sync with the version of the Git repository. If you update for the first time, be sure [to follow our _How To Update NetBox Docker_ guide in the wiki][netbox-docker-wiki-updating]. @@ -137,7 +139,8 @@ If you update for the first time, be sure [to follow our _How To Update NetBox D ## Rebuilding the Image -`./build.sh` can be used to rebuild the container image. See `./build.sh --help` for more information. +`./build.sh` can be used to rebuild the container image. +See `./build.sh --help` for more information or `./build-latest.sh` for an example. For more details on custom builds [consult our wiki][netbox-docker-wiki-build]. @@ -146,13 +149,15 @@ For more details on custom builds [consult our wiki][netbox-docker-wiki-build]. ## Tests We have a test script. -It runs NetBox's own unit tests and ensures that all initializers work: +It runs NetBox's own unit tests and ensures that NetBox starts: ```bash -IMAGE=netboxcommunity/netbox:latest ./test.sh +IMAGE=docker.io/netboxcommunity/netbox:latest ./test.sh ``` ## Support This repository is currently maintained by the community. +The community is expected to help each other. + Please consider sponsoring the maintainers of this project. diff --git a/VERSION b/VERSION index ccbccc3dc..a423d4217 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.2.0 +3.4.2 \ No newline at end of file diff --git a/actionlint.yml b/actionlint.yml new file mode 100644 index 000000000..28fadea16 --- /dev/null +++ b/actionlint.yml @@ -0,0 +1,5 @@ +--- +paths: + .github/workflows/**/*.{yml,yaml}: + ignore: + - ".*ubuntu-24.04-arm.*" diff --git a/build-functions/check-commands.sh b/build-functions/check-commands.sh new file mode 100644 index 000000000..e99849040 --- /dev/null +++ b/build-functions/check-commands.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +NEEDED_COMMANDS="curl jq docker skopeo" +for c in $NEEDED_COMMANDS; do + if ! command -v "$c" &>/dev/null; then + echo "⚠️ '$c' is not installed. Can't proceed with build." + exit 1 + fi +done diff --git a/build-functions/get-public-image-config.sh b/build-functions/get-public-image-config.sh index 4f5b6950a..0a19c3fc9 100644 --- a/build-functions/get-public-image-config.sh +++ b/build-functions/get-public-image-config.sh @@ -1,82 +1,18 @@ #!/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() { +check_if_tags_exists() { 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]}" + skopeo list-tags "docker://$image" | jq -r ".Tags | contains([\"$tag\"])" } -_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_image_label() { + local label=$1 + local image=$2 + skopeo inspect "docker://$image" | jq -r ".Labels[\"$label\"]" } -_get_layers() { +get_image_last_layer() { 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' + skopeo inspect "docker://$image" | jq -r ".Layers | last" } diff --git a/build-functions/gh-functions.sh b/build-functions/gh-functions.sh index 4928d0d22..4c04dc05e 100644 --- a/build-functions/gh-functions.sh +++ b/build-functions/gh-functions.sh @@ -19,3 +19,14 @@ gh_env() { echo "${@}" >>"${GITHUB_ENV}" fi } + +### +# Prints the output to the file defined in ${GITHUB_OUTPUT}. +# Only executes if ${GH_ACTION} is defined. +# Example Usage: gh_env "FOO_VAR=bar_value" +### +gh_out() { + if [ -n "${GH_ACTION}" ]; then + echo "${@}" >>"$GITHUB_OUTPUT" + fi +} diff --git a/build-latest.sh b/build-latest.sh index bcafda40e..0cc6c3800 100755 --- a/build-latest.sh +++ b/build-latest.sh @@ -1,26 +1,27 @@ #!/bin/bash # Builds the latest released version +# Check if we have everything needed for the build +source ./build-functions/check-commands.sh + +source ./build-functions/gh-functions.sh + echo "▶️ $0 $*" -### -# Check for the jq library needed for parsing JSON -### -if ! command -v jq; then - echo "⚠️ jq command missing from \$PATH!" - exit 1 -fi +CURL_ARGS=( + --silent +) ### -# Checking for the presence of GITHUB_OAUTH_CLIENT_ID -# and GITHUB_OAUTH_CLIENT_SECRET +# Checking for the presence of GITHUB_TOKEN ### -if [ -n "${GITHUB_OAUTH_CLIENT_ID}" ] && [ -n "${GITHUB_OAUTH_CLIENT_SECRET}" ]; then +if [ -n "${GITHUB_TOKEN}" ]; then echo "🗝 Performing authenticated Github API calls." - GITHUB_OAUTH_PARAMS="client_id=${GITHUB_OAUTH_CLIENT_ID}&client_secret=${GITHUB_OAUTH_CLIENT_SECRET}" + CURL_ARGS+=( + --header "Authorization: Bearer ${GITHUB_TOKEN}" + ) else echo "🕶 Performing unauthenticated Github API calls. This might result in lower Github rate limits!" - GITHUB_OAUTH_PARAMS="" fi ### @@ -42,31 +43,27 @@ fi ### ORIGINAL_GITHUB_REPO="netbox-community/netbox" GITHUB_REPO="${GITHUB_REPO-$ORIGINAL_GITHUB_REPO}" -URL_RELEASES="https://api.github.com/repos/${GITHUB_REPO}/releases?${GITHUB_OAUTH_PARAMS}" +URL_RELEASES="https://api.github.com/repos/${GITHUB_REPO}/releases" # Composing the JQ commans to extract the most recent version number JQ_LATEST="group_by(.prerelease) | .[] | sort_by(.published_at) | reverse | .[0] | select(.prerelease==${PRERELEASE-false}) | .tag_name" -CURL="curl -sS" +CURL="curl" # Querying the Github API to fetch the most recent version number -VERSION=$($CURL "${URL_RELEASES}" | jq -r "${JQ_LATEST}") +VERSION=$($CURL "${CURL_ARGS[@]}" "${URL_RELEASES}" | jq -r "${JQ_LATEST}" 2>/dev/null) ### # Check if the prerelease version is actually higher than stable version ### if [ "${PRERELEASE}" == "true" ]; then JQ_STABLE="group_by(.prerelease) | .[] | sort_by(.published_at) | reverse | .[0] | select(.prerelease==false) | .tag_name" - STABLE_VERSION=$($CURL "${URL_RELEASES}" | jq -r "${JQ_STABLE}") + STABLE_VERSION=$($CURL "${CURL_ARGS[@]}" "${URL_RELEASES}" | jq -r "${JQ_STABLE}" 2>/dev/null) - # shellcheck disable=SC2003 - MAJOR_STABLE=$(expr match "${STABLE_VERSION}" 'v\([0-9]\+\)') - # shellcheck disable=SC2003 - MINOR_STABLE=$(expr match "${STABLE_VERSION}" 'v[0-9]\+\.\([0-9]\+\)') - # shellcheck disable=SC2003 - MAJOR_UNSTABLE=$(expr match "${VERSION}" 'v\([0-9]\+\)') - # shellcheck disable=SC2003 - MINOR_UNSTABLE=$(expr match "${VERSION}" 'v[0-9]\+\.\([0-9]\+\)') + MAJOR_STABLE=$(expr "${STABLE_VERSION}" : 'v\([0-9]\+\)') + MINOR_STABLE=$(expr "${STABLE_VERSION}" : 'v[0-9]\+\.\([0-9]\+\)') + MAJOR_UNSTABLE=$(expr "${VERSION}" : 'v\([0-9]\+\)') + MINOR_UNSTABLE=$(expr "${VERSION}" : 'v[0-9]\+\.\([0-9]\+\)') if { [ "${MAJOR_STABLE}" -eq "${MAJOR_UNSTABLE}" ] && @@ -75,10 +72,7 @@ if [ "${PRERELEASE}" == "true" ]; then echo "❎ Latest unstable version '${VERSION}' is not higher than the latest stable version '$STABLE_VERSION'." if [ -z "$DEBUG" ]; then - if [ -n "${GH_ACTION}" ]; then - echo "::set-output name=skipped::true" - fi - + gh_out "skipped=true" exit 0 else echo "⚠️ Would exit here with code '0', but DEBUG is enabled." diff --git a/build.sh b/build.sh index 169568e03..ef4dc9607 100755 --- a/build.sh +++ b/build.sh @@ -6,96 +6,127 @@ echo "▶️ $0 $*" set -e if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then - echo "Usage: ${0} [--push]" - echo " branch The branch or tag to build. Required." - echo " --push Pushes the built Docker image to the registry." - echo "" - echo "You can use the following ENV variables to customize the build:" - echo " SRC_ORG Which fork of netbox to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO})." - echo " Default: netbox-community" - echo " SRC_REPO The name of the repository to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO})." - echo " Default: netbox" - echo " URL Where to fetch the code from." - echo " Must be a git repository. Can be private." - echo " Default: https://github.com/\${SRC_ORG}/\${SRC_REPO}.git" - echo " NETBOX_PATH The path where netbox will be checkout out." - echo " Must not be outside of the netbox-docker repository (because of Docker)!" - echo " Default: .netbox" - echo " SKIP_GIT If defined, git is not invoked and \${NETBOX_PATH} will not be altered." - echo " This may be useful, if you are manually managing the NETBOX_PATH." - echo " Default: undefined" - echo " TAG The version part of the docker tag." - echo " Default:" - echo " When =master: latest" - echo " When =develop: snapshot" - echo " Else: same as " - echo " IMAGE_NAMES The names used for the image including the registry" - echo " Used for tagging the image." - echo " Default: docker.io/netboxcommunity/netbox" - echo " Example: 'docker.io/netboxcommunity/netbox quay.io/netboxcommunity/netbox'" - echo " DOCKER_TAG The name of the tag which is applied to the image." - echo " Useful for pushing into another registry than hub.docker.com." - echo " Default: \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:\${TAG}" - echo " DOCKER_SHORT_TAG The name of the short tag which is applied to the" - echo " image. This is used to tag all patch releases to their" - echo " containing version e.g. v2.5.1 -> v2.5" - echo " Default: \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:." - echo " DOCKERFILE The name of Dockerfile to use." - echo " Default: Dockerfile" - echo " DOCKER_FROM The base image to use." - echo " Default: 'ubuntu:22.04'" - echo " BUILDX_PLATFORMS" - echo " Specifies the platform(s) to build the image for." - echo " Example: 'linux/amd64,linux/arm64'" - echo " Default: 'linux/amd64'" - echo " BUILDX_BUILDER_NAME" - echo " If defined, the image build will be assigned to the given builder." - echo " If you specify this variable, make sure that the builder exists." - echo " If this value is not defined, a new builx builder with the directory name of the" - echo " current directory (i.e. '$(basename "${PWD}")') is created." - echo " Example: 'clever_lovelace'" - echo " Default: undefined" - echo " BUILDX_REMOVE_BUILDER" - echo " If defined (and only if BUILDX_BUILDER_NAME is undefined)," - echo " then the buildx builder created by this script will be removed after use." - echo " This is useful if you build NetBox Docker on an automated system that does" - echo " not manage the builders for you." - echo " Example: 'on'" - echo " Default: undefined" - echo " HTTP_PROXY The proxy to use for http requests." - echo " Example: http://proxy.domain.tld:3128" - echo " Default: undefined" - echo " NO_PROXY Comma-separated list of domain extensions proxy should not be used for." - echo " Example: .domain1.tld,.domain2.tld" - echo " Default: undefined" - echo " DEBUG If defined, the script does not stop when certain checks are unsatisfied." - echo " Default: undefined" - echo " DRY_RUN Prints all build statements instead of running them." - 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 "Examples:" - echo " ${0} master" - echo " This will fetch the latest 'master' branch, build a Docker Image and tag it" - echo " 'netboxcommunity/netbox:latest'." - echo " ${0} develop" - echo " This will fetch the latest 'develop' branch, build a Docker Image and tag it" - echo " 'netboxcommunity/netbox:snapshot'." - echo " ${0} v2.6.6" - echo " This will fetch the 'v2.6.6' tag, build a Docker Image and tag it" - echo " 'netboxcommunity/netbox:v2.6.6' and 'netboxcommunity/netbox:v2.6'." - echo " ${0} develop-2.7" - echo " This will fetch the 'develop-2.7' branch, build a Docker Image and tag it" - echo " 'netboxcommunity/netbox:develop-2.7'." - echo " SRC_ORG=cimnine ${0} feature-x" - echo " This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git," - echo " build a Docker Image and tag it 'netboxcommunity/netbox:feature-x'." - echo " SRC_ORG=cimnine DOCKER_ORG=cimnine ${0} feature-x" - echo " This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git," - echo " build a Docker Image and tag it 'cimnine/netbox:feature-x'." + _BOLD=$(tput bold) + _GREEN=$(tput setaf 2) + _CYAN=$(tput setaf 6) + _CLEAR=$(tput sgr0) + + cat < [--push] + +branch The branch or tag to build. Required. +--push Pushes the built container image to the registry. + +${_BOLD}You can use the following ENV variables to customize the build:${_CLEAR} + +SRC_ORG Which fork of netbox to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO}). + ${_GREEN}Default:${_CLEAR} netbox-community + +SRC_REPO The name of the repository to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO}). + ${_GREEN}Default:${_CLEAR} netbox + +URL Where to fetch the code from. + Must be a git repository. Can be private. + ${_GREEN}Default:${_CLEAR} https://github.com/\${SRC_ORG}/\${SRC_REPO}.git + +NETBOX_PATH The path where netbox will be checkout out. + Must not be outside of the netbox-docker repository (because of Docker)! + ${_GREEN}Default:${_CLEAR} .netbox + +SKIP_GIT If defined, git is not invoked and \${NETBOX_PATH} will not be altered. + This may be useful, if you are manually managing the NETBOX_PATH. + ${_GREEN}Default:${_CLEAR} undefined + +TAG The version part of the image tag. + ${_GREEN}Default:${_CLEAR} + When =main: snapshot + Else: same as + +IMAGE_NAMES The names used for the image including the registry + Used for tagging the image. + ${_GREEN}Default:${_CLEAR} docker.io/netboxcommunity/netbox + ${_CYAN}Example:${_CLEAR} 'docker.io/netboxcommunity/netbox quay.io/netboxcommunity/netbox' + +DOCKER_TAG The name of the tag which is applied to the image. + Useful for pushing into another registry than hub.docker.com. + ${_GREEN}Default:${_CLEAR} \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:\${TAG} + +DOCKER_SHORT_TAG The name of the short tag which is applied to the + image. This is used to tag all patch releases to their + containing version e.g. v2.5.1 -> v2.5 + ${_GREEN}Default:${_CLEAR} \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:. + +DOCKERFILE The name of Dockerfile to use. + ${_GREEN}Default:${_CLEAR} Dockerfile + +DOCKER_FROM The base image to use. + ${_GREEN}Default:${_CLEAR} 'ubuntu:24.04' + +BUILDX_PLATFORM + Specifies the platform(s) to build the image for. + ${_CYAN}Example:${_CLEAR} 'linux/amd64,linux/arm64' + ${_GREEN}Default:${_CLEAR} 'linux/amd64' + +BUILDX_BUILDER_NAME + If defined, the image build will be assigned to the given builder. + If you specify this variable, make sure that the builder exists. + If this value is not defined, a new builx builder with the directory name of the + current directory (i.e. '$(basename "${PWD}")') is created." + ${_CYAN}Example:${_CLEAR} 'clever_lovelace' + ${_GREEN}Default:${_CLEAR} undefined + +BUILDX_REMOVE_BUILDER + If defined (and only if BUILDX_BUILDER_NAME is undefined), + then the buildx builder created by this script will be removed after use. + This is useful if you build NetBox Docker on an automated system that does + not manage the builders for you. + ${_CYAN}Example:${_CLEAR} 'on' + ${_GREEN}Default:${_CLEAR} undefined + +HTTP_PROXY The proxy to use for http requests. + ${_CYAN}Example:${_CLEAR} http://proxy.domain.tld:3128 + ${_GREEN}Default:${_CLEAR} undefined + +NO_PROXY Comma-separated list of domain extensions proxy should not be used for. + ${_CYAN}Example:${_CLEAR} .domain1.tld,.domain2.tld + ${_GREEN}Default:${_CLEAR} undefined + +DEBUG If defined, the script does not stop when certain checks are unsatisfied. + ${_GREEN}Default:${_CLEAR} undefined + +DRY_RUN Prints all build statements instead of running them. + ${_GREEN}Default:${_CLEAR} undefined + +GH_ACTION If defined, special 'echo' statements are enabled that set the + following environment variables in Github Actions: + - FINAL_DOCKER_TAG: The final value of the DOCKER_TAG env variable + ${_GREEN}Default:${_CLEAR} undefined + +CHECK_ONLY Only checks if the build is needed and sets the GH Action output. + ${_GREEN}Default:${_CLEAR} undefined + +${_BOLD}Examples:${_CLEAR} + +${0} main + This will fetch the latest 'main' branch, build a Docker Image and tag it + 'netboxcommunity/netbox:snapshot'. + +${0} v4.2.0 + This will fetch the 'v4.2.0' tag, build a Docker Image and tag it + 'netboxcommunity/netbox:v4.2.0' and 'netboxcommunity/netbox:v4.2'. + +${0} feature + This will fetch the 'feature' branch, build a Docker Image and tag it + 'netboxcommunity/netbox:feature'. + +SRC_ORG=cimnine ${0} feature-x + This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git, + build a Docker Image and tag it 'netboxcommunity/netbox:feature-x'. + +SRC_ORG=cimnine DOCKER_ORG=cimnine ${0} feature-x + This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git, + build a Docker Image and tag it 'cimnine/netbox:feature-x'. +END_OF_HELP if [ "${1}x" == "x" ]; then exit 1 @@ -104,6 +135,10 @@ if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then fi fi +# Check if we have everything needed for the build +source ./build-functions/check-commands.sh +# Load all build functions +source ./build-functions/get-public-image-config.sh source ./build-functions/gh-functions.sh IMAGE_NAMES="${IMAGE_NAMES-docker.io/netboxcommunity/netbox}" @@ -137,7 +172,7 @@ if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ]; then REMOTE_EXISTS=$(git ls-remote --heads --tags "${URL}" "${NETBOX_BRANCH}" | wc -l) if [ "${REMOTE_EXISTS}" == "0" ]; then echo "❌ Remote branch '${NETBOX_BRANCH}' not found in '${URL}'; Nothing to do" - gh_echo "::set-output name=skipped::true" + gh_out "skipped=true" exit 0 fi echo "🌐 Checking out '${NETBOX_BRANCH}' of NetBox from the url '${URL}' into '${NETBOX_PATH}'" @@ -182,13 +217,13 @@ fi # Determining the value for DOCKER_FROM ### if [ -z "$DOCKER_FROM" ]; then - DOCKER_FROM="ubuntu:22.04" + DOCKER_FROM="docker.io/ubuntu:24.04" fi ### # Variables for labelling the docker image ### -BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M+00:00')" +BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%S+00:00')" if [ -d ".git" ] && [ -z "${SKIP_GIT}" ]; then GIT_REF="$(git rev-parse HEAD)" @@ -220,10 +255,7 @@ DOCKER_REGISTRY="${DOCKER_REGISTRY-docker.io}" DOCKER_ORG="${DOCKER_ORG-netboxcommunity}" DOCKER_REPO="${DOCKER_REPO-netbox}" case "${NETBOX_BRANCH}" in -master) - TAG="${TAG-latest}" - ;; -develop) +main) TAG="${TAG-snapshot}" ;; *) @@ -239,7 +271,7 @@ TARGET_DOCKER_TAG_PROJECT="${TARGET_DOCKER_TAG}-${PROJECT_VERSION}" ### # composing the additional DOCKER_SHORT_TAG, -# i.e. "v2.6.1" becomes "v2.6", +# i.e. "v4.2.0" becomes "v4.2", # which is only relevant for version tags # Also let "latest" follow the highest version ### @@ -267,39 +299,37 @@ if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then done fi +FINAL_DOCKER_TAG="${IMAGE_NAME_TAGS[0]}" gh_env "FINAL_DOCKER_TAG=${IMAGE_NAME_TAGS[0]}" ### # Checking if the build is necessary, # meaning build only if one of those values changed: +# - a new tag is beeing created # - base image digest # - netbox git ref (Label: netbox.git-ref) # - netbox-docker git ref (Label: org.opencontainers.image.revision) ### -# Load information from registry (only for docker.io) +# Load information from registry (only for first registry in "IMAGE_NAMES") SHOULD_BUILD="false" BUILD_REASON="" if [ -z "${GH_ACTION}" ]; then # Asuming non Github builds should always proceed SHOULD_BUILD="true" BUILD_REASON="${BUILD_REASON} interactive" -elif [[ "${IMAGE_NAME_TAGS[0]}" = docker.io* ]]; then - source ./build-functions/get-public-image-config.sh - IFS=':' read -ra DOCKER_FROM_SPLIT <<<"${DOCKER_FROM}" - if ! [[ ${DOCKER_FROM_SPLIT[0]} =~ .*/.* ]]; then - # Need to use "library/..." for images the have no two part name - DOCKER_FROM_SPLIT[0]="library/${DOCKER_FROM_SPLIT[0]}" - fi - IFS='/' read -ra ORG_REPO <<<"${IMAGE_NAMES[0]}" - echo "Checking labels for '${ORG_REPO[1]}' and '${ORG_REPO[2]}'" - BASE_LAST_LAYER=$(get_image_last_layer "${DOCKER_FROM_SPLIT[0]}" "${DOCKER_FROM_SPLIT[1]}") - mapfile -t IMAGES_LAYERS_OLD < <(get_image_layers "${ORG_REPO[1]}"/"${ORG_REPO[2]}" "${TAG}") - NETBOX_GIT_REF_OLD=$(get_image_label netbox.git-ref "${ORG_REPO[1]}"/"${ORG_REPO[2]}" "${TAG}") - GIT_REF_OLD=$(get_image_label org.opencontainers.image.revision "${ORG_REPO[1]}"/"${ORG_REPO[2]}" "${TAG}") - - if ! printf '%s\n' "${IMAGES_LAYERS_OLD[@]}" | grep -q -P "^${BASE_LAST_LAYER}\$"; then +elif [ "false" == "$(check_if_tags_exists "${IMAGE_NAMES[0]}" "$TARGET_DOCKER_TAG")" ]; then + SHOULD_BUILD="true" + BUILD_REASON="${BUILD_REASON} newtag" +else + echo "Checking labels for '${FINAL_DOCKER_TAG}'" + BASE_LAST_LAYER=$(get_image_last_layer "${DOCKER_FROM}") + OLD_BASE_LAST_LAYER=$(get_image_label netbox.last-base-image-layer "${FINAL_DOCKER_TAG}") + NETBOX_GIT_REF_OLD=$(get_image_label netbox.git-ref "${FINAL_DOCKER_TAG}") + GIT_REF_OLD=$(get_image_label org.opencontainers.image.revision "${FINAL_DOCKER_TAG}") + + if [ "${BASE_LAST_LAYER}" != "${OLD_BASE_LAST_LAYER}" ]; then SHOULD_BUILD="true" - BUILD_REASON="${BUILD_REASON} debian" + BUILD_REASON="${BUILD_REASON} ubuntu" fi if [ "${NETBOX_GIT_REF}" != "${NETBOX_GIT_REF_OLD}" ]; then SHOULD_BUILD="true" @@ -309,20 +339,22 @@ elif [[ "${IMAGE_NAME_TAGS[0]}" = docker.io* ]]; then SHOULD_BUILD="true" BUILD_REASON="${BUILD_REASON} netbox-docker" fi -else - SHOULD_BUILD="true" - BUILD_REASON="${BUILD_REASON} no-check" fi if [ "${SHOULD_BUILD}" != "true" ]; then echo "Build skipped because sources didn't change" - echo "::set-output name=skipped::true" + gh_out "skipped=true" exit 0 # Nothing to do -> exit else - gh_echo "::set-output name=skipped::false" + gh_out "skipped=false" fi gh_echo "::endgroup::" +if [ "${CHECK_ONLY}" = "true" ]; then + echo "Only check if build needed was requested. Exiting" + exit 0 +fi + ### # Build the image ### @@ -360,6 +392,7 @@ fi if [ -n "${BUILD_REASON}" ]; then BUILD_REASON=$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<<"$BUILD_REASON") DOCKER_BUILD_ARGS+=(--label "netbox.build-reason=${BUILD_REASON}") + DOCKER_BUILD_ARGS+=(--label "netbox.last-base-image-layer=${BASE_LAST_LAYER}") fi # --build-arg diff --git a/configuration/configuration.py b/configuration/configuration.py index c8ddd14c2..8dfa736d3 100644 --- a/configuration/configuration.py +++ b/configuration/configuration.py @@ -7,12 +7,17 @@ import re from os import environ from os.path import abspath, dirname, join +from typing import Any, Callable, Tuple -# For reference see https://netbox.readthedocs.io/en/stable/configuration/ -# Based on https://github.com/netbox-community/netbox/blob/master/netbox/netbox/configuration.example.py +# For reference see https://docs.netbox.dev/en/stable/configuration/ +# Based on https://github.com/netbox-community/netbox/blob/develop/netbox/netbox/configuration_example.py + +### +# NetBox-Docker Helper functions +### # Read secret from file -def _read_secret(secret_name, default = None): +def _read_secret(secret_name: str, default: str | None = None) -> str | None: try: f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8') except EnvironmentError: @@ -21,6 +26,25 @@ def _read_secret(secret_name, default = None): with f: return f.readline().strip() +# If the `map_fn` isn't defined, then the value that is read from the environment (or the default value if not found) is returned. +# If the `map_fn` is defined, then `map_fn` is invoked and the value (that was read from the environment or the default value if not found) +# is passed to it as a parameter. The value returned from `map_fn` is then the return value of this function. +# The `map_fn` is not invoked, if the value (that was read from the environment or the default value if not found) is None. +def _environ_get_and_map(variable_name: str, default: str | None = None, map_fn: Callable[[str], Any | None] = None) -> Any | None: + env_value = environ.get(variable_name, default) + + if env_value == None: + return env_value + + if not map_fn: + return env_value + + return map_fn(env_value) + +_AS_BOOL = lambda value : value.lower() == 'true' +_AS_INT = lambda value : int(value) +_AS_LIST = lambda value : list(filter(None, value.split(' '))) + _BASE_DIR = dirname(dirname(abspath(__file__))) ######################### @@ -34,22 +58,27 @@ def _read_secret(secret_name, default = None): # # Example: ALLOWED_HOSTS = ['netbox.example.com', 'netbox.internal.local'] ALLOWED_HOSTS = environ.get('ALLOWED_HOSTS', '*').split(' ') +# ensure that '*' or 'localhost' is always in ALLOWED_HOSTS (needed for health checks) +if '*' not in ALLOWED_HOSTS and 'localhost' not in ALLOWED_HOSTS: + ALLOWED_HOSTS.append('localhost') # PostgreSQL database configuration. See the Django documentation for a complete list of available parameters: # https://docs.djangoproject.com/en/stable/ref/settings/#databases -DATABASE = { - 'NAME': environ.get('DB_NAME', 'netbox'), # Database name - 'USER': environ.get('DB_USER', ''), # PostgreSQL username - 'PASSWORD': _read_secret('db_password', environ.get('DB_PASSWORD', '')), - # PostgreSQL password - 'HOST': environ.get('DB_HOST', 'localhost'), # Database server - 'PORT': environ.get('DB_PORT', ''), # Database port (leave blank for default) - 'OPTIONS': {'sslmode': environ.get('DB_SSLMODE', 'prefer')}, - # Database connection SSLMODE - 'CONN_MAX_AGE': int(environ.get('DB_CONN_MAX_AGE', '300')), - # Max database connection age - 'DISABLE_SERVER_SIDE_CURSORS': environ.get('DB_DISABLE_SERVER_SIDE_CURSORS', 'False').lower() == 'true', - # Disable the use of server-side cursors transaction pooling +DATABASES = { + 'default': { + 'NAME': environ.get('DB_NAME', 'netbox'), # Database name + 'USER': environ.get('DB_USER', ''), # PostgreSQL username + 'PASSWORD': _read_secret('db_password', environ.get('DB_PASSWORD', '')), + # PostgreSQL password + 'HOST': environ.get('DB_HOST', 'localhost'), # Database server + 'PORT': environ.get('DB_PORT', ''), # Database port (leave blank for default) + 'OPTIONS': {'sslmode': environ.get('DB_SSLMODE', 'prefer')}, + # Database connection SSLMODE + 'CONN_MAX_AGE': _environ_get_and_map('DB_CONN_MAX_AGE', '300', _AS_INT), + # Max database connection age + 'DISABLE_SERVER_SIDE_CURSORS': _environ_get_and_map('DB_DISABLE_SERVER_SIDE_CURSORS', 'False', _AS_BOOL), + # Disable the use of server-side cursors transaction pooling + } } # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate @@ -58,19 +87,26 @@ def _read_secret(secret_name, default = None): REDIS = { 'tasks': { 'HOST': environ.get('REDIS_HOST', 'localhost'), - 'PORT': int(environ.get('REDIS_PORT', 6379)), + 'PORT': _environ_get_and_map('REDIS_PORT', 6379, _AS_INT), + 'SENTINELS': [tuple(uri.split(':')) for uri in _environ_get_and_map('REDIS_SENTINELS', '', _AS_LIST) if uri != ''], + 'SENTINEL_SERVICE': environ.get('REDIS_SENTINEL_SERVICE', 'default'), + 'SENTINEL_TIMEOUT': _environ_get_and_map('REDIS_SENTINEL_TIMEOUT', 10, _AS_INT), + 'USERNAME': environ.get('REDIS_USERNAME', ''), 'PASSWORD': _read_secret('redis_password', environ.get('REDIS_PASSWORD', '')), - 'DATABASE': int(environ.get('REDIS_DATABASE', 0)), - 'SSL': environ.get('REDIS_SSL', 'False').lower() == 'true', - 'INSECURE_SKIP_TLS_VERIFY': environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False').lower() == 'true', + 'DATABASE': _environ_get_and_map('REDIS_DATABASE', 0, _AS_INT), + 'SSL': _environ_get_and_map('REDIS_SSL', 'False', _AS_BOOL), + 'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False', _AS_BOOL), }, 'caching': { 'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')), - 'PORT': int(environ.get('REDIS_CACHE_PORT', environ.get('REDIS_PORT', 6379))), + 'PORT': _environ_get_and_map('REDIS_CACHE_PORT', environ.get('REDIS_PORT', '6379'), _AS_INT), + 'SENTINELS': [tuple(uri.split(':')) for uri in _environ_get_and_map('REDIS_CACHE_SENTINELS', '', _AS_LIST) if uri != ''], + 'SENTINEL_SERVICE': environ.get('REDIS_CACHE_SENTINEL_SERVICE', environ.get('REDIS_SENTINEL_SERVICE', 'default')), + 'USERNAME': environ.get('REDIS_CACHE_USERNAME', environ.get('REDIS_USERNAME', '')), 'PASSWORD': _read_secret('redis_cache_password', environ.get('REDIS_CACHE_PASSWORD', environ.get('REDIS_PASSWORD', ''))), - 'DATABASE': int(environ.get('REDIS_CACHE_DATABASE', 1)), - 'SSL': environ.get('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False')).lower() == 'true', - 'INSECURE_SKIP_TLS_VERIFY': environ.get('REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY', environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False')).lower() == 'true', + 'DATABASE': _environ_get_and_map('REDIS_CACHE_DATABASE', '1', _AS_INT), + 'SSL': _environ_get_and_map('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False'), _AS_BOOL), + 'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY', environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False'), _AS_BOOL), }, } @@ -80,6 +116,11 @@ def _read_secret(secret_name, default = None): # https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY SECRET_KEY = _read_secret('secret_key', environ.get('SECRET_KEY', '')) +API_TOKEN_PEPPERS = {} +if api_token_pepper := _read_secret('api_token_pepper_1', environ.get('API_TOKEN_PEPPER_1', '')): + API_TOKEN_PEPPERS.update({1: api_token_pepper}) + + ######################### # # @@ -87,170 +128,232 @@ def _read_secret(secret_name, default = None): # # ######################### -# Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of -# application errors (assuming correct email settings are provided). -ADMINS = [ - # ['John Doe', 'jdoe@example.com'], -] +# # Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of +# # application errors (assuming correct email settings are provided). +# ADMINS = [ +# # ['John Doe', 'jdoe@example.com'], +# ] -# URL schemes that are allowed within links in NetBox -ALLOWED_URL_SCHEMES = ( - 'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp', -) +if 'ALLOWED_URL_SCHEMES' in environ: + ALLOWED_URL_SCHEMES = _environ_get_and_map('ALLOWED_URL_SCHEMES', None, _AS_LIST) # Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same # content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP. -BANNER_TOP = environ.get('BANNER_TOP', '') -BANNER_BOTTOM = environ.get('BANNER_BOTTOM', '') +if 'BANNER_TOP' in environ: + BANNER_TOP = environ.get('BANNER_TOP', None) +if 'BANNER_BOTTOM' in environ: + BANNER_BOTTOM = environ.get('BANNER_BOTTOM', None) # Text to include on the login page above the login form. HTML is allowed. -BANNER_LOGIN = environ.get('BANNER_LOGIN', '') - -# Base URL path if accessing NetBox within a directory. For example, if installed at http://example.com/netbox/, set: -# BASE_PATH = 'netbox/' -BASE_PATH = environ.get('BASE_PATH', '') +if 'BANNER_LOGIN' in environ: + BANNER_LOGIN = environ.get('BANNER_LOGIN', None) # Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90) -CHANGELOG_RETENTION = int(environ.get('CHANGELOG_RETENTION', 90)) +if 'CHANGELOG_RETENTION' in environ: + CHANGELOG_RETENTION = _environ_get_and_map('CHANGELOG_RETENTION', None, _AS_INT) + +# Maximum number of days to retain job results (scripts and reports). Set to 0 to retain job results in the database indefinitely. (Default: 90) +if 'JOB_RETENTION' in environ: + JOB_RETENTION = _environ_get_and_map('JOB_RETENTION', None, _AS_INT) +# JOBRESULT_RETENTION was renamed to JOB_RETENTION in the v3.5.0 release of NetBox. For backwards compatibility, map JOBRESULT_RETENTION to JOB_RETENTION +elif 'JOBRESULT_RETENTION' in environ: + JOB_RETENTION = _environ_get_and_map('JOBRESULT_RETENTION', None, _AS_INT) # API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be # allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or # CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers -CORS_ORIGIN_ALLOW_ALL = environ.get('CORS_ORIGIN_ALLOW_ALL', 'False').lower() == 'true' -CORS_ORIGIN_WHITELIST = list(filter(None, environ.get('CORS_ORIGIN_WHITELIST', 'https://localhost').split(' '))) -CORS_ORIGIN_REGEX_WHITELIST = [re.compile(r) for r in list(filter(None, environ.get('CORS_ORIGIN_REGEX_WHITELIST', '').split(' ')))] - -# Cross-Site-Request-Forgery-Attack settings. If Netbox is sitting behind a reverse proxy, you might need to set the CSRF_TRUSTED_ORIGINS flag. -# Django 4.0 requires to specify the URL Scheme in this setting. An example environment variable could be specified like: -# CSRF_TRUSTED_ORIGINS=https://demo.netbox.dev http://demo.netbox.dev -CSRF_TRUSTED_ORIGINS = list(filter(None, environ.get('CSRF_TRUSTED_ORIGINS', '').split(' '))) +CORS_ORIGIN_ALLOW_ALL = _environ_get_and_map('CORS_ORIGIN_ALLOW_ALL', 'False', _AS_BOOL) +CORS_ORIGIN_WHITELIST = _environ_get_and_map('CORS_ORIGIN_WHITELIST', 'https://localhost', _AS_LIST) +CORS_ORIGIN_REGEX_WHITELIST = [re.compile(r) for r in _environ_get_and_map('CORS_ORIGIN_REGEX_WHITELIST', '', _AS_LIST)] # Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal -# sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging -# on a production system. -DEBUG = environ.get('DEBUG', 'False').lower() == 'true' +# sensitive information about your installation. Only enable debugging while performing testing. +# Never enable debugging on a production system. +DEBUG = _environ_get_and_map('DEBUG', 'False', _AS_BOOL) + +# This parameter serves as a safeguard to prevent some potentially dangerous behavior, +# such as generating new database schema migrations. +# Set this to True only if you are actively developing the NetBox code base. +DEVELOPER = _environ_get_and_map('DEVELOPER', 'False', _AS_BOOL) # Email settings EMAIL = { 'SERVER': environ.get('EMAIL_SERVER', 'localhost'), - 'PORT': int(environ.get('EMAIL_PORT', 25)), + 'PORT': _environ_get_and_map('EMAIL_PORT', 25, _AS_INT), 'USERNAME': environ.get('EMAIL_USERNAME', ''), 'PASSWORD': _read_secret('email_password', environ.get('EMAIL_PASSWORD', '')), - 'USE_SSL': environ.get('EMAIL_USE_SSL', 'False').lower() == 'true', - 'USE_TLS': environ.get('EMAIL_USE_TLS', 'False').lower() == 'true', + 'USE_SSL': _environ_get_and_map('EMAIL_USE_SSL', 'False', _AS_BOOL), + 'USE_TLS': _environ_get_and_map('EMAIL_USE_TLS', 'False', _AS_BOOL), 'SSL_CERTFILE': environ.get('EMAIL_SSL_CERTFILE', ''), 'SSL_KEYFILE': environ.get('EMAIL_SSL_KEYFILE', ''), - 'TIMEOUT': int(environ.get('EMAIL_TIMEOUT', 10)), # seconds + 'TIMEOUT': _environ_get_and_map('EMAIL_TIMEOUT', 10, _AS_INT), # seconds 'FROM_EMAIL': environ.get('EMAIL_FROM', ''), } # Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space within the global table # (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True. -ENFORCE_GLOBAL_UNIQUE = environ.get('ENFORCE_GLOBAL_UNIQUE', 'False').lower() == 'true' +if 'ENFORCE_GLOBAL_UNIQUE' in environ: + ENFORCE_GLOBAL_UNIQUE = _environ_get_and_map('ENFORCE_GLOBAL_UNIQUE', None, _AS_BOOL) + +# By default, netbox sends census reporting data using a single HTTP request each time a worker starts. +# This data enables the project maintainers to estimate how many NetBox deployments exist and track the adoption of new versions over time. +# The only data reported by this function are the NetBox version, Python version, and a pseudorandom unique identifier. +# To opt out of census reporting, set CENSUS_REPORTING_ENABLED to False. +if 'CENSUS_REPORTING_ENABLED' in environ: + CENSUS_REPORTING_ENABLED = _environ_get_and_map('CENSUS_REPORTING_ENABLED', None, _AS_BOOL) # Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and # by anonymous users. List models in the form `.`. Add '*' to this list to exempt all models. -EXEMPT_VIEW_PERMISSIONS = list(filter(None, environ.get('EXEMPT_VIEW_PERMISSIONS', '').split(' '))) +EXEMPT_VIEW_PERMISSIONS = _environ_get_and_map('EXEMPT_VIEW_PERMISSIONS', '', _AS_LIST) + +# HTTP proxies NetBox should use when sending outbound HTTP requests (e.g. for webhooks). +HTTP_PROXIES = { + 'http': environ.get('HTTP_PROXY', None), + 'https': environ.get('HTTPS_PROXY', None), +} + +# IP addresses recognized as internal to the system. The debugging toolbar will be available only to clients accessing +# NetBox from an internal IP. +INTERNAL_IPS = _environ_get_and_map('INTERNAL_IPS', '127.0.0.1 ::1', _AS_LIST) # Enable GraphQL API. -GRAPHQL_ENABLED = environ.get('GRAPHQL_ENABLED', 'True').lower() == 'true' +if 'GRAPHQL_ENABLED' in environ: + GRAPHQL_ENABLED = _environ_get_and_map('GRAPHQL_ENABLED', None, _AS_BOOL) -# Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs: -# https://docs.djangoproject.com/en/stable/topics/logging/ -LOGGING = {} +# # Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs: +# # https://docs.djangoproject.com/en/stable/topics/logging/ +# LOGGING = {} -# Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users -# are permitted to access most data in NetBox (excluding secrets) but not make any changes. -LOGIN_REQUIRED = environ.get('LOGIN_REQUIRED', 'False').lower() == 'true' +# Automatically reset the lifetime of a valid session upon each authenticated request. Enables users to remain +# authenticated to NetBox indefinitely. +LOGIN_PERSISTENCE = _environ_get_and_map('LOGIN_PERSISTENCE', 'False', _AS_BOOL) + +# When enabled, only authenticated users are permitted to access any part of NetBox. +# Disabling this will allow unauthenticated users to access most areas of NetBox (but not make any changes). +LOGIN_REQUIRED = _environ_get_and_map('LOGIN_REQUIRED', 'True', _AS_BOOL) # 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 = int(environ.get('LOGIN_TIMEOUT', 1209600)) +LOGIN_TIMEOUT = _environ_get_and_map('LOGIN_TIMEOUT', 1209600, _AS_INT) # Setting this to True will display a "maintenance mode" banner at the top of every page. -MAINTENANCE_MODE = environ.get('MAINTENANCE_MODE', 'False').lower() == 'true' +if 'MAINTENANCE_MODE' in environ: + MAINTENANCE_MODE = _environ_get_and_map('MAINTENANCE_MODE', None, _AS_BOOL) # Maps provider -MAPS_URL = environ.get('MAPS_URL', None) +if 'MAPS_URL' in environ: + MAPS_URL = environ.get('MAPS_URL', None) # An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g. # "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request # all objects by specifying "?limit=0". -MAX_PAGE_SIZE = int(environ.get('MAX_PAGE_SIZE', 1000)) +if 'MAX_PAGE_SIZE' in environ: + MAX_PAGE_SIZE = _environ_get_and_map('MAX_PAGE_SIZE', None, _AS_INT) # The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that # the default value of this setting is derived from the installed location. MEDIA_ROOT = environ.get('MEDIA_ROOT', join(_BASE_DIR, 'media')) # Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics' -METRICS_ENABLED = environ.get('METRICS_ENABLED', 'False').lower() == 'true' - -# Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM. -NAPALM_USERNAME = environ.get('NAPALM_USERNAME', '') -NAPALM_PASSWORD = _read_secret('napalm_password', environ.get('NAPALM_PASSWORD', '')) - -# NAPALM timeout (in seconds). (Default: 30) -NAPALM_TIMEOUT = int(environ.get('NAPALM_TIMEOUT', 30)) - -# NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must -# be provided as a dictionary. -NAPALM_ARGS = {} +METRICS_ENABLED = _environ_get_and_map('METRICS_ENABLED', 'False', _AS_BOOL) # Determine how many objects to display per page within a list. (Default: 50) -PAGINATE_COUNT = int(environ.get('PAGINATE_COUNT', 50)) +if 'PAGINATE_COUNT' in environ: + PAGINATE_COUNT = _environ_get_and_map('PAGINATE_COUNT', None, _AS_INT) -# Enable installed plugins. Add the name of each plugin to the list. -PLUGINS = [] +# # Enable installed plugins. Add the name of each plugin to the list. +# PLUGINS = [] -# Plugins configuration settings. These settings are used by various plugins that the user may have installed. -# Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings. -PLUGINS_CONFIG = { -} +# # Plugins configuration settings. These settings are used by various plugins that the user may have installed. +# # Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings. +# PLUGINS_CONFIG = { +# } # When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to # prefer IPv4 instead. -PREFER_IPV4 = environ.get('PREFER_IPV4', 'False').lower() == 'true' +if 'PREFER_IPV4' in environ: + PREFER_IPV4 = _environ_get_and_map('PREFER_IPV4', None, _AS_BOOL) + +# The default value for the amperage field when creating new power feeds. +if 'POWERFEED_DEFAULT_AMPERAGE' in environ: + POWERFEED_DEFAULT_AMPERAGE = _environ_get_and_map('POWERFEED_DEFAULT_AMPERAGE', None, _AS_INT) + +# The default value (percentage) for the max_utilization field when creating new power feeds. +if 'POWERFEED_DEFAULT_MAX_UTILIZATION' in environ: + POWERFEED_DEFAULT_MAX_UTILIZATION = _environ_get_and_map('POWERFEED_DEFAULT_MAX_UTILIZATION', None, _AS_INT) + +# The default value for the voltage field when creating new power feeds. +if 'POWERFEED_DEFAULT_VOLTAGE' in environ: + POWERFEED_DEFAULT_VOLTAGE = _environ_get_and_map('POWERFEED_DEFAULT_VOLTAGE', None, _AS_INT) # Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1. -RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = int(environ.get('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', 22)) -RACK_ELEVATION_DEFAULT_UNIT_WIDTH = int(environ.get('RACK_ELEVATION_DEFAULT_UNIT_WIDTH', 220)) +if 'RACK_ELEVATION_DEFAULT_UNIT_HEIGHT' in environ: + RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', None, _AS_INT) +if 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH' in environ: + RACK_ELEVATION_DEFAULT_UNIT_WIDTH = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_WIDTH', None, _AS_INT) # Remote authentication support -REMOTE_AUTH_ENABLED = environ.get('REMOTE_AUTH_ENABLED', 'False').lower() == 'true' -REMOTE_AUTH_BACKEND = environ.get('REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend') +REMOTE_AUTH_AUTO_CREATE_GROUPS = _environ_get_and_map('REMOTE_AUTH_AUTO_CREATE_GROUPS', 'False', _AS_BOOL) +REMOTE_AUTH_AUTO_CREATE_USER = _environ_get_and_map('REMOTE_AUTH_AUTO_CREATE_USER', 'False', _AS_BOOL) +REMOTE_AUTH_BACKEND = _environ_get_and_map('REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend', _AS_LIST) +REMOTE_AUTH_DEFAULT_GROUPS = _environ_get_and_map('REMOTE_AUTH_DEFAULT_GROUPS', '', _AS_LIST) +# REMOTE_AUTH_DEFAULT_PERMISSIONS = {} # dicts can't be configured via environment variables. See extra.py instead. +REMOTE_AUTH_ENABLED = _environ_get_and_map('REMOTE_AUTH_ENABLED', 'False', _AS_BOOL) +REMOTE_AUTH_GROUP_HEADER = _environ_get_and_map('REMOTE_AUTH_GROUP_HEADER', 'HTTP_REMOTE_USER_GROUP') +REMOTE_AUTH_GROUP_SEPARATOR = _environ_get_and_map('REMOTE_AUTH_GROUP_SEPARATOR', '|') +REMOTE_AUTH_GROUP_SYNC_ENABLED = _environ_get_and_map('REMOTE_AUTH_GROUP_SYNC_ENABLED', 'False', _AS_BOOL) REMOTE_AUTH_HEADER = environ.get('REMOTE_AUTH_HEADER', 'HTTP_REMOTE_USER') -REMOTE_AUTH_AUTO_CREATE_USER = environ.get('REMOTE_AUTH_AUTO_CREATE_USER', 'True').lower() == 'true' -REMOTE_AUTH_DEFAULT_GROUPS = list(filter(None, environ.get('REMOTE_AUTH_DEFAULT_GROUPS', '').split(' '))) +REMOTE_AUTH_USER_EMAIL = environ.get('REMOTE_AUTH_USER_EMAIL', 'HTTP_REMOTE_USER_EMAIL') +REMOTE_AUTH_USER_FIRST_NAME = environ.get('REMOTE_AUTH_USER_FIRST_NAME', 'HTTP_REMOTE_USER_FIRST_NAME') +REMOTE_AUTH_USER_LAST_NAME = environ.get('REMOTE_AUTH_USER_LAST_NAME', 'HTTP_REMOTE_USER_LAST_NAME') +REMOTE_AUTH_SUPERUSER_GROUPS = _environ_get_and_map('REMOTE_AUTH_SUPERUSER_GROUPS', '', _AS_LIST) +REMOTE_AUTH_SUPERUSERS = _environ_get_and_map('REMOTE_AUTH_SUPERUSERS', '', _AS_LIST) +REMOTE_AUTH_STAFF_GROUPS = _environ_get_and_map('REMOTE_AUTH_STAFF_GROUPS', '', _AS_LIST) +REMOTE_AUTH_STAFF_USERS = _environ_get_and_map('REMOTE_AUTH_STAFF_USERS', '', _AS_LIST) # 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 = environ.get('RELEASE_CHECK_URL', None) - -# The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of -# this setting is derived from the installed location. -REPORTS_ROOT = environ.get('REPORTS_ROOT', '/etc/netbox/reports') +# RELEASE_CHECK_URL = 'https://api.github.com/repos/netbox-community/netbox/releases' # Maximum execution time for background tasks, in seconds. -RQ_DEFAULT_TIMEOUT = int(environ.get('RQ_DEFAULT_TIMEOUT', 300)) +RQ_DEFAULT_TIMEOUT = _environ_get_and_map('RQ_DEFAULT_TIMEOUT', 300, _AS_INT) + +# The name to use for the csrf token cookie. +CSRF_COOKIE_NAME = environ.get('CSRF_COOKIE_NAME', 'csrftoken') + +# Cross-Site-Request-Forgery-Attack settings. If Netbox is sitting behind a reverse proxy, you might need to set the CSRF_TRUSTED_ORIGINS flag. +# Django 4.0 requires to specify the URL Scheme in this setting. An example environment variable could be specified like: +# CSRF_TRUSTED_ORIGINS=https://demo.netbox.dev http://demo.netbox.dev +CSRF_TRUSTED_ORIGINS = _environ_get_and_map('CSRF_TRUSTED_ORIGINS', '', _AS_LIST) + +# The name to use for the session cookie. +SESSION_COOKIE_NAME = environ.get('SESSION_COOKIE_NAME', 'sessionid') + +# If true, the `includeSubDomains` directive will be included in the HTTP Strict Transport Security (HSTS) header. +# This directive instructs the browser to apply the HSTS policy to all subdomains of the current domain. +SECURE_HSTS_INCLUDE_SUBDOMAINS = _environ_get_and_map('SECURE_HSTS_INCLUDE_SUBDOMAINS', 'False', _AS_BOOL) + +# If true, the `preload` directive will be included in the HTTP Strict Transport Security (HSTS) header. +# This directive instructs the browser to preload the site in HTTPS. Browsers that use the HSTS preload list will force the +# site to be accessed via HTTPS even if the user types HTTP in the address bar. +SECURE_HSTS_PRELOAD = _environ_get_and_map('SECURE_HSTS_PRELOAD', 'False', _AS_BOOL) + +# If set to a non-zero integer value, the SecurityMiddleware sets the HTTP Strict Transport Security (HSTS) header on all +# responses that do not already have it. This will instruct the browser that the website must be accessed via HTTPS, +# blocking any HTTP request. +SECURE_HSTS_SECONDS = _environ_get_and_map('SECURE_HSTS_SECONDS', 0, _AS_INT) -# The file path where custom scripts will be stored. A trailing slash is not needed. Note that the default value of -# this setting is derived from the installed location. -SCRIPTS_ROOT = environ.get('SCRIPTS_ROOT', '/etc/netbox/scripts') +# If true, all non-HTTPS requests will be automatically redirected to use HTTPS. +SECURE_SSL_REDIRECT = _environ_get_and_map('SECURE_SSL_REDIRECT', 'False', _AS_BOOL) # By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use # local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only # database access.) Note that the user as which NetBox runs must have read and write permissions to this path. -SESSION_FILE_PATH = environ.get('SESSIONS_ROOT', None) +SESSION_FILE_PATH = environ.get('SESSION_FILE_PATH', environ.get('SESSIONS_ROOT', None)) # Time zone (default: UTC) TIME_ZONE = environ.get('TIME_ZONE', 'UTC') -# Date/time formatting. See the following link for supported formats: -# https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date -DATE_FORMAT = environ.get('DATE_FORMAT', 'N j, Y') -SHORT_DATE_FORMAT = environ.get('SHORT_DATE_FORMAT', 'Y-m-d') -TIME_FORMAT = environ.get('TIME_FORMAT', 'g:i a') -SHORT_TIME_FORMAT = environ.get('SHORT_TIME_FORMAT', 'H:i:s') -DATETIME_FORMAT = environ.get('DATETIME_FORMAT', 'N j, Y g:i a') -SHORT_DATETIME_FORMAT = environ.get('SHORT_DATETIME_FORMAT', 'Y-m-d H:i') +# If true disables miscellaneous functionality which depends on access to the Internet. +ISOLATED_DEPLOYMENT = _environ_get_and_map('ISOLATED_DEPLOYMENT', 'False', _AS_BOOL) diff --git a/configuration/extra.py b/configuration/extra.py index 46f1877ed..8bd133716 100644 --- a/configuration/extra.py +++ b/configuration/extra.py @@ -15,12 +15,6 @@ # 'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp', # ) - -## NAPALM optional arguments (see http://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must -## be provided as a dictionary. -# NAPALM_ARGS = {} - - ## Enable installed plugins. Add the name of each plugin to the list. # from netbox.configuration.configuration import PLUGINS # PLUGINS.append('my_plugin') diff --git a/configuration/ldap/ldap_config.py b/configuration/ldap/ldap_config.py index 3071b4581..59e6aea8f 100644 --- a/configuration/ldap/ldap_config.py +++ b/configuration/ldap/ldap_config.py @@ -31,9 +31,12 @@ def _import_group_type(group_type_name): ldap.OPT_REFERRALS: 0 } -# Set the DN and password for the NetBox service account. -AUTH_LDAP_BIND_DN = environ.get('AUTH_LDAP_BIND_DN', '') -AUTH_LDAP_BIND_PASSWORD = _read_secret('auth_ldap_bind_password', environ.get('AUTH_LDAP_BIND_PASSWORD', '')) +AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = environ.get('AUTH_LDAP_BIND_AS_AUTHENTICATING_USER', 'False').lower() == 'true' + +# Set the DN and password for the NetBox service account if needed. +if not AUTH_LDAP_BIND_AS_AUTHENTICATING_USER: + AUTH_LDAP_BIND_DN = environ.get('AUTH_LDAP_BIND_DN', '') + AUTH_LDAP_BIND_PASSWORD = _read_secret('auth_ldap_bind_password', environ.get('AUTH_LDAP_BIND_PASSWORD', '')) # Set a string template that describes any user’s distinguished name based on the username. AUTH_LDAP_USER_DN_TEMPLATE = environ.get('AUTH_LDAP_USER_DN_TEMPLATE', None) @@ -46,20 +49,38 @@ def _import_group_type(group_type_name): # ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) LDAP_IGNORE_CERT_ERRORS = environ.get('LDAP_IGNORE_CERT_ERRORS', 'False').lower() == 'true' +# Include this setting if you want to validate the LDAP server certificates against a CA certificate directory on your server +# Note that this is a NetBox-specific setting which sets: +# ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, LDAP_CA_CERT_DIR) +LDAP_CA_CERT_DIR = environ.get('LDAP_CA_CERT_DIR', None) + +# Include this setting if you want to validate the LDAP server certificates against your own CA. +# Note that this is a NetBox-specific setting which sets: +# ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, LDAP_CA_CERT_FILE) +LDAP_CA_CERT_FILE = environ.get('LDAP_CA_CERT_FILE', None) + AUTH_LDAP_USER_SEARCH_BASEDN = environ.get('AUTH_LDAP_USER_SEARCH_BASEDN', '') AUTH_LDAP_USER_SEARCH_ATTR = environ.get('AUTH_LDAP_USER_SEARCH_ATTR', 'sAMAccountName') +AUTH_LDAP_USER_SEARCH_FILTER: str = environ.get( + 'AUTH_LDAP_USER_SEARCH_FILTER', f'({AUTH_LDAP_USER_SEARCH_ATTR}=%(user)s)' +) + AUTH_LDAP_USER_SEARCH = LDAPSearch( - AUTH_LDAP_USER_SEARCH_BASEDN, - ldap.SCOPE_SUBTREE, - "(" + AUTH_LDAP_USER_SEARCH_ATTR + "=%(user)s)" + AUTH_LDAP_USER_SEARCH_BASEDN, ldap.SCOPE_SUBTREE, AUTH_LDAP_USER_SEARCH_FILTER ) # This search ought to return all groups to which the user belongs. django_auth_ldap uses this to determine group # heirarchy. + AUTH_LDAP_GROUP_SEARCH_BASEDN = environ.get('AUTH_LDAP_GROUP_SEARCH_BASEDN', '') AUTH_LDAP_GROUP_SEARCH_CLASS = environ.get('AUTH_LDAP_GROUP_SEARCH_CLASS', 'group') -AUTH_LDAP_GROUP_SEARCH = LDAPSearch(AUTH_LDAP_GROUP_SEARCH_BASEDN, ldap.SCOPE_SUBTREE, - "(objectClass=" + AUTH_LDAP_GROUP_SEARCH_CLASS + ")") + +AUTH_LDAP_GROUP_SEARCH_FILTER: str = environ.get( + 'AUTH_LDAP_GROUP_SEARCH_FILTER', f'(objectclass={AUTH_LDAP_GROUP_SEARCH_CLASS})' +) +AUTH_LDAP_GROUP_SEARCH = LDAPSearch( + AUTH_LDAP_GROUP_SEARCH_BASEDN, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER +) AUTH_LDAP_GROUP_TYPE = _import_group_type(environ.get('AUTH_LDAP_GROUP_TYPE', 'GroupOfNamesType')) # Define a group required to login. @@ -88,3 +109,6 @@ def _import_group_type(group_type_name): "last_name": environ.get('AUTH_LDAP_ATTR_LASTNAME', 'sn'), "email": environ.get('AUTH_LDAP_ATTR_MAIL', 'mail') } + +# Update user object with the latest values from the LDAP directory every time the user logs in. +AUTH_LDAP_ALWAYS_UPDATE_USER = environ.get('AUTH_LDAP_ALWAYS_UPDATE_USER', 'True').lower() == 'true' diff --git a/docker-compose.override.yml.example b/docker-compose.override.yml.example index f08d6c017..d7ef96167 100644 --- a/docker-compose.override.yml.example +++ b/docker-compose.override.yml.example @@ -1,5 +1,22 @@ -version: '3.4' services: netbox: ports: - - 8000:8080 + - "8000:8080" + # If you want the Nginx unit status page visible from the + # outside of the container add the following port mapping: + # - "8001:8081" + # healthcheck: + # Time for which the health check can fail after the container is started. + # This depends mostly on the performance of your database. On the first start, + # when all tables need to be created the start_period should be higher than on + # subsequent starts. For the first start after major version upgrades of NetBox + # the start_period might also need to be set higher. + # Default value in our docker-compose.yml is 60s + # start_period: 90s + # environment: + # SKIP_SUPERUSER: "false" + # SUPERUSER_API_TOKEN: "" + # SUPERUSER_EMAIL: "" + # SUPERUSER_NAME: "" + # SUPERUSER_PASSWORD: "" + diff --git a/docker-compose.test.override.yml b/docker-compose.test.override.yml new file mode 100644 index 000000000..749e11a00 --- /dev/null +++ b/docker-compose.test.override.yml @@ -0,0 +1,5 @@ +services: + netbox: + ports: + - "127.0.0.1:8000:8080" + diff --git a/docker-compose.test.yml b/docker-compose.test.yml index 3e4686e32..9388022cd 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -1,38 +1,61 @@ -version: '3.4' services: - netbox: - image: ${IMAGE-netboxcommunity/netbox:latest} + netbox: &netbox + image: ${IMAGE-docker.io/netboxcommunity/netbox:latest} depends_on: - - postgres - - redis - - redis-cache + postgres: + condition: service_healthy + redis: + condition: service_healthy + redis-cache: + condition: service_healthy env_file: env/netbox.env - environment: - SKIP_STARTUP_SCRIPTS: ${SKIP_STARTUP_SCRIPTS-false} - user: 'unit:root' + user: "unit:root" volumes: - - ./configuration:/etc/netbox/config:z,ro - - ./test-configuration/logging.py:/etc/netbox/config/logging.py:z,ro - - ./reports:/etc/netbox/reports:z,ro - - ./scripts:/etc/netbox/scripts:z,ro - - netbox-media-files:/opt/netbox/netbox/media:z + - ./test-configuration/test_config.py:/etc/netbox/config/test_config.py:z,ro + healthcheck: + test: curl -f http://localhost:8080/login/ || exit 1 + start_period: ${NETBOX_START_PERIOD-120s} + timeout: 3s + interval: 15s + netbox-worker: + <<: *netbox + command: + - /opt/netbox/venv/bin/python + - /opt/netbox/netbox/manage.py + - rqworker + healthcheck: + test: ps -aux | grep -v grep | grep -q rqworker || exit 1 + start_period: 40s + timeout: 3s + interval: 15s + postgres: - image: postgres:14-alpine + image: docker.io/postgres:17-alpine env_file: env/postgres.env - redis: - image: redis:7-alpine + healthcheck: + test: pg_isready -q -t 2 -d $$POSTGRES_DB -U $$POSTGRES_USER ## $$ because of docker-compose + start_period: 20s + interval: 1s + timeout: 5s + retries: 5 + + redis: &redis + image: docker.io/valkey/valkey:8.1-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 + - sh + - -c # this is to evaluate the $REDIS_PASSWORD from the env + - valkey-server --save "" --appendonly no --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose env_file: env/redis.env + healthcheck: + test: '[ $$(valkey-cli --pass "$${REDIS_PASSWORD}" ping) = ''PONG'' ]' + start_period: 5s + timeout: 3s + interval: 1s + retries: 5 redis-cache: - image: redis:7-alpine - command: - - sh - - -c # this is to evaluate the $REDIS_PASSWORD from the env - - redis-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose + <<: *redis env_file: env/redis-cache.env + volumes: netbox-media-files: driver: local diff --git a/docker-compose.yml b/docker-compose.yml index 029d87e7c..0af361cc2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,65 +1,87 @@ -version: '3.4' services: netbox: &netbox - image: netboxcommunity/netbox:${VERSION-v3.3-2.2.0} + image: docker.io/netboxcommunity/netbox:${VERSION-v4.4-3.4.1} depends_on: - - postgres - - redis - - redis-cache - - netbox-worker + - postgres + - redis + - redis-cache env_file: env/netbox.env - user: 'unit:root' + user: "unit:root" + healthcheck: + test: curl -f http://localhost:8080/login/ || exit 1 + start_period: 90s + timeout: 3s + interval: 15s volumes: - - ./configuration:/etc/netbox/config:z,ro - - ./reports:/etc/netbox/reports:z,ro - - ./scripts:/etc/netbox/scripts:z,ro - - netbox-media-files:/opt/netbox/netbox/media:z + - ./configuration:/etc/netbox/config:z,ro + - netbox-media-files:/opt/netbox/netbox/media:rw + - netbox-reports-files:/opt/netbox/netbox/reports:rw + - netbox-scripts-files:/opt/netbox/netbox/scripts:rw netbox-worker: <<: *netbox depends_on: - - redis - - postgres + netbox: + condition: service_healthy command: - - /opt/netbox/venv/bin/python - - /opt/netbox/netbox/manage.py - - rqworker - netbox-housekeeping: - <<: *netbox - depends_on: - - redis - - postgres - command: - - /opt/netbox/housekeeping.sh + - /opt/netbox/venv/bin/python + - /opt/netbox/netbox/manage.py + - rqworker + healthcheck: + test: ps -aux | grep -v grep | grep -q rqworker || exit 1 + start_period: 20s + timeout: 3s + interval: 15s # postgres postgres: - image: postgres:14-alpine + image: docker.io/postgres:17-alpine + healthcheck: + test: pg_isready -q -t 2 -d $$POSTGRES_DB -U $$POSTGRES_USER + start_period: 20s + timeout: 30s + interval: 10s + retries: 5 env_file: env/postgres.env volumes: - - netbox-postgres-data:/var/lib/postgresql/data + - netbox-postgres-data:/var/lib/postgresql/data # redis redis: - image: redis:7-alpine + image: docker.io/valkey/valkey:8.1-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 + - sh + - -c # this is to evaluate the $REDIS_PASSWORD from the env + - valkey-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose + healthcheck: &redis-healthcheck + test: '[ $$(valkey-cli --pass "$${REDIS_PASSWORD}" ping) = ''PONG'' ]' + start_period: 5s + timeout: 3s + interval: 1s + retries: 5 env_file: env/redis.env volumes: - - netbox-redis-data:/data + - netbox-redis-data:/data redis-cache: - image: redis:7-alpine + image: docker.io/valkey/valkey:8.1-alpine command: - - sh - - -c # this is to evaluate the $REDIS_PASSWORD from the env - - redis-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose + - sh + - -c # this is to evaluate the $REDIS_PASSWORD from the env + - valkey-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose + healthcheck: *redis-healthcheck env_file: env/redis-cache.env + volumes: + - netbox-redis-cache-data:/data volumes: netbox-media-files: driver: local netbox-postgres-data: driver: local + netbox-redis-cache-data: + driver: local netbox-redis-data: driver: local + netbox-reports-files: + driver: local + netbox-scripts-files: + driver: local diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 6afa1eab9..fa5930d49 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -46,6 +46,8 @@ if ! ./manage.py migrate --check >/dev/null 2>&1; then ./manage.py remove_stale_contenttypes --no-input echo "⚙️ Removing expired user sessions" ./manage.py clearsessions + echo "⚙️ Building search index (lazy)" + ./manage.py reindex --lazy fi # Create Superuser if required @@ -70,24 +72,24 @@ else fi ./manage.py shell --interface python <