├── .editorconfig ├── .gitea └── workflows │ ├── build_release.yml │ ├── check_code.yml │ └── update_changelog.yml ├── .github ├── issue_template │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── build_release.yml │ └── check_code.yml ├── .gitignore ├── .tool-versions ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── dev ├── .env.example ├── Dockerfile ├── compose.yml ├── configuration.py ├── sync.yml ├── sync2.yml └── zones │ ├── 1.168.192.in-addr.arpa.yaml │ ├── example.com.yaml │ ├── test.example.com.yaml │ └── view.example.com.yaml ├── examples ├── netbox-to-cloudflare.yml └── netbox-to-yaml.yml ├── justfile ├── pyproject.toml ├── renovate.json ├── src └── octodns_netbox_dns │ ├── __about__.py │ └── __init__.py └── tests ├── test_escaple_semicolon.py ├── test_make_absolute.py └── test_records.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.{yml,yaml,xml,hcl,tf}] 15 | indent_size = 2 16 | 17 | [*.{bat,cmd,ps1,ps[md]1}] 18 | end_of_line = crlf 19 | -------------------------------------------------------------------------------- /.gitea/workflows/build_release.yml: -------------------------------------------------------------------------------- 1 | name: build pypackage and create release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | pull_request: 9 | branches: [main, master] 10 | 11 | jobs: 12 | release-github: 13 | runs-on: ubuntu-latest 14 | env: 15 | HATCH_INDEX_REPO: main 16 | HATCH_INDEX_USER: __token__ 17 | HATCH_INDEX_AUTH: ${{ secrets.PACKAGE_TOKEN }} 18 | steps: 19 | - name: checkout code 20 | uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | 24 | - uses: actions/setup-python@v5 25 | with: 26 | python-version: "3.11" 27 | 28 | - name: setup go 29 | uses: actions/setup-go@v5 30 | with: 31 | go-version: ">=1.20" 32 | 33 | - name: install hatch 34 | run: pip install -U hatch hatchling 35 | 36 | - name: build package 37 | run: hatch build --clean 38 | 39 | - name: install auto-changelog 40 | run: npm install auto-changelog 41 | 42 | - name: generate changelog 43 | run: >- 44 | npx auto-changelog -t keepachangelog 45 | --commit-limit 50 --backfill-limit 50 46 | --ignore-commit-pattern '[Bb]ump version|[Uu]pdate changelog|[Mm]erge pull request' 47 | 48 | - name: get release notes 49 | id: release-notes 50 | uses: olofvndrhr/releasenote-gen@v1 51 | 52 | - name: publish package 53 | if: gitea.event_name != 'pull_request' 54 | run: hatch publish --yes --no-prompt 55 | 56 | - name: create gitea release 57 | uses: https://gitea.com/actions/release-action@main 58 | if: gitea.event_name != 'pull_request' 59 | with: 60 | title: ${{ gitea.ref_name }} 61 | body: ${{ steps.release-notes.outputs.releasenotes }} 62 | files: |- 63 | dist/** 64 | -------------------------------------------------------------------------------- /.gitea/workflows/check_code.yml: -------------------------------------------------------------------------------- 1 | name: check code 2 | 3 | on: 4 | push: 5 | branches: [main, master, dev] 6 | 7 | pull_request: 8 | branches: [main, master, dev] 9 | 10 | jobs: 11 | check-code: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: checkout code 15 | uses: actions/checkout@v3 16 | 17 | - uses: actions/setup-python@v5 18 | with: 19 | python-version: "3.11" 20 | 21 | - name: install hatch 22 | run: pip install -U hatch 23 | 24 | - name: test codestyle 25 | run: hatch run lint:style 26 | 27 | - name: test typing 28 | run: hatch run lint:typing 29 | 30 | - name: run tests 31 | run: hatch run default:test 32 | -------------------------------------------------------------------------------- /.gitea/workflows/update_changelog.yml: -------------------------------------------------------------------------------- 1 | name: update changelog 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | update-changelog: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: checkout code 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | ref: main 17 | 18 | - name: install auto-changelog 19 | run: npm install auto-changelog 20 | 21 | - name: generate changelog 22 | run: >- 23 | npx auto-changelog -t keepachangelog 24 | --commit-limit 50 --backfill-limit 50 25 | --ignore-commit-pattern '[Bb]ump version|[Uu]pdate changelog|[Mm]erge pull request' 26 | 27 | - name: commit and push changelog 28 | uses: EndBug/add-and-commit@v9 29 | with: 30 | add: CHANGELOG.md 31 | message: "[bot] update changelog" 32 | author_name: actions-bot 33 | author_email: actions@bots.44net.ch 34 | -------------------------------------------------------------------------------- /.github/issue_template/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help improve the tool 4 | title: "[BUG] " 5 | labels: bug 6 | assignees: olofvndrhr 7 | 8 | --- 9 | 10 | **System info (please complete the following information):** 11 | 12 | - Host: [e.g. Linux/Debian or Docker] 13 | - `octodns-netbox-dns` version [e.g. v2.1.5] 14 | - `netbox-dns` version [e.g. v2.1.5] 15 | - Netbox version [e.g. v2.1.5] 16 | 17 | **Describe the bug** 18 | A clear and concise description of what the bug is. 19 | 20 | **To Reproduce** 21 | Steps to reproduce the behavior: 22 | 23 | 1. Go to '...' 24 | 2. Click on '....' 25 | 3. Scroll down to '....' 26 | 4. See error 27 | 28 | **Expected behavior** 29 | A clear and concise description of what you expected to happen. 30 | 31 | **Screenshots** 32 | If applicable, add screenshots to help explain your problem. 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/issue_template/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE] " 5 | labels: feature-request 6 | assignees: olofvndrhr 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/build_release.yml: -------------------------------------------------------------------------------- 1 | name: build pypackage and create release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | pull_request: 9 | branches: [main, master] 10 | 11 | jobs: 12 | release-github: 13 | runs-on: ubuntu-latest 14 | env: 15 | HATCH_INDEX_REPO: main 16 | HATCH_INDEX_USER: __token__ 17 | HATCH_INDEX_AUTH: ${{ secrets.PACKAGE_TOKEN }} 18 | steps: 19 | - name: checkout code 20 | uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | 24 | - uses: actions/setup-python@v5 25 | with: 26 | python-version: "3.11" 27 | 28 | - name: install hatch 29 | run: pip install -U hatch hatchling 30 | 31 | - name: build package 32 | run: hatch build --clean 33 | 34 | - name: install auto-changelog 35 | run: npm install auto-changelog 36 | 37 | - name: generate changelog 38 | run: >- 39 | npx auto-changelog -t keepachangelog 40 | --commit-limit 50 --backfill-limit 50 41 | --ignore-commit-pattern '[Bb]ump version|[Uu]pdate changelog|[Mm]erge pull request' 42 | 43 | - name: get release notes 44 | id: release-notes 45 | uses: olofvndrhr/releasenote-gen@v1 46 | 47 | - name: create github release 48 | uses: ncipollo/release-action@v1 49 | if: github.event_name != 'pull_request' 50 | with: 51 | name: ${{ github.ref_name }} 52 | body: ${{ steps.release-notes.outputs.releasenotes }} 53 | artifacts: |- 54 | dist/** 55 | -------------------------------------------------------------------------------- /.github/workflows/check_code.yml: -------------------------------------------------------------------------------- 1 | name: check code 2 | 3 | on: 4 | push: 5 | branches: [main, master, dev] 6 | 7 | pull_request: 8 | branches: [main, master, dev] 9 | 10 | jobs: 11 | check-code: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: checkout code 15 | uses: actions/checkout@v3 16 | 17 | - uses: actions/setup-python@v5 18 | with: 19 | python-version: "3.11" 20 | 21 | - name: install hatch 22 | run: pip install -U hatch 23 | 24 | - name: test codestyle 25 | run: hatch run lint:style 26 | 27 | - name: test typing 28 | run: hatch run lint:typing 29 | 30 | - name: run tests 31 | run: hatch run default:test 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | test.ipynb 4 | test.py 5 | test.sh 6 | downloads/ 7 | venv 8 | .mypy_cache/ 9 | .ruff_cache/ 10 | __pycache__/ 11 | .pytest_cache/ 12 | 13 | # dev 14 | dev/db-data 15 | dev/redis-data 16 | dev/netbox-data 17 | dev/output 18 | 19 | .stignore 20 | 21 | ### Python template 22 | # Byte-compiled / optimized / DLL files 23 | __pycache__/ 24 | *.py[cod] 25 | *$py.class 26 | 27 | # C extensions 28 | *.so 29 | 30 | # Distribution / packaging 31 | .Python 32 | build/ 33 | develop-eggs/ 34 | dist/ 35 | downloads/ 36 | eggs/ 37 | .eggs/ 38 | lib/ 39 | lib64/ 40 | parts/ 41 | sdist/ 42 | var/ 43 | wheels/ 44 | share/python-wheels/ 45 | *.egg-info/ 46 | .installed.cfg 47 | *.egg 48 | MANIFEST 49 | 50 | # PyInstaller 51 | # Usually these files are written by a python script from a template 52 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 53 | *.manifest 54 | *.spec 55 | 56 | # Installer logs 57 | pip-log.txt 58 | pip-delete-this-directory.txt 59 | 60 | # Unit test / coverage reports 61 | htmlcov/ 62 | .tox/ 63 | .nox/ 64 | .coverage 65 | .coverage.* 66 | .cache 67 | nosetests.xml 68 | coverage.xml 69 | *.cover 70 | *.py,cover 71 | .hypothesis/ 72 | .pytest_cache/ 73 | cover/ 74 | 75 | # Translations 76 | *.mo 77 | *.pot 78 | 79 | # Django stuff: 80 | *.log 81 | local_settings.py 82 | db.sqlite3 83 | db.sqlite3-journal 84 | 85 | # Flask stuff: 86 | instance/ 87 | .webassets-cache 88 | 89 | # Scrapy stuff: 90 | .scrapy 91 | 92 | # Sphinx documentation 93 | docs/_build/ 94 | 95 | # PyBuilder 96 | .pybuilder/ 97 | target/ 98 | 99 | # Jupyter Notebook 100 | .ipynb_checkpoints 101 | 102 | # IPython 103 | profile_default/ 104 | ipython_config.py 105 | 106 | # pyenv 107 | # For a library or package, you might want to ignore these files since the code is 108 | # intended to run in multiple environments; otherwise, check them in: 109 | # .python-version 110 | 111 | # pipenv 112 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 113 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 114 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 115 | # install all needed dependencies. 116 | #Pipfile.lock 117 | 118 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 119 | __pypackages__/ 120 | 121 | # Celery stuff 122 | celerybeat-schedule 123 | celerybeat.pid 124 | 125 | # SageMath parsed files 126 | *.sage.py 127 | 128 | # Environments 129 | .env 130 | .venv 131 | env/ 132 | venv/ 133 | ENV/ 134 | env.bak/ 135 | venv.bak/ 136 | 137 | # Spyder project settings 138 | .spyderproject 139 | .spyproject 140 | 141 | # Rope project settings 142 | .ropeproject 143 | 144 | # mkdocs documentation 145 | /site 146 | 147 | # mypy 148 | .mypy_cache/ 149 | .dmypy.json 150 | dmypy.json 151 | 152 | # Pyre type checker 153 | .pyre/ 154 | 155 | # pytype static type analyzer 156 | .pytype/ 157 | 158 | # Cython debug symbols 159 | cython_debug/ 160 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | just 1.40.0 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). 9 | 10 | ## [v0.3.12](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.11...v0.3.12) - 2025-03-04 11 | 12 | ### Commits 13 | 14 | - update supported record types and add tests for all supported types [`e10a710`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/e10a710f45c39b801d88b6e85fd8dd782f6a0506) 15 | - add all record types for test setup [`5f22212`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/5f22212319b633589b857b483490b5f322d3b758) 16 | - fix CAA record and deprecate SPF record type [`d8e9ed0`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/d8e9ed0f108c13b7b19cc230a03b60d6a3719f78) 17 | - update README with limitations [`7247896`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/72478964ace09c3de055ffaee292a2cf16ac83b8) 18 | - fix spf test [`f70eba8`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/f70eba8b5564d4f4b6c6276f301a04725f962290) 19 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v4.2.4 [`2532a60`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/2532a603097f0aeac04c3d7777f641b7991166cd) 20 | - remove NS record from semicolon unescape [`ec69f11`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/ec69f11defc1f7b932f1e79953f5780ff17fa8ef) 21 | - fix unused parameter [`f09187b`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/f09187b672e564022e3c854a3e9f9049bae06445) 22 | 23 | ## [v0.3.11](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.10...v0.3.11) - 2025-02-19 24 | 25 | ### Fixed 26 | 27 | - Fix SSHFP fingerprint hexadecimal value [`#1240`](https://github.com/octodns/octodns/issues/1240) 28 | 29 | ### Commits 30 | 31 | - add new test for PTR records [`91246cc`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/91246cc53ffccc23ccb35c7c642492bda8e90290) 32 | - update sync config for tests [`dbfbb8f`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/dbfbb8f8b56dcbf4f4c83665f50c5585f34705e6) 33 | - fix test for ptr record [`5f7e34c`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/5f7e34c24211bdcc2e6939d9830eb5d7fe873317) 34 | - update coverage and mypy [`4b84aa4`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/4b84aa4e135a5e0176a568c54016d3906f3344d1) 35 | - fix gitignore double entry [`01881e7`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/01881e7067b9273973d9599836b32924fd8d6ef8) 36 | - chore(deps): update dependency pytest to v8.3.4 [`37ce27a`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/37ce27aa4875d92d612319f11f5a5d91a60be97b) 37 | - chore(deps): update dependency just to v1.39.0 [`d92731f`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/d92731f241590edeaca2226722cdd37898937f0f) 38 | - chore(deps): update dependency pytest to v8.3.4 [`c4a41eb`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/c4a41eb2dd51aecf453e592fd82361a73f770466) 39 | - chore(deps): update dependency ruff to v0.9.6 [`f9f63a2`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/f9f63a2c66b2e727f49c577ba19f0dece29cd8f4) 40 | - chore(deps): update dependency octodns to v1.11.0 [`3078dfb`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/3078dfb472990db9774a805e111b79a07f3ff863) 41 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v4.2.3 [`b4ece3d`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/b4ece3d2ee49a0a1c7747ee612505d69d3a686d8) 42 | - fix: Remove PTR from _include_change [`69bdc15`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/69bdc15374d2517fdf2fa2ebd5cc1e7d26b1be29) 43 | 44 | ## [v0.3.10](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.9...v0.3.10) - 2025-01-09 45 | 46 | ### Commits 47 | 48 | - fix type error on missing None [`a61e6ab`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/a61e6ab69f1ca7de4d89bda2a2b570a76d264b57) 49 | - Make CNAME '@' values absolute [`d75568d`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/d75568dc8ff2a04ba4f9880a1763f098f13b024e) 50 | - allow insecure_request to netbox [`fc8e50e`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/fc8e50e32b268a8969fcc30f104f05ece2c9e6f8) 51 | - Update README.md [`4423c85`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/4423c85ac189f063b73716777d82425d026bd489) 52 | - fix typo [`d941437`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/d9414371cbe7cbbb568ab515ab4fc60ffb0bca13) 53 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v4.1.6 [`7ecc79d`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/7ecc79ddb567fe40d14c198742936cf8bc8d8674) 54 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v4.1.5 [`b4efc26`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/b4efc26660a6834584a5675f00728898c0d8342f) 55 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v4.1.4 [`58549b5`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/58549b52244c03e5cb775558534eeeaca6cde602) 56 | - chore(deps): update dependency octodns to v1.10.0 [`0fe6008`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/0fe600824059833e2445f1a0e042e78c87a78f38) 57 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v4.1.3 [`5c28277`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/5c28277e57a2062abe326a957d71f1816da4bdce) 58 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v4.1.2 [`4235a41`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/4235a41e70bb1c46d5510c7223c30b77db9f2f53) 59 | - fix whitespace error [`024d6dc`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/024d6dc52e356c741f02892cefb4aa9af084ee25) 60 | 61 | ## [v0.3.9](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.8...v0.3.9) - 2024-09-12 62 | 63 | ### Commits 64 | 65 | - caa records: convert bytes values to string [`5a35e1b`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/5a35e1b51e4ba8e9b1c9e172d11ae8684e344eb9) 66 | - fix changelog action [`015745f`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/015745f353cd29861c2b6a4927e8f3cdf137f4ec) 67 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v4.1.0 [`d04d72b`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/d04d72bad043b37ac745d055f2e482493b7e962b) 68 | 69 | ## [v0.3.8](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.7...v0.3.8) - 2024-08-30 70 | 71 | ### Commits 72 | 73 | - add examples [`1283d83`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/1283d83060ca0728fe399bccbc01a1a5f27452ce) 74 | - fix cicd files [`8e168c4`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/8e168c4d88de25da1a68eff15d955968daa6b23a) 75 | - update version matrix [`00a6ff6`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/00a6ff600beeeb25e432e5ccac774c43e1fdd3fb) 76 | - set SUPPORTS_DYNAMIC to true [`cb3ea58`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/cb3ea583e1ab126b49f3f31055cec8f541d78716) 77 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v4.0.10 [`e07b905`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/e07b9054eab1e9baf7676ac36a8989a00ab23c80) 78 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v4.0.9 [`6b4baff`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/6b4bafff5ace100d3de651c50c5d25063907819b) 79 | - chore(deps): update actions/setup-go action to v5 [`1cce3ee`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/1cce3eee7376c56ef89528e35384c75dc5a279e6) 80 | 81 | ## [v0.3.7](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.6...v0.3.7) - 2024-08-09 82 | 83 | ### Commits 84 | 85 | - update ci ciles [`6572375`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/65723753f6747921990fcd4f1b6c39c00005a92e) 86 | - always normalize zones [`f1f6602`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/f1f6602985ef57d2b1531f7099a1df7515fbaa6f) 87 | - run auto-formatter [`9efd4fa`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/9efd4faea1320a8e037ef0e4a7deab89fb3a95ab) 88 | - update to netbox>4.0 and netbox-plugin-dns>1.0 [`6772f0a`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/6772f0a5e0a929cc589c4fb08ff989eb8a962c3c) 89 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v4.0.8 [`aaab7d9`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/aaab7d948e5cba8c34785b5f4c53cee3c6791184) 90 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v4.0.7 [`b528c58`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/b528c58eaed41afd59c3fa76ae5f9940d5ba5049) 91 | - remove lefthook from justfile [`f4e877e`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/f4e877e2fe41dbf8a5e52bc795bf04b5da654aed) 92 | 93 | ## [v0.3.6](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.5...v0.3.6) - 2024-07-07 94 | 95 | ### Commits 96 | 97 | - update tests and deps [`adeca85`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/adeca856be84af888ec7d1de3a567fba5c439f68) 98 | - Implement Provider.list_zones for dynamic zone config support [`0051c30`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/0051c30b8d948971b7ec447595b50eef80515993) 99 | - use make_absolute function [`60b3226`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/60b322636169fd8a32804f29264fe750caff607b) 100 | - fix whitespace [`ba2a3f6`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/ba2a3f6edfe2be8566c609784f659b1638c3922d) 101 | - chore(deps): update dependency octodns to v1.7.0 [`cb51054`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/cb510541e25cd5b1d57afc8496b4bb89088f3b3c) 102 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v3.7.6 [`d97d2b7`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/d97d2b749cd5a62e310600b31159665b44e724d7) 103 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v3.7.5 [`cdd9f0d`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/cdd9f0d910f682f8ab70c66887e75f095e43094a) 104 | - chore(deps): update dependency octodns to v1.6.1 [`d70c9de`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/d70c9dec0aa532c699feba8bd5991cfe7a7a13d7) 105 | - chore(deps): update lscr.io/linuxserver/netbox docker tag to v3.7.4 [`6f1424c`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/6f1424c25e7ce387a70094086a55ece38e6c7342) 106 | - chore(deps): update dependency octodns to v1.6.0 [`b19a598`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/b19a598c387c6cc53756bfc0b255ec6134e3aeef) 107 | - chore(deps): update dependency just to v1.25.2 [`dff84f4`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/dff84f48ab87fb257ee82ecf7ddb9e2b6993bd86) 108 | 109 | ## [v0.3.5](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.4...v0.3.5) - 2024-03-04 110 | 111 | ### Commits 112 | 113 | - add new tests [`a8ecfab`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/a8ecfab93096cd3201efcb06a65bc86a2444611a) 114 | - fix typo in method call and add function for semicolons [`afe1826`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/afe182628a7b2fc784dc48bebaf9978c85d682d2) 115 | - update ci files for github [`6b06cad`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/6b06cada38751648b9365fd27b850aa2b6d250a8) 116 | - unescape the changeset before comparison to live records to have an accurate changeset. fixes changes for txt records [`4fe8be6`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/4fe8be62918b5bea821f4f88a151918899b61e91) 117 | - rollback changes to semicolon escaping [`91ba03a`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/91ba03af7f5f53a7f3b5218f0f4a905eebcdb914) 118 | - revert: change from repr to string in changeset [`8d29b5e`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/8d29b5e639b25bb57b43e9e85bbe78e51d70b493) 119 | - update logging [`ff1a47c`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/ff1a47cf8cda21695fb4e30910dd92d0f9cb8b63) 120 | - update test cases [`f38a103`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/f38a1036b79b9779a8480c2d1f79132370b83bb4) 121 | - change from repr to string in changeset [`696d2a2`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/696d2a2532eef05fee95cddff627c9b09736ae52) 122 | - only unescape txt and spf records [`78448d2`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/78448d2bde85fda1718c64e6cd8983b4fcb20fe0) 123 | - fix semicolon escaping [`6e624a7`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/6e624a79a75051295d1863f9af7e907d44a9b307) 124 | - fix tests [`2e468c8`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/2e468c85699808ed9ba2edb35a6cc39591869346) 125 | 126 | ## [v0.3.4](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.3...v0.3.4) - 2024-02-29 127 | 128 | ### Commits 129 | 130 | - add dev setup [`880a210`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/880a210b9bacf9158f3f8f90142c34d4e1719f45) 131 | - update dev setup [`ba9d301`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/ba9d301dacf532a4fd217f6b08b4d3b407f315c6) 132 | - simplify some code [`792b9d5`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/792b9d5429c43bbf67b99b35c7852b5d1e048980) 133 | - first tests with provider support [`284f001`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/284f001236bda738693cea213cd3c1b6c5d9d16b) 134 | - revert the test changes [`09898b7`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/09898b7a228454a26bcf1e83b6cac7b556ec9aa3) 135 | - rename class and add new tests [`408cf3f`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/408cf3fdd279d5911eefb21f3789410c287c6611) 136 | - add tests and move multiple replaces to regex replace [`0fb38b9`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/0fb38b9ae54efb1d593f3daa1a2af601a7e52d60) 137 | - fix some errors in record filtering [`6612dae`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/6612daeee79a8759d4af3c0caa86129fce27f0b6) 138 | - rename some variables [`59a9b01`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/59a9b01fef8feadfc448716eec30e9f1157d34ae) 139 | - add first test [`3728594`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/3728594b4e022e77846468d7f8e3260ed3b13029) 140 | - fix definition [`29cf05c`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/29cf05c888fb96dab1710d86168250563b4b0bf6) 141 | - add dev branch to tests [`e27f893`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/e27f8938a51449718de638651550ea54a187589d) 142 | - Update dependency octodns to v1.5.0 [`6953643`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/69536438767c8c7483c330967278a19b02d99f7f) 143 | - Update lscr.io/linuxserver/netbox Docker tag to v3.7.3 [`537cd20`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/537cd2061b859e1f4bff4e009154c1ac2ff31e5f) 144 | - Update dependency octodns to v1.5.0 [`6c9f77c`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/6c9f77c5f420bfc06d28dbbbf5ef512fb8f32d1f) 145 | - add typehint for __init__ [`6326903`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/6326903bb3990ed57045f91bae53b377677862b9) 146 | - Update lscr.io/linuxserver/netbox Docker tag to v3.7.3 [`93be71f`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/93be71fabad25f1285f827e347da82a97212a425) 147 | - fix workflow name [`c5f3fa9`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/c5f3fa9e00ad63231ecbd6654853510ca338883f) 148 | - fix github release action [`c81f324`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/c81f3241d9841798582cbfdf75e11c5d59ea6eee) 149 | 150 | ## [v0.3.3](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.2...v0.3.3) - 2024-02-20 151 | 152 | ### Commits 153 | 154 | - fix changelog generation [`e0e7694`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/e0e769409d7c5c369e7f0c0c1cbd74da1f2ddcde) 155 | 156 | ## [v0.3.2](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.1...v0.3.2) - 2024-02-20 157 | 158 | ### Commits 159 | 160 | - add issue templates, ci linting and update pre-commit [`6aeda2c`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/6aeda2c48bf50c5081a2f5cf8dfed096440a6e5f) 161 | - update ci files for future releases [`86789a1`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/86789a1c4bc3ab45d34e1dfcdcc765d7890138d5) 162 | - add CONTRIBUTING.md [`74c447b`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/74c447b282949184be41f05bd975f442ac6f7b92) 163 | - add changelog [`35c069e`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/35c069efe94442c6307e3ac87185a84ad49a1282) 164 | 165 | ## [v0.3.1](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.3.0...v0.3.1) - 2024-01-25 166 | 167 | ### Commits 168 | 169 | - fix for single value rcd [`256d1a8`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/256d1a8f0ecc5de6a8d8d3e3ff10752785b3d2d2) 170 | - fix type errors [`f20e190`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/f20e190ef6d4bf130550616fac7d83af721b4ff2) 171 | - fix type hint for rdata [`95c6815`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/95c6815dad6ea0b2c2eca42e83f79da08487e409) 172 | 173 | ## [v0.3.0](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/compare/v0.2.1...v0.3.0) - 2024-01-25 174 | 175 | ### Commits 176 | 177 | - make populate function smaller [`c95f95f`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/c95f95fb8b8a2ae79b8f4db05675e98386a723b6) 178 | - add docstrings and refactor make absolute function [`f7f54f8`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/f7f54f8eb71fef26c6759509a589d9afc7a91bdc) 179 | - add new workflows [`ff3dd03`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/ff3dd03ce53002e457e172b8718af1e65ac7f78d) 180 | - fix mypy issues [`988502a`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/988502a499d86339fc7f2f4c623c442590b1f59d) 181 | - fix deps [`de1e67c`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/de1e67c6be5063c6dbed6483f232baf9420dcfa7) 182 | 183 | ## v0.2.1 - 2024-01-09 184 | 185 | ### Fixed 186 | 187 | - remove wayward debug pprint [`#10`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/issues/10) 188 | - Record type as parameter value passed to get_nb_zone [`#7`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/issues/7) 189 | 190 | ### Commits 191 | 192 | - first commit [`cab1692`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/cab1692ea57fedc54a2633330c442d2acebba1e8) 193 | - update query for views and use hatch as a build tool [`cdd0b00`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/cdd0b00cd155ac5af9d6a457fa1ecfb71081b3a9) 194 | - update to python template for future pypi releases [`4a2ba28`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/4a2ba28aaa3c05fdc163a3b703c7226a74e349e6) 195 | - use fork of pynetbox until pkg_resources runtime dependency is removed [`daa03aa`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/daa03aae2df979c2469696011ce42c694192a02c) 196 | - move from provider base class to source [`5714ce5`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/5714ce5af9d738999ddb68e7b92eb60c5776cd02) 197 | - update readme and make absolute optional [`61b1b47`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/61b1b47a96bb81ab6eb216a4a0988f7682a7fa9f) 198 | - update dependencies [`4cf1c62`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/4cf1c627742593a8590f26d49bd375e3b51b31bc) 199 | - _get_nb_zone() is now aware of same zones declared under different view [`c2f27f7`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/c2f27f7c43f78040d79d6fdc2c84d250f6f64403) 200 | - add @ support and make cname records absolute [`9d79906`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/9d79906f960bb61aacfb464979899805bce34dbf) 201 | - implement replace duplicate option [`790a2de`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/790a2de3458e3fd9cefd0fdf35b82dfa156820bf) 202 | - fix 'Abstract base class, log property missing' [`f8251ad`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/f8251ad8ffcf6f92dfcf1dfdcffe954ac55eee4c) 203 | - update view query [`3cb883a`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/3cb883abd67a275e4e64a79c6a28f46d706b9679) 204 | - zone can also be configured without assigned view [`4828bbf`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/4828bbf00bb8f4946f07134a39cddc04a2ed306b) 205 | - make some records absolute [`c77cef6`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/c77cef64ee5d0470ab8fa1f5fc2781e1f24a75a2) 206 | - update requirements [`66d9551`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/66d95519b1070489859d665671d50e50f94f5a8e) 207 | - Fix issue where octodns was failing on missing TTL value for NS records in PTR zones [`d3c1d0f`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/d3c1d0f65390dd595441ac3dca1cf52db790ddad) 208 | - fix parameter call to better reflect function type hint. [`e4db78a`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/e4db78a281a27765271f1afda07dee08602ed2d0) 209 | - Use upstream pynetbox library as their pull req #426 is merged. [`423207e`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/423207e5dd1673f306fb392cc5d37f63a4545fe5) 210 | - fix attribute error in case view is null [`693ea86`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/693ea866296132b1cd94e2dda61ae94e94a77557) 211 | - fix log merge [`f0b502a`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/f0b502a74f3ea72cfd78d9837bbbcc0934e0b826) 212 | - fix literal type [`bec8384`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/bec83845577417d0ede5bd57a7772946bd997142) 213 | - update debug log [`3537917`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/353791797ded363c9610b5f40e4bd857b93fd328) 214 | - escape ; character in txt records [`ac395e8`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/ac395e84e24b1b271ff76721137e2d82f548553c) 215 | - fix request parameter [`d4c0955`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/d4c095540f0b925dcca249d2896ca890731af256) 216 | - add log message for total records found [`14a98fe`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/14a98fe132b3e6789ca3ad410bc101bac00b4f78) 217 | - view argument type hints must also accept None value [`c5ae5bf`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/c5ae5bf4057756030f0447681e9e6c2dccb59ec1) 218 | - update lock [`d4a4032`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/d4a403261fb2e80a4335071cc17dbff00fa9366c) 219 | - Fix invalid query to Netbox, search only with zone ID. [`e752d3d`](https://git.44net.ch/olofvndrhr/octodns-netbox-dns/commit/e752d3db221f9b79e3df5dc30ca40f597a655873) 220 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # getting started 2 | 3 | ## with asdf 4 | 5 | ### tools needed 6 | 7 | - python 3.11 8 | - [hatch](https://hatch.pypa.io/) 9 | - [asdf](https://asdf-vm.com/guide/getting-started.html) 10 | 11 | ### setup 12 | 13 | 1. install [asdf](https://asdf-vm.com/guide/getting-started.html) 14 | 2. run `asdf install` in this directory to install all needed tools 15 | 3. run `just setup` to install the pre-commit hooks etc. 16 | 17 | ### pre-commit 18 | 19 | 1. run `just check` to lint the files and auto-format them. 20 | you can optionally run `just format` and `just lint` as a single action. 21 | 2. fix the issues which ruff reports 22 | 3. run `just build` to check if it builds correctly 23 | 4. commit changes 24 | 25 | ## manual 26 | 27 | ### tools needed 28 | 29 | - python 3.11 30 | - [hatch](https://hatch.pypa.io/) 31 | 32 | ### setup 33 | 34 | 1. install [just](https://github.com/casey/just) 35 | 2. run `just setup` to install the pre-commit hooks etc. 36 | 37 | ### pre-commit 38 | 39 | 1. run `hatch run lint:fmt` to lint the files and auto-format them. 40 | 2. fix the issues which ruff reports 41 | 3. run `hatch build --clean` to check if it builds correctly 42 | 4. commit changes 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | Copyright 2017 DigitalOcean -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft src 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netbox-plugin-dns provider for octodns 2 | 3 | > works with 4 | 5 | ## config 6 | 7 | ```yml 8 | providers: 9 | config: 10 | class: octodns_netbox_dns.NetBoxDNSProvider 11 | # Netbox url 12 | # [mandatory, default=null] 13 | url: "https://some-url" 14 | # Netbox API token 15 | # [mandatory, default=null] 16 | token: env/NETBOX_API_KEY 17 | # View of the zone. Can be either a string -> the view name 18 | # "null" -> to only query zones without a view 19 | # false -> to ignore views 20 | # [optional, default=false] 21 | view: false 22 | # When records sourced from multiple providers, allows provider 23 | # to replace entries coming from the previous one. 24 | # Implementation matches YamlProvider's 'populate_should_replace' 25 | # [optional, default=false] 26 | replace_duplicates: false 27 | # Make CNAME, MX and SRV records absolute if they are missing the trailing "." 28 | # [optional, default=false] 29 | make_absolute: false 30 | # Disable automatic PTR record creating in the NetboxDNS plugin. 31 | # [optional, default=true] 32 | disable_ptr: true 33 | # Disable certificate verification for unsecure https. 34 | # [optional, default=false] 35 | insecure_request: false 36 | ``` 37 | 38 | ## compatibility 39 | 40 | > actively tested on the newest `netbox-plugin-dns` and `netbox` versions 41 | 42 | | provider | [netbox-plugin-dns](https://github.com/peteeckel/netbox-plugin-dns) | [netbox](https://github.com/netbox-community/netbox) | 43 | | ------------ | ------------------------------------------------------------------- | ---------------------------------------------------- | 44 | | `>= v0.3.3` | `>=0.21.0` | `>=3.6.0` | 45 | | `>= v0.3.6` | `>=1.0.0` | `>=4.0.0` | 46 | | `>= v0.3.11` | `>=1.2.3` | `>=4.2.0` | 47 | 48 | ## limitations 49 | 50 | the records can only be synced to netbox-dns if the zone is already existing. 51 | the provider _CAN NOT_ create zones (as of now). 52 | 53 | ## install 54 | 55 | ### via pip 56 | 57 | ```bash 58 | pip install octodns-netbox-dns 59 | ``` 60 | 61 | ### via pip + git 62 | 63 | ```bash 64 | pip install octodns-netbox-dns@git+https://github.com/olofvndrhr/octodns-netbox-dns.git@main 65 | ``` 66 | 67 | ### via pip + `requirements.txt` 68 | 69 | add the following line to your requirements file 70 | 71 | ```bash 72 | octodns-netbox-dns@git+https://github.com/olofvndrhr/octodns-netbox-dns.git@main 73 | ``` 74 | -------------------------------------------------------------------------------- /dev/.env.example: -------------------------------------------------------------------------------- 1 | # uid for docker containers 2 | UID=1000 3 | # gid for docker containers 4 | GID=1000 5 | -------------------------------------------------------------------------------- /dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM lscr.io/linuxserver/netbox:4.2.8 2 | 3 | RUN pip install -U \ 4 | wheel \ 5 | setuptools \ 6 | netbox-plugin-dns 7 | -------------------------------------------------------------------------------- /dev/compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | app: 3 | image: netbox-dev:build 4 | container_name: netbox-dev-app 5 | restart: unless-stopped 6 | security_opt: ["no-new-privileges:true"] 7 | build: 8 | dockerfile: Dockerfile 9 | depends_on: 10 | - db 11 | - redis 12 | networks: 13 | - appnet 14 | ports: 15 | - "8000:8000" 16 | volumes: 17 | - ./netbox-data/:/config/:rw 18 | - ./configuration.py:/config/configuration.py:ro 19 | environment: 20 | - TZ=Europe/Zurich 21 | - PUID=${UID} 22 | - PGID=${GID} 23 | - DB_HOST=netbox-dev-db 24 | - DB_NAME=netbox 25 | - DB_USER=netbox 26 | - DB_PASSWORD=netbox-dev 27 | - REDIS_HOST=netbox-dev-redis 28 | - SUPERUSER_EMAIL=admin@example.com 29 | - SUPERUSER_PASSWORD=netbox-dev 30 | 31 | db: 32 | image: bitnami/postgresql:14.9.0 33 | container_name: netbox-dev-db 34 | restart: unless-stopped 35 | user: ${UID} 36 | security_opt: ["no-new-privileges:true"] 37 | networks: 38 | - appnet 39 | volumes: 40 | - /etc/localtime:/etc/localtime:ro 41 | - ./db-data/:/bitnami/postgresql/:rw 42 | environment: 43 | - TZ=Europe/Zurich 44 | - POSTGRESQL_POSTGRES_PASSWORD=netbox-dev 45 | - POSTGRES_DB=netbox 46 | - POSTGRES_USER=netbox 47 | - POSTGRES_PASSWORD=netbox-dev 48 | 49 | redis: 50 | image: bitnami/redis:7.2 51 | container_name: netbox-dev-redis 52 | restart: unless-stopped 53 | user: ${UID} 54 | security_opt: ["no-new-privileges:true"] 55 | networks: 56 | - appnet 57 | volumes: 58 | - ./redis-data/:/bitnami/redis/data/:rw 59 | environment: 60 | - TZ=Europe/Zurich 61 | - ALLOW_EMPTY_PASSWORD=yes 62 | 63 | networks: 64 | appnet: 65 | name: netbox-dev 66 | driver: bridge 67 | -------------------------------------------------------------------------------- /dev/configuration.py: -------------------------------------------------------------------------------- 1 | ######################### 2 | # # 3 | # Required settings # 4 | # # 5 | ######################### 6 | 7 | PLUGINS = ["netbox_dns"] 8 | 9 | PLUGINS_CONFIG = { 10 | "netbox_dns": { 11 | "zone_default_ttl": 3600, 12 | "zone_soa_serial_auto": True, 13 | "zone_soa_minimum": 3600, 14 | "zone_nameservers": ["ns1.example.com", "ns2.example.com"], 15 | "zone_soa_mname": "ns1.example.com", 16 | "zone_soa_rname": "admin.example.com", 17 | "tolerate_underscores_in_labels": False, 18 | "tolerate_leading_underscore_types": [ 19 | "TXT", 20 | "SRV", 21 | ], 22 | "enable_root_zones": False, 23 | "enforce_unique_records": False, 24 | "feature_ipam_coupling": False, 25 | "feature_ipam_dns_info": True, 26 | }, 27 | } 28 | 29 | # This is a list of valid fully-qualified domain names (FQDNs) for the NetBox server. NetBox will not permit write 30 | # access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name. 31 | # 32 | # Example: ALLOWED_HOSTS = ['netbox.example.com', 'netbox.internal.local'] 33 | ALLOWED_HOSTS = ["localhost"] 34 | 35 | # PostgreSQL database configuration. See the Django documentation for a complete list of available parameters: 36 | # https://docs.djangoproject.com/en/stable/ref/settings/#databases 37 | DATABASE = { 38 | "NAME": "netbox", # Database name 39 | "USER": "netbox", # PostgreSQL username 40 | "PASSWORD": "netbox-dev", # PostgreSQL password 41 | "HOST": "netbox-dev-db", # Database server 42 | "PORT": "", # Database port (leave blank for default) 43 | "CONN_MAX_AGE": 300, # Max database connection age 44 | } 45 | 46 | # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate 47 | # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended 48 | # to use two separate database IDs. 49 | REDIS = { 50 | "tasks": { 51 | "HOST": "netbox-dev-redis", 52 | "PORT": 6379, 53 | # Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel 54 | # 'SENTINELS': [('mysentinel.redis.example.com', 6379)], 55 | # 'SENTINEL_SERVICE': 'netbox', 56 | "PASSWORD": "", 57 | "DATABASE": 0, 58 | "SSL": False, 59 | # Set this to True to skip TLS certificate verification 60 | # This can expose the connection to attacks, be careful 61 | # 'INSECURE_SKIP_TLS_VERIFY': False, 62 | }, 63 | "caching": { 64 | "HOST": "netbox-dev-redis", 65 | "PORT": 6379, 66 | # Comment out `HOST` and `PORT` lines and uncomment the following if using Redis Sentinel 67 | # 'SENTINELS': [('mysentinel.redis.example.com', 6379)], 68 | # 'SENTINEL_SERVICE': 'netbox', 69 | "PASSWORD": "", 70 | "DATABASE": 1, 71 | "SSL": False, 72 | # Set this to True to skip TLS certificate verification 73 | # This can expose the connection to attacks, be careful 74 | # 'INSECURE_SKIP_TLS_VERIFY': False, 75 | }, 76 | } 77 | 78 | # This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file. 79 | # For optimal security, SECRET_KEY should be at least 50 characters in length and contain a mix of letters, numbers, and 80 | # symbols. NetBox will not run without this defined. For more information, see 81 | # https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY 82 | SECRET_KEY = "ZFK2dO-pD@Vnjxf7gM@gh#jHwXm_wih%V6v@Byld7yX6c^Vsb!" 83 | 84 | ######################### 85 | # # 86 | # Optional settings # 87 | # # 88 | ######################### 89 | 90 | # Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of 91 | # application errors (assuming correct email settings are provided). 92 | ADMINS = [ 93 | # ('John Doe', 'jdoe@example.com'), 94 | ] 95 | 96 | # URL schemes that are allowed within links in NetBox 97 | ALLOWED_URL_SCHEMES = ( 98 | "file", 99 | "ftp", 100 | "ftps", 101 | "http", 102 | "https", 103 | "irc", 104 | "mailto", 105 | "sftp", 106 | "ssh", 107 | "tel", 108 | "telnet", 109 | "tftp", 110 | "vnc", 111 | "xmpp", 112 | ) 113 | 114 | # Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same 115 | # content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP. 116 | BANNER_TOP = "" 117 | BANNER_BOTTOM = "" 118 | 119 | # Text to include on the login page above the login form. HTML is allowed. 120 | BANNER_LOGIN = "" 121 | 122 | # Base URL path if accessing NetBox within a directory. For example, if installed at https://example.com/netbox/, set: 123 | # BASE_PATH = 'netbox/' 124 | BASE_PATH = "" 125 | 126 | # Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90) 127 | CHANGELOG_RETENTION = 90 128 | 129 | # API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be 130 | # allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or 131 | # CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers 132 | CORS_ORIGIN_ALLOW_ALL = False 133 | CORS_ORIGIN_WHITELIST = [ 134 | # 'https://hostname.example.com', 135 | ] 136 | CORS_ORIGIN_REGEX_WHITELIST = [ 137 | # r'^(https?://)?(\w+\.)?example\.com$', 138 | ] 139 | 140 | # Specify any custom validators here, as a mapping of model to a list of validators classes. Validators should be 141 | # instances of or inherit from CustomValidator. 142 | # from extras.validators import CustomValidator 143 | CUSTOM_VALIDATORS = { 144 | # 'dcim.site': [ 145 | # CustomValidator({ 146 | # 'name': { 147 | # 'min_length': 10, 148 | # 'regex': r'\d{3}$', 149 | # } 150 | # }) 151 | # ], 152 | } 153 | 154 | # Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal 155 | # sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging 156 | # on a production system. 157 | DEBUG = False 158 | 159 | # Email settings 160 | EMAIL = { 161 | "SERVER": "localhost", 162 | "PORT": 25, 163 | "USERNAME": "", 164 | "PASSWORD": "", 165 | "USE_SSL": False, 166 | "USE_TLS": False, 167 | "TIMEOUT": 10, # seconds 168 | "FROM_EMAIL": "", 169 | } 170 | 171 | # Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space within the global table 172 | # (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True. 173 | ENFORCE_GLOBAL_UNIQUE = False 174 | 175 | # Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and 176 | # by anonymous users. List models in the form `.`. Add '*' to this list to exempt all models. 177 | EXEMPT_VIEW_PERMISSIONS = [ 178 | # 'dcim.site', 179 | # 'dcim.region', 180 | # 'ipam.prefix', 181 | ] 182 | 183 | # Enable the GraphQL API 184 | GRAPHQL_ENABLED = True 185 | 186 | # HTTP proxies NetBox should use when sending outbound HTTP requests (e.g. for webhooks). 187 | # HTTP_PROXIES = { 188 | # 'http': 'http://10.10.1.10:3128', 189 | # 'https': 'http://10.10.1.10:1080', 190 | # } 191 | 192 | # IP addresses recognized as internal to the system. The debugging toolbar will be available only to clients accessing 193 | # NetBox from an internal IP. 194 | INTERNAL_IPS = ("127.0.0.1", "::1") 195 | 196 | # Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs: 197 | # https://docs.djangoproject.com/en/stable/topics/logging/ 198 | LOGGING = {} 199 | 200 | # Automatically reset the lifetime of a valid session upon each authenticated request. Enables users to remain 201 | # authenticated to NetBox indefinitely. 202 | LOGIN_PERSISTENCE = False 203 | 204 | # Setting this to True will permit only authenticated users to access any part of NetBox. By default, anonymous users 205 | # are permitted to access most data in NetBox but not make any changes. 206 | LOGIN_REQUIRED = False 207 | 208 | # The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to 209 | # re-authenticate. (Default: 1209600 [14 days]) 210 | LOGIN_TIMEOUT = None 211 | 212 | # Setting this to True will display a "maintenance mode" banner at the top of every page. 213 | MAINTENANCE_MODE = False 214 | 215 | # The URL to use when mapping physical addresses or GPS coordinates 216 | MAPS_URL = "https://maps.google.com/?q=" 217 | 218 | # An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g. 219 | # "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request 220 | # all objects by specifying "?limit=0". 221 | MAX_PAGE_SIZE = 1000 222 | 223 | # The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that 224 | # the default value of this setting is derived from the installed location. 225 | # MEDIA_ROOT = '/opt/netbox/netbox/media' 226 | 227 | # By default uploaded media is stored on the local filesystem. Using Django-storages is also supported. Provide the 228 | # class path of the storage driver in STORAGE_BACKEND and any configuration options in STORAGE_CONFIG. For example: 229 | # STORAGE_BACKEND = 'storages.backends.s3boto3.S3Boto3Storage' 230 | # STORAGE_CONFIG = { 231 | # 'AWS_ACCESS_KEY_ID': 'Key ID', 232 | # 'AWS_SECRET_ACCESS_KEY': 'Secret', 233 | # 'AWS_STORAGE_BUCKET_NAME': 'netbox', 234 | # 'AWS_S3_REGION_NAME': 'eu-west-1', 235 | # } 236 | 237 | # Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics' 238 | METRICS_ENABLED = False 239 | 240 | # Credentials that NetBox will uses to authenticate to devices when connecting via NAPALM. 241 | NAPALM_USERNAME = "" 242 | NAPALM_PASSWORD = "" 243 | 244 | # NAPALM timeout (in seconds). (Default: 30) 245 | NAPALM_TIMEOUT = 30 246 | 247 | # NAPALM optional arguments (see https://napalm.readthedocs.io/en/latest/support/#optional-arguments). Arguments must 248 | # be provided as a dictionary. 249 | NAPALM_ARGS = {} 250 | 251 | # Determine how many objects to display per page within a list. (Default: 50) 252 | PAGINATE_COUNT = 50 253 | 254 | # When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to 255 | # prefer IPv4 instead. 256 | PREFER_IPV4 = False 257 | 258 | # Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1. 259 | RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = 22 260 | RACK_ELEVATION_DEFAULT_UNIT_WIDTH = 220 261 | 262 | # Remote authentication support 263 | REMOTE_AUTH_ENABLED = False 264 | REMOTE_AUTH_BACKEND = "netbox.authentication.RemoteUserBackend" 265 | REMOTE_AUTH_HEADER = "HTTP_REMOTE_USER" 266 | REMOTE_AUTH_AUTO_CREATE_USER = False 267 | REMOTE_AUTH_DEFAULT_GROUPS = [] 268 | REMOTE_AUTH_DEFAULT_PERMISSIONS = {} 269 | 270 | # This repository is used to check whether there is a new release of NetBox available. Set to None to disable the 271 | # version check or use the URL below to check for release in the official NetBox repository. 272 | RELEASE_CHECK_URL = None 273 | # RELEASE_CHECK_URL = 'https://api.github.com/repos/netbox-community/netbox/releases' 274 | 275 | # The file path where custom reports will be stored. A trailing slash is not needed. Note that the default value of 276 | # this setting is derived from the installed location. 277 | # REPORTS_ROOT = '/opt/netbox/netbox/reports' 278 | 279 | # Maximum execution time for background tasks, in seconds. 280 | RQ_DEFAULT_TIMEOUT = 300 281 | 282 | # The file path where custom scripts will be stored. A trailing slash is not needed. Note that the default value of 283 | # this setting is derived from the installed location. 284 | SCRIPTS_ROOT = "/config/scripts" 285 | 286 | # The name to use for the session cookie. 287 | SESSION_COOKIE_NAME = "sessionid" 288 | 289 | # By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use 290 | # local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only 291 | # database access.) Note that the user as which NetBox runs must have read and write permissions to this path. 292 | SESSION_FILE_PATH = None 293 | 294 | # Time zone (default: UTC) 295 | TIME_ZONE = "UTC" 296 | 297 | # Date/time formatting. See the following link for supported formats: 298 | # https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date 299 | DATE_FORMAT = "N j, Y" 300 | SHORT_DATE_FORMAT = "Y-m-d" 301 | TIME_FORMAT = "g:i a" 302 | SHORT_TIME_FORMAT = "H:i:s" 303 | DATETIME_FORMAT = "N j, Y g:i a" 304 | SHORT_DATETIME_FORMAT = "Y-m-d H:i" 305 | -------------------------------------------------------------------------------- /dev/sync.yml: -------------------------------------------------------------------------------- 1 | manager: 2 | max_workers: 1 3 | plan_outputs: 4 | html: 5 | class: octodns.provider.plan.PlanMarkdown 6 | 7 | providers: 8 | config: 9 | class: octodns.provider.yaml.YamlProvider 10 | directory: ./zones 11 | default_ttl: 3600 12 | enforce_order: true 13 | populate_should_replace: false 14 | 15 | netbox: 16 | class: octodns_netbox_dns.NetBoxDNSProvider 17 | url: http://localhost:8000 18 | token: 1ca8f8de1d651b0859052dc5e6a0858fd1e43e3d # change token for netbox 19 | view: false 20 | replace_duplicates: false 21 | make_absolute: true 22 | disable_ptr: true 23 | insecure_request: false 24 | 25 | zones: 26 | example.com.: 27 | sources: 28 | - config 29 | targets: 30 | - netbox 31 | 32 | test.example.com.: 33 | sources: 34 | - config 35 | targets: 36 | - netbox 37 | 38 | view.example.com.: 39 | sources: 40 | - config 41 | targets: 42 | - netbox 43 | 44 | 1.168.192.in-addr.arpa.: 45 | sources: 46 | - config 47 | targets: 48 | - netbox 49 | -------------------------------------------------------------------------------- /dev/sync2.yml: -------------------------------------------------------------------------------- 1 | manager: 2 | max_workers: 1 3 | plan_outputs: 4 | html: 5 | class: octodns.provider.plan.PlanMarkdown 6 | 7 | providers: 8 | config: 9 | class: octodns.provider.yaml.YamlProvider 10 | directory: ./zones 11 | default_ttl: 3600 12 | enforce_order: true 13 | populate_should_replace: false 14 | 15 | netbox: 16 | class: octodns_netbox_dns.NetBoxDNSProvider 17 | url: http://localhost:8000 18 | token: 1ca8f8de1d651b0859052dc5e6a0858fd1e43e3d # change token for netbox 19 | view: false 20 | replace_duplicates: false 21 | make_absolute: true 22 | disable_ptr: true 23 | insecure_request: false 24 | 25 | zones: 26 | "*": 27 | sources: 28 | - config 29 | targets: 30 | - netbox 31 | -------------------------------------------------------------------------------- /dev/zones/1.168.192.in-addr.arpa.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ? '' 3 | : ttl: 43200 4 | type: NS 5 | values: 6 | - ns1.example.com. 7 | - ns2.example.com. 8 | '1': 9 | type: PTR 10 | value: ns1.example.com. 11 | '2': 12 | type: PTR 13 | value: ns2.example.com. 14 | '11': 15 | type: PTR 16 | value: record11.example.com. 17 | '12': 18 | type: PTR 19 | value: record12.example.com. 20 | -------------------------------------------------------------------------------- /dev/zones/example.com.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ? '' 3 | : ttl: 43200 4 | type: NS 5 | values: 6 | - ns1.example.com. 7 | - ns2.example.com. 8 | _srv1._tcp: 9 | type: SRV 10 | value: 11 | port: 9987 12 | priority: 0 13 | target: example.com. 14 | weight: 5 15 | a1: 16 | type: A 17 | value: 192.168.1.11 18 | a2: 19 | type: A 20 | value: 192.168.1.12 21 | aaaa1: 22 | type: AAAA 23 | value: 2001:db8::8a2e:370:7334 24 | aaaa2: 25 | type: AAAA 26 | value: 2001:db8::8a2e:370:7335 27 | abc: 28 | type: CNAME 29 | value: def.example.com. 30 | caa1: 31 | type: CAA 32 | value: 33 | flags: 0 34 | tag: issue 35 | value: letsencrypt.org 36 | cname1: 37 | type: CNAME 38 | value: a1.example.com. 39 | cname2: 40 | type: CNAME 41 | value: a1.test.example.com. 42 | cname3: 43 | type: CNAME 44 | value: a1.view.example.com. 45 | dname1: 46 | type: DNAME 47 | value: example.net. 48 | loc1: 49 | type: LOC 50 | value: 51 | altitude: 1.0 52 | lat_degrees: 0 53 | lat_direction: N 54 | lat_minutes: 0 55 | lat_seconds: 0.0 56 | long_degrees: 0 57 | long_direction: W 58 | long_minutes: 0 59 | long_seconds: 0.0 60 | precision_horz: 10000.0 61 | precision_vert: 10.0 62 | size: 1.0 63 | mx1: 64 | type: MX 65 | value: 66 | exchange: mx.example.com. 67 | preference: 10 68 | record1: 69 | type: CNAME 70 | value: record11.example.com. 71 | sshfp1: 72 | type: SSHFP 73 | value: 74 | algorithm: 4 75 | fingerprint: 123456789abcdef67890123456789abcdef67890123456789abcdef123456789 76 | fingerprint_type: 2 77 | txt1: 78 | type: TXT 79 | value: v=TLSRPTv1\; rua=mailto:tlsrpt@example.com 80 | txt2: 81 | type: TXT 82 | value: v=TLSRPTv1\\; rua=mailto:tlsrpt@example.com 83 | txt3: 84 | type: TXT 85 | value: v=DKIM1\; k=rsa\; p=/0f+sikE+k9ZKbn1BJu0/soWht/+Zd/nc/+Gy//mQ1B5sCKYKgAmYTSWkxRjFzkc6KAQhi+/IzaFogEV050wcscdC8Rc8lAQzDUFrMs2ZZK1vFtkwIDAQAB 86 | txt4: 87 | type: TXT 88 | value: t=y\\;o=~\\; 89 | txt5: 90 | type: TXT 91 | value: t=y\;o=~\; 92 | -------------------------------------------------------------------------------- /dev/zones/test.example.com.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ? '' 3 | : ttl: 43200 4 | type: NS 5 | values: 6 | - ns1.example.com. 7 | - ns2.example.com. 8 | a1: 9 | type: A 10 | value: 192.168.1.11 11 | a2: 12 | type: A 13 | value: 192.168.1.12 14 | cname1: 15 | type: CNAME 16 | value: a1.test.example.com. 17 | -------------------------------------------------------------------------------- /dev/zones/view.example.com.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ? '' 3 | : ttl: 43200 4 | type: NS 5 | values: 6 | - ns1.example.com. 7 | - ns2.example.com. 8 | a1: 9 | type: A 10 | value: 192.168.1.11 11 | a2: 12 | type: A 13 | value: 192.168.1.13 14 | cname1: 15 | type: CNAME 16 | value: a1.view.example.com. 17 | -------------------------------------------------------------------------------- /examples/netbox-to-cloudflare.yml: -------------------------------------------------------------------------------- 1 | manager: 2 | max_workers: 2 3 | plan_outputs: 4 | html: 5 | class: octodns.provider.plan.PlanMarkdown 6 | 7 | processors: 8 | spf: 9 | class: octodns_spf.SpfDnsLookupProcessor 10 | no-root-ns: 11 | class: octodns.processor.filter.IgnoreRootNsFilter 12 | ignore-non-public: 13 | class: octodns.processor.filter.NetworkValueRejectlistFilter 14 | rejectlist: 15 | - 127.0.0.0/8 # loopback 16 | - 192.168.0.0/16 # rfc1918 17 | - 172.16.0.0/12 # rfc1918 18 | - 10.0.0.0/8 # rfc1918 19 | - ::1/128 # loopback 20 | - fc00::/7 # ula 21 | - fe80::/10 # link-local 22 | - f00::/8 # multicast 23 | 24 | providers: 25 | netbox: 26 | class: octodns_netbox_dns.NetBoxDNSProvider 27 | url: https://netbox.example.net 28 | token: env/NETBOX_API_KEY 29 | view: false 30 | replace_duplicates: false 31 | make_absolute: true 32 | cloudflare: 33 | class: octodns_cloudflare.CloudflareProvider 34 | token: env/CLOUDFLARE_API_KEY 35 | account_id: env/CLOUDFLARE_ACCOUNT_ID 36 | cdn: false 37 | pagerules: false 38 | retry_count: 4 39 | retry_period: 300 40 | zones_per_page: 50 41 | records_per_page: 100 42 | 43 | zones: 44 | "*": 45 | sources: 46 | - netbox 47 | processors: 48 | - spf 49 | - no-root-ns 50 | - ignore-non-public 51 | targets: 52 | - cloudflare 53 | -------------------------------------------------------------------------------- /examples/netbox-to-yaml.yml: -------------------------------------------------------------------------------- 1 | manager: 2 | max_workers: 2 3 | plan_outputs: 4 | html: 5 | class: octodns.provider.plan.PlanMarkdown 6 | 7 | processors: 8 | spf: 9 | class: octodns_spf.SpfDnsLookupProcessor 10 | 11 | providers: 12 | config: 13 | class: octodns.provider.yaml.YamlProvider 14 | directory: ./zones 15 | default_ttl: 3600 16 | enforce_order: true 17 | populate_should_replace: false 18 | netbox: 19 | class: octodns_netbox_dns.NetBoxDNSProvider 20 | url: https://netbox.example.net 21 | token: env/NETBOX_API_KEY 22 | view: false 23 | replace_duplicates: false 24 | make_absolute: true 25 | 26 | zones: 27 | "*": 28 | sources: 29 | - netbox 30 | processors: 31 | - spf 32 | targets: 33 | - config 34 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env just --justfile 2 | 3 | default: show_receipts 4 | 5 | set shell := ["bash", "-uc"] 6 | set dotenv-load := true 7 | 8 | show_receipts: 9 | just --list 10 | 11 | show_system_info: 12 | @echo "==================================" 13 | @echo "os : {{ os() }}" 14 | @echo "arch: {{ arch() }}" 15 | @echo "justfile dir: {{ justfile_directory() }}" 16 | @echo "invocation dir: {{ invocation_directory() }}" 17 | @echo "running dir: `pwd -P`" 18 | @echo "==================================" 19 | 20 | setup: 21 | asdf install 22 | 23 | create_venv: 24 | @echo "creating venv" 25 | python3 -m pip install --upgrade pip setuptools wheel 26 | python3 -m venv venv 27 | 28 | install_deps: 29 | @echo "installing dependencies" 30 | python3 -m hatch dep show requirements --project-only > /tmp/requirements.txt 31 | pip3 install -r /tmp/requirements.txt 32 | 33 | install_deps_dev: 34 | @echo "installing dev dependencies" 35 | python3 -m hatch dep show requirements --project-only > /tmp/requirements.txt 36 | python3 -m hatch dep show requirements --env-only >> /tmp/requirements.txt 37 | pip3 install -r /tmp/requirements.txt 38 | 39 | create_reqs: 40 | @echo "creating requirements" 41 | pipreqs --force --savepath requirements.txt src/octodns_netbox_dns 42 | 43 | lint: 44 | just show_system_info 45 | hatch run lint:style 46 | hatch run lint:typing 47 | 48 | format *args: 49 | just show_system_info 50 | hatch run lint:fmt {{ args }} 51 | 52 | check: 53 | just lint 54 | just format 55 | 56 | build: 57 | hatch build --clean 58 | 59 | test: 60 | hatch run default:test 61 | 62 | up: 63 | docker compose -f dev/compose.yml build 64 | docker compose -f dev/compose.yml up 65 | 66 | down: 67 | docker compose -f dev/compose.yml down 68 | 69 | clean: 70 | rm -rf dev/db-data/* 71 | rm -rf dev/redis-data/* 72 | rm -rf dev/netbox-data/* 73 | 74 | sync *args: 75 | hatch -v run default:sync {{ args }} 76 | 77 | sync2 *args: 78 | hatch -v run default:sync {{ args }} 79 | 80 | dump *args: 81 | hatch -v run default:dump {{ args }} 82 | 83 | dump2 *args: 84 | hatch -v run default:dump2 {{ args }} 85 | 86 | validate *args: 87 | hatch -v run default:validate {{ args }} 88 | 89 | validate2 *args: 90 | hatch -v run default:validate {{ args }} 91 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling>=1.24", "hatch-regex-commit>=0.0.3"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "octodns-netbox-dns" 7 | description = "octodns netbox-dns provider" 8 | readme = "README.md" 9 | license = "MIT" 10 | requires-python = ">=3.11" 11 | dynamic = ["version"] 12 | authors = [ 13 | { name = "Jeffrey C. Ollie", email = "" }, 14 | { name = "Ivan Schaller", email = "ivan@schaller.sh" }, 15 | ] 16 | classifiers = [ 17 | "License :: OSI Approved :: MIT License", 18 | "Natural Language :: English", 19 | "Operating System :: OS Independent", 20 | "Programming Language :: Python :: 3.11", 21 | ] 22 | dependencies = ["pynetbox~=7.5.0", "dnspython~=2.7.0"] 23 | 24 | [project.urls] 25 | Homepage = "https://github.com/olofvndrhr/octodns-netbox-dns" 26 | History = "https://github.com/olofvndrhr/octodns-netbox-dns/commits/master" 27 | Tracker = "https://github.com/olofvndrhr/octodns-netbox-dns/issues" 28 | Source = "https://github.com/olofvndrhr/octodns-netbox-dns" 29 | 30 | [tool.hatch.version] 31 | source = "regex_commit" 32 | path = "src/octodns_netbox_dns/__about__.py" 33 | tag_sign = false 34 | 35 | [tool.hatch.build.targets.sdist] 36 | packages = ["src/octodns_netbox_dns"] 37 | 38 | [tool.hatch.build.targets.wheel] 39 | packages = ["src/octodns_netbox_dns"] 40 | 41 | ### 42 | ### envs 43 | ### 44 | 45 | [tool.hatch.envs.default] 46 | python = "3.11" 47 | dependencies = [ 48 | "pytest~=8.3.5", 49 | "coverage~=7.8.0", 50 | "octodns==1.11.0", 51 | "octodns-spf==1.0.0", 52 | ] 53 | 54 | [tool.hatch.envs.default.scripts] 55 | test = "pytest {args:tests}" 56 | test-cov = ["coverage erase", "coverage run -m pytest {args:tests}"] 57 | cov-report = ["- coverage combine", "coverage report", "coverage xml"] 58 | cov = ["test-cov", "cov-report"] 59 | 60 | sync = ["cd dev && octodns-sync --debug --config-file sync.yml --force {args}"] 61 | sync2 = ["cd dev && octodns-sync --debug --config-file sync2.yml --force {args}"] 62 | dump = [ 63 | "cd dev && octodns-dump --debug --config-file sync.yml --output-dir output {args} '*' netbox", 64 | ] 65 | dump2 = [ 66 | "cd dev && octodns-dump --debug --config-file sync2.yml --output-dir output {args} '*' netbox", 67 | ] 68 | validate = ["cd dev && octodns-validate --debug --config-file sync.yml {args}"] 69 | validate2 = ["cd dev && octodns-validate --debug --config-file sync2.yml {args}"] 70 | 71 | [tool.hatch.envs.lint] 72 | python = "3.11" 73 | detached = true 74 | dependencies = ["mypy~=1.15.0", "ruff~=0.11.10"] 75 | 76 | [tool.hatch.envs.lint.scripts] 77 | typing = "mypy --non-interactive --install-types {args:src/octodns_netbox_dns}" 78 | style = ["ruff check --diff {args:.}", "ruff format --check --diff {args:.}"] 79 | fmt = ["ruff check --fix {args:.}", "ruff format {args:.}", "style"] 80 | all = ["style", "typing"] 81 | 82 | ### 83 | ### ruff 84 | ### 85 | 86 | [tool.ruff] 87 | target-version = "py311" 88 | line-length = 100 89 | indent-width = 4 90 | fix = true 91 | show-fixes = true 92 | respect-gitignore = true 93 | src = ["src", "tests"] 94 | exclude = [ 95 | ".direnv", 96 | ".git", 97 | ".mypy_cache", 98 | ".ruff_cache", 99 | ".svn", 100 | ".tox", 101 | ".nox", 102 | ".venv", 103 | "venv", 104 | "__pypackages__", 105 | "build", 106 | "dist", 107 | "node_modules", 108 | "venv", 109 | "dev", 110 | ] 111 | 112 | [tool.ruff.lint] 113 | select = [ 114 | "A", 115 | "ARG", 116 | "B", 117 | "C", 118 | "DTZ", 119 | "E", 120 | "EM", 121 | "F", 122 | "FBT", 123 | "I", 124 | "ICN", 125 | "ISC", 126 | "N", 127 | "PLC", 128 | "PLE", 129 | "PLR", 130 | "PLW", 131 | "Q", 132 | "RUF", 133 | "S", 134 | "T", 135 | "TID", 136 | "UP", 137 | "W", 138 | "YTT", 139 | ] 140 | ignore = [ 141 | "E501", 142 | "D103", 143 | "D100", 144 | "D102", 145 | "PLR2004", 146 | "D403", 147 | "ISC001", 148 | "FBT001", 149 | "FBT002", 150 | "FBT003", 151 | ] 152 | unfixable = ["F401"] 153 | 154 | [tool.ruff.format] 155 | quote-style = "double" 156 | indent-style = "space" 157 | skip-magic-trailing-comma = false 158 | line-ending = "lf" 159 | docstring-code-format = true 160 | 161 | [tool.ruff.lint.per-file-ignores] 162 | "__init__.py" = ["D104"] 163 | "__about__.py" = ["D104", "F841"] 164 | "tests/**/*" = ["PLR2004", "S101", "TID252"] 165 | 166 | [tool.ruff.lint.pyupgrade] 167 | keep-runtime-typing = true 168 | 169 | [tool.ruff.lint.isort] 170 | lines-after-imports = 2 171 | known-first-party = ["octodns_netbox_dns"] 172 | 173 | [tool.ruff.lint.flake8-tidy-imports] 174 | ban-relative-imports = "all" 175 | 176 | [tool.ruff.lint.pylint] 177 | max-branches = 24 178 | max-returns = 12 179 | max-statements = 100 180 | max-args = 15 181 | allow-magic-value-types = ["str", "bytes", "complex", "float", "int"] 182 | 183 | [tool.ruff.lint.mccabe] 184 | max-complexity = 15 185 | 186 | [tool.ruff.lint.pydocstyle] 187 | convention = "google" 188 | 189 | [tool.ruff.lint.pycodestyle] 190 | max-doc-length = 100 191 | 192 | ### 193 | ### mypy 194 | ### 195 | 196 | [tool.mypy] 197 | #plugins = ["pydantic.mypy"] 198 | follow_imports = "silent" 199 | warn_redundant_casts = true 200 | warn_unused_ignores = true 201 | disallow_any_generics = true 202 | check_untyped_defs = true 203 | no_implicit_reexport = true 204 | ignore_missing_imports = true 205 | warn_return_any = true 206 | pretty = true 207 | show_column_numbers = true 208 | show_error_codes = true 209 | show_error_context = true 210 | 211 | #[tool.pydantic-mypy] 212 | #init_forbid_extra = true 213 | #init_typed = true 214 | #warn_required_dynamic_aliases = true 215 | 216 | ### 217 | ### pytest 218 | ### 219 | 220 | [tool.pytest.ini_options] 221 | pythonpath = ["src"] 222 | addopts = "--color=yes --exitfirst --verbose -ra" 223 | filterwarnings = [ 224 | 'ignore:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning', 225 | ] 226 | 227 | ### 228 | ### coverage 229 | ### 230 | 231 | [tool.coverage.run] 232 | source_pkgs = ["octodns_netbox_dns", "tests"] 233 | branch = true 234 | parallel = true 235 | omit = ["src/octodns_netbox_dns/__about__.py"] 236 | 237 | [tool.coverage.paths] 238 | octodns_netbox_dns = [ 239 | "src/octodns_netbox_dns", 240 | "*/octodns-netbox-dns/src/octodns_netbox_dns", 241 | ] 242 | tests = ["tests", "*/octodns-netbox-dns/tests"] 243 | 244 | [tool.coverage.report] 245 | # Regexes for lines to exclude from consideration 246 | exclude_lines = [ 247 | # Have to re-enable the standard pragma 248 | "pragma: no cover", 249 | # Don't complain about missing debug-only code: 250 | "def __repr__", 251 | "if self.debug", 252 | # Don't complain if tests don't hit defensive assertion code: 253 | "raise AssertionError", 254 | "raise NotImplementedError", 255 | # Don't complain if non-runnable code isn't run: 256 | "if 0:", 257 | "if __name__ == .__main__.:", 258 | # Don't complain about abstract methods, they aren't run: 259 | "@(abc.)?abstractmethod", 260 | "no cov", 261 | "if TYPE_CHECKING:", 262 | ] 263 | # ignore_errors = true 264 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["local>44net/renovate"] 4 | } 5 | -------------------------------------------------------------------------------- /src/octodns_netbox_dns/__about__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.3.12" 2 | -------------------------------------------------------------------------------- /src/octodns_netbox_dns/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any, Literal 3 | 4 | import dns.rdata 5 | import octodns.provider.base 6 | import octodns.provider.plan 7 | import octodns.record 8 | import octodns.zone 9 | import pynetbox.core.api 10 | import pynetbox.core.response 11 | 12 | 13 | class NetBoxDNSProvider(octodns.provider.base.BaseProvider): 14 | """OctoDNS provider for NetboxDNS""" 15 | 16 | SUPPORTS_GEO = False 17 | SUPPORTS_DYNAMIC = True 18 | SUPPORTS_ROOT_NS = True 19 | SUPPORTS_MULTIVALUE_PTR = True 20 | SUPPORTS = { # noqa 21 | "A", 22 | "AAAA", 23 | # "ALIAS", 24 | "CAA", 25 | "CNAME", 26 | "DNAME", 27 | # "DS", 28 | "LOC", 29 | "MX", 30 | # "NAPTR", 31 | "NS", 32 | "PTR", 33 | # "SPF", 34 | "SRV", 35 | "SSHFP", 36 | # "TLSA", 37 | "TXT", 38 | # "URLFWD", 39 | } 40 | 41 | def __init__( 42 | self, 43 | id: int, # noqa 44 | url: str, 45 | token: str, 46 | view: str | None | Literal[False] = False, 47 | ttl=3600, 48 | replace_duplicates=False, 49 | make_absolute=False, 50 | disable_ptr=True, 51 | insecure_request=False, 52 | *args, 53 | **kwargs, 54 | ) -> None: 55 | """initialize the NetBoxDNSProvider""" 56 | self.log = logging.getLogger(f"NetBoxDNSProvider[{id}]") 57 | self.log.debug( 58 | f"__init__: {id=}, {url=}, {view=}, {replace_duplicates=}, {make_absolute=}, {disable_ptr=}, {args=}, {kwargs=}" 59 | ) 60 | super().__init__(id, *args, **kwargs) 61 | 62 | self.api = pynetbox.core.api.Api(url, token) 63 | if insecure_request: 64 | import urllib3 65 | 66 | urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) 67 | self.api.http_session.verify = False 68 | self.nb_view = self._get_nb_view(view) 69 | self.ttl = ttl 70 | self.replace_duplicates = replace_duplicates 71 | self.make_absolute = make_absolute 72 | self.disable_ptr = disable_ptr 73 | 74 | def _make_absolute(self, value: str, force: bool = False) -> str: 75 | """return dns name with trailing dot to make it absolute 76 | 77 | @param value: dns record value 78 | @param force: when `True`, disregard configuration option `make_absolute` 79 | 80 | @return: absolute dns record value 81 | """ 82 | if value.endswith("."): 83 | return value 84 | 85 | if not (self.make_absolute or force): 86 | return value 87 | 88 | absolute_value = value + "." 89 | self.log.debug(f"relative={value}, absolute={absolute_value}") 90 | 91 | return absolute_value 92 | 93 | def _escape_semicolon(self, value: str) -> str: 94 | fixed = value.replace(";", r"\;") 95 | self.log.debug(rf"in='{value}', escaped='{fixed}'") 96 | return fixed 97 | 98 | def _unescape_semicolon(self, value: str) -> str: 99 | fixed = value.replace(r"\\", "\\").replace(r"\;", ";") 100 | self.log.debug(rf"in='{value}', unescaped='{fixed}'") 101 | return fixed 102 | 103 | def _get_nb_view(self, view: str | None | Literal[False]) -> dict[str, int | str]: 104 | """get the correct netbox view when requested 105 | 106 | @param view: `False` for no view, `None` for zones without a view, else the view name 107 | 108 | @return: the netbox view id in the netbox query format 109 | """ 110 | if view is False: 111 | return {} 112 | if view is None: 113 | return {"view": "null"} 114 | 115 | nb_view: pynetbox.core.response.Record | None = self.api.plugins.netbox_dns.views.get( 116 | name=view 117 | ) 118 | if nb_view is None: 119 | msg = f"dns view={view}, has not been found" 120 | self.log.error(msg) 121 | raise ValueError(msg) 122 | 123 | self.log.debug(f"found view={nb_view.name}, id={nb_view.id}") 124 | 125 | return {"view_id": nb_view.id} 126 | 127 | def _get_nb_zone(self, name: str, view: dict[str, str | int]) -> pynetbox.core.response.Record: 128 | """given a zone name and a view name, look it up in NetBox. 129 | 130 | @param name: name of the dns zone 131 | @param view: the netbox view id in the api query format 132 | 133 | @raise pynetbox.RequestError: if declared view is not existent 134 | 135 | @return: the netbox dns zone object 136 | """ 137 | query_params = {"name": name[:-1], **view} 138 | nb_zone: pynetbox.core.response.Record | None = self.api.plugins.netbox_dns.zones.get( 139 | **query_params 140 | ) 141 | if nb_zone is None: 142 | msg = f"dns zone={name}, has not been found" 143 | self.log.error(msg) 144 | raise ValueError(msg) 145 | 146 | self.log.debug(f"found zone={nb_zone.name}, id={nb_zone.id}") 147 | 148 | return nb_zone 149 | 150 | def _format_rdata(self, rcd_type: str, rcd_value: str) -> str | dict[str, Any]: 151 | """format netbox record values to correct octodns record values 152 | 153 | @param rcd_type: record type 154 | @param rcd_value: record value 155 | 156 | @return: formatted rrdata value 157 | """ 158 | rdata = dns.rdata.from_text("IN", rcd_type, rcd_value) 159 | match rdata.rdtype.name: 160 | case "A" | "AAAA": 161 | value = rdata.address 162 | 163 | case "CNAME" | "DNAME" | "NS" | "PTR": 164 | value = self._make_absolute(rdata.target.to_text()) 165 | 166 | case "CAA": 167 | value = { 168 | "flags": rdata.flags, 169 | "tag": rdata.tag.decode(), 170 | "value": rdata.value.decode(), 171 | } 172 | 173 | case "LOC": 174 | value = { 175 | "lat_degrees": rdata.latitude[0], 176 | "lat_minutes": rdata.latitude[1], 177 | "lat_seconds": rdata.latitude[2] + rdata.latitude[3] / 1000, 178 | "lat_direction": "N" if rdata.latitude[4] >= 0 else "S", 179 | "long_degrees": rdata.longitude[0], 180 | "long_minutes": rdata.longitude[1], 181 | "long_seconds": rdata.longitude[2] + rdata.longitude[3] / 1000, 182 | "long_direction": "W" if rdata.latitude[4] >= 0 else "E", 183 | "altitude": rdata.altitude / 100, 184 | "size": rdata.size / 100, 185 | "precision_horz": rdata.horizontal_precision / 100, 186 | "precision_vert": rdata.vertical_precision / 100, 187 | } 188 | 189 | case "MX": 190 | value = { 191 | "preference": rdata.preference, 192 | "exchange": self._make_absolute(rdata.exchange.to_text()), 193 | } 194 | 195 | case "NAPTR": 196 | value = { 197 | "order": rdata.order, 198 | "preference": rdata.preference, 199 | "flags": rdata.flags, 200 | "service": rdata.service, 201 | "regexp": rdata.regexp, 202 | "replacement": rdata.replacement.to_text(), 203 | } 204 | 205 | case "SSHFP": 206 | value = { 207 | "algorithm": rdata.algorithm, 208 | "fingerprint_type": rdata.fp_type, 209 | "fingerprint": rdata.fingerprint.hex(), 210 | } 211 | 212 | case "TXT": 213 | value = self._escape_semicolon(rcd_value) 214 | 215 | case "SRV": 216 | value = { 217 | "priority": rdata.priority, 218 | "weight": rdata.weight, 219 | "port": rdata.port, 220 | "target": self._make_absolute(rdata.target.to_text()), 221 | } 222 | 223 | case "ALIAS" | "DS" | "NAPTR" | "SPF" | "TLSA" | "URLFWD" | "SOA": 224 | self.log.debug(f"'{rcd_type}' record type not implemented. ignoring record") 225 | raise NotImplementedError 226 | 227 | case _: 228 | self.log.error(f"ignoring invalid record with type: '{rcd_type}'") 229 | raise NotImplementedError 230 | 231 | self.log.debug(rf"formatted record value={value}") 232 | 233 | return value # type:ignore 234 | 235 | def _format_nb_records(self, zone: octodns.zone.Zone) -> list[dict[str, Any]]: 236 | """format netbox dns records to the octodns format 237 | 238 | @param zone: octodns zone 239 | 240 | @return: a list of octodns compatible record dicts 241 | """ 242 | records: dict[tuple[str, str], dict[str, Any]] = {} 243 | 244 | nb_zone = self._get_nb_zone(zone.name, view=self.nb_view) 245 | if not nb_zone: 246 | self.log.error(f"zone={zone.name}, not found in view={self.nb_view}") 247 | raise LookupError 248 | 249 | nb_records: pynetbox.core.response.RecordSet = self.api.plugins.netbox_dns.records.filter( 250 | zone_id=nb_zone.id, status="active" 251 | ) 252 | for nb_record in nb_records: 253 | rcd_name: str = "" if nb_record.name == "@" else nb_record.name 254 | rcd_value: str = ( 255 | self._make_absolute(nb_record.zone.name, True) 256 | if nb_record.value == "@" 257 | else nb_record.value 258 | ) 259 | rcd_type: str = nb_record.type 260 | rcd_ttl: int = nb_record.ttl or nb_zone.default_ttl 261 | if nb_record.type == "NS": 262 | rcd_ttl = nb_zone.soa_refresh 263 | 264 | rcd_data = { 265 | "name": rcd_name, 266 | "type": rcd_type, 267 | "ttl": rcd_ttl, 268 | "values": [], 269 | } 270 | self.log.debug(rf"working on record={rcd_data}, value={rcd_value}") 271 | 272 | try: 273 | rcd_rdata = self._format_rdata(rcd_type, rcd_value) 274 | except NotImplementedError: 275 | continue 276 | 277 | if (rcd_name, rcd_type) not in records: 278 | records[(rcd_name, rcd_type)] = rcd_data 279 | 280 | records[(rcd_name, rcd_type)]["values"].append(rcd_rdata) 281 | 282 | self.log.debug(rf"record data={records[(rcd_name, rcd_type)]}") 283 | 284 | return list(records.values()) 285 | 286 | def populate( 287 | self, zone: octodns.zone.Zone, target: bool = False, lenient: bool = False 288 | ) -> bool: 289 | """get all the records of a zone from NetBox and add them to the OctoDNS zone 290 | 291 | @param zone: octodns zone 292 | @param target: when `True`, load the current state of the provider. 293 | @param lenient: when `True`, skip record validation and do a "best effort" load of data. 294 | 295 | @return: true if the zone exists, else false. 296 | """ 297 | self.log.info(f"--> populate '{zone.name}', target={target}, lenient={lenient}") 298 | 299 | try: 300 | records = self._format_nb_records(zone) 301 | except LookupError: 302 | return False 303 | 304 | for data in records: 305 | if len(data["values"]) == 1: 306 | data["value"] = data.pop("values")[0] 307 | record = octodns.record.Record.new( 308 | zone=zone, 309 | name=data["name"], 310 | data=data, 311 | source=self, 312 | lenient=lenient, 313 | ) 314 | zone.add_record(record, lenient=lenient, replace=self.replace_duplicates) 315 | 316 | self.log.info(f"populate -> found {len(zone.records)} records for zone '{zone.name}'") 317 | 318 | return True 319 | 320 | def _format_changeset(self, change: Any) -> set[str]: 321 | """format the changeset 322 | 323 | @param change: the raw changes 324 | 325 | @return: the formatted/escaped changeset 326 | """ 327 | match change._type: 328 | case "CAA": 329 | changeset = {repr(v) for v in change.values} 330 | case "TXT": 331 | changeset = {self._unescape_semicolon(repr(v)[1:-1]) for v in change.values} 332 | case _: 333 | match change: 334 | case octodns.record.ValueMixin(): 335 | changeset = {repr(change.value)[1:-1]} 336 | case octodns.record.ValuesMixin(): 337 | changeset = {repr(v)[1:-1] for v in change.values} 338 | case _: 339 | raise ValueError 340 | 341 | self.log.debug(f"{changeset=}") 342 | 343 | return changeset 344 | 345 | def _include_change(self, _change: octodns.record.change.Change) -> bool: 346 | """filter out record types which the provider can't create in netbox 347 | 348 | @param change: the planned change 349 | 350 | @return: false if the change should be discarded, true if it should be kept. 351 | """ 352 | return True # currently unused 353 | 354 | def _apply(self, plan: octodns.provider.plan.Plan) -> None: 355 | """apply the changes to the NetBox DNS zone. 356 | 357 | @param plan: the planned changes 358 | 359 | @return: none 360 | """ 361 | self.log.debug(f"--> _apply zone={plan.desired.name}, changes={len(plan.changes)}") 362 | 363 | nb_zone = self._get_nb_zone(plan.desired.name, view=self.nb_view) 364 | 365 | for change in plan.changes: 366 | match change: 367 | case octodns.record.Create(): 368 | rcd_name = "@" if change.new.name == "" else change.new.name 369 | 370 | new_changeset = self._format_changeset(change.new) 371 | for record in new_changeset: 372 | self.log.debug(rf"ADD {change.new._type} {rcd_name} {record}") 373 | self.api.plugins.netbox_dns.records.create( 374 | zone=nb_zone.id, 375 | name=rcd_name, 376 | type=change.new._type, 377 | ttl=change.new.ttl, 378 | value=record, 379 | disable_ptr=self.disable_ptr, 380 | ) 381 | 382 | case octodns.record.Delete(): 383 | nb_records: pynetbox.core.response.RecordSet = ( 384 | self.api.plugins.netbox_dns.records.filter( 385 | zone_id=nb_zone.id, 386 | name=change.existing.name, 387 | type=change.existing._type, 388 | ) 389 | ) 390 | 391 | existing_changeset = self._format_changeset(change.existing) 392 | for nb_record in nb_records: 393 | for record in existing_changeset: 394 | if nb_record.value != record: 395 | continue 396 | self.log.debug( 397 | rf"DELETE {nb_record.type} {nb_record.name} {nb_record.value}" 398 | ) 399 | nb_record.delete() 400 | 401 | case octodns.record.Update(): 402 | rcd_name = "@" if change.existing.name == "" else change.existing.name 403 | 404 | nb_records = self.api.plugins.netbox_dns.records.filter( 405 | zone_id=nb_zone.id, 406 | name=rcd_name, 407 | type=change.existing._type, 408 | ) 409 | 410 | existing_changeset = self._format_changeset(change.existing) 411 | new_changeset = self._format_changeset(change.new) 412 | 413 | to_delete = existing_changeset.difference(new_changeset) 414 | to_update = existing_changeset.intersection(new_changeset) 415 | to_create = new_changeset.difference(existing_changeset) 416 | 417 | for nb_record in nb_records: 418 | if nb_record.value in to_delete: 419 | self.log.debug( 420 | rf"DELETE {nb_record.type} {nb_record.name} {nb_record.value}" 421 | ) 422 | nb_record.delete() 423 | if nb_record.value in to_update: 424 | self.log.debug( 425 | rf"MODIFY (ttl) {nb_record.type} {nb_record.name} {nb_record.value}" 426 | ) 427 | nb_record.ttl = change.new.ttl 428 | nb_record.save() 429 | 430 | for record in to_create: 431 | self.log.debug(rf"ADD {change.new._type} {rcd_name} {record}") 432 | nb_record = self.api.plugins.netbox_dns.records.create( 433 | zone=nb_zone.id, 434 | name=rcd_name, 435 | type=change.new._type, 436 | ttl=change.new.ttl, 437 | value=record, 438 | disable_ptr=self.disable_ptr, 439 | ) 440 | 441 | def list_zones(self) -> list[str]: 442 | """get all zones from netbox 443 | 444 | @return: a list with all active zones 445 | """ 446 | query_params = {"status": "active", **self.nb_view} 447 | zones = self.api.plugins.netbox_dns.zones.filter(**query_params) 448 | absolute_zones = [self._make_absolute(z.name, True) for z in zones] 449 | 450 | return sorted(absolute_zones) 451 | -------------------------------------------------------------------------------- /tests/test_escaple_semicolon.py: -------------------------------------------------------------------------------- 1 | from octodns_netbox_dns import NetBoxDNSProvider 2 | 3 | 4 | DEFAULT_CONFIG = { 5 | "id": 1, 6 | "url": "https://localhost:8000", 7 | "token": "", 8 | "view": False, 9 | "replace_duplicates": False, 10 | "make_absolute": True, 11 | } 12 | nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG) 13 | 14 | 15 | def test_escape1(): 16 | rcd_value = r"v=TLSRPTv1; rua=mailto:tlsrpt@example.com" 17 | value = nbdns._escape_semicolon(rcd_value) 18 | 19 | assert value == r"v=TLSRPTv1\; rua=mailto:tlsrpt@example.com" 20 | 21 | 22 | def test_escape2(): 23 | rcd_value = r"v=TLSRPTv1\; rua=mailto:tlsrpt@example.com" 24 | value = nbdns._escape_semicolon(rcd_value) 25 | 26 | assert value == r"v=TLSRPTv1\\; rua=mailto:tlsrpt@example.com" 27 | 28 | 29 | def test_escape3(): 30 | rcd_value = r"t=y\;o=~\;" 31 | value = nbdns._escape_semicolon(rcd_value) 32 | 33 | assert value == r"t=y\\;o=~\\;" 34 | 35 | 36 | def test_escape4(): 37 | rcd_value = r"t=y;o=~;" 38 | value = nbdns._escape_semicolon(rcd_value) 39 | 40 | assert value == r"t=y\;o=~\;" 41 | 42 | 43 | def test_unescape1(): 44 | rcd_value = r"v=TLSRPTv1\; rua=mailto:tlsrpt@example.com" 45 | value = nbdns._unescape_semicolon(rcd_value) 46 | 47 | assert value == r"v=TLSRPTv1; rua=mailto:tlsrpt@example.com" 48 | 49 | 50 | def test_unescape2(): 51 | rcd_value = r"v=TLSRPTv1\\; rua=mailto:tlsrpt@example.com" 52 | value = nbdns._unescape_semicolon(rcd_value) 53 | 54 | assert value == r"v=TLSRPTv1; rua=mailto:tlsrpt@example.com" 55 | 56 | 57 | def test_unescape3(): 58 | rcd_value = r"t=y\\;o=~\;" 59 | value = nbdns._unescape_semicolon(rcd_value) 60 | 61 | assert value == r"t=y;o=~;" 62 | 63 | 64 | def test_unescape4(): 65 | rcd_value = r"t=y;o=~;" 66 | value = nbdns._unescape_semicolon(rcd_value) 67 | 68 | assert value == r"t=y;o=~;" 69 | -------------------------------------------------------------------------------- /tests/test_make_absolute.py: -------------------------------------------------------------------------------- 1 | from octodns_netbox_dns import NetBoxDNSProvider 2 | 3 | 4 | DEFAULT_CONFIG = { 5 | "id": 1, 6 | "url": "https://localhost:8000", 7 | "token": "", 8 | "view": False, 9 | "replace_duplicates": False, 10 | "make_absolute": True, 11 | } 12 | 13 | nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG) 14 | 15 | 16 | def test_absolute(): 17 | rcd = "example.com" 18 | absolute = nbdns._make_absolute(rcd) 19 | 20 | assert absolute == "example.com." 21 | 22 | 23 | def test_noop(): 24 | rcd = "example.com." 25 | absolute = nbdns._make_absolute(rcd) 26 | 27 | assert absolute == "example.com." 28 | 29 | 30 | def test_disabled(): 31 | args = {**DEFAULT_CONFIG, "make_absolute": False} 32 | nbdns = NetBoxDNSProvider(**args) 33 | rcd = "example.com" 34 | relative = nbdns._make_absolute(rcd, force=False) 35 | 36 | assert relative == "example.com" 37 | 38 | 39 | def test_force(): 40 | args = {**DEFAULT_CONFIG, "make_absolute": False} 41 | nbdns = NetBoxDNSProvider(**args) 42 | rcd = "example.com" 43 | absolute = nbdns._make_absolute(rcd, force=True) 44 | 45 | assert absolute == "example.com." 46 | -------------------------------------------------------------------------------- /tests/test_records.py: -------------------------------------------------------------------------------- 1 | from octodns_netbox_dns import NetBoxDNSProvider 2 | 3 | 4 | DEFAULT_CONFIG = { 5 | "id": 1, 6 | "url": "https://localhost:8000", 7 | "token": "", 8 | "view": False, 9 | "replace_duplicates": False, 10 | "make_absolute": True, 11 | } 12 | 13 | nbdns = NetBoxDNSProvider(**DEFAULT_CONFIG) 14 | 15 | 16 | def test_a(): 17 | rcd_type = "A" 18 | rcd_value = "127.0.0.1" 19 | value = nbdns._format_rdata(rcd_type, rcd_value) 20 | 21 | assert value == "127.0.0.1" 22 | 23 | 24 | def test_aaaa(): 25 | rcd_type = "AAAA" 26 | rcd_value = "fc07::1" 27 | value = nbdns._format_rdata(rcd_type, rcd_value) 28 | 29 | assert value == "fc07::1" 30 | 31 | 32 | def test_alias(): 33 | pass # not supported 34 | 35 | 36 | def test_caa(): 37 | rcd_type = "CAA" 38 | rcd_value = '0 issue "letsencrypt.org"' 39 | value = nbdns._format_rdata(rcd_type, rcd_value) 40 | 41 | assert value == { 42 | "flags": 0, 43 | "tag": "issue", 44 | "value": "letsencrypt.org", 45 | } 46 | 47 | 48 | def test_cname(): 49 | rcd_type = "CNAME" 50 | rcd_value = "test.example.com." 51 | value = nbdns._format_rdata(rcd_type, rcd_value) 52 | 53 | assert value == "test.example.com." 54 | 55 | 56 | def test_dname(): 57 | rcd_type = "DNAME" 58 | rcd_value = "example.com." 59 | value = nbdns._format_rdata(rcd_type, rcd_value) 60 | 61 | assert value == "example.com." 62 | 63 | 64 | def test_ds(): 65 | pass # not supported 66 | 67 | 68 | def test_loc(): 69 | rcd_type = "LOC" 70 | rcd_value = "0 0 0 N 0 0 0 W 1 1 10000 10" 71 | value = nbdns._format_rdata(rcd_type, rcd_value) 72 | 73 | assert value == { 74 | "lat_degrees": 0, 75 | "lat_minutes": 0, 76 | "lat_seconds": 0.0, 77 | "lat_direction": "N", 78 | "long_degrees": 0, 79 | "long_minutes": 0, 80 | "long_seconds": 0.0, 81 | "long_direction": "W", 82 | "altitude": 1.0, 83 | "size": 1.0, 84 | "precision_horz": 10000.0, 85 | "precision_vert": 10.0, 86 | } 87 | 88 | 89 | def test_mx(): 90 | rcd_type = "MX" 91 | rcd_value = "10 mx.example.com" 92 | value = nbdns._format_rdata(rcd_type, rcd_value) 93 | 94 | assert value == { 95 | "preference": 10, 96 | "exchange": "mx.example.com.", 97 | } 98 | 99 | 100 | def test_naptr(): 101 | pass # not supported 102 | 103 | 104 | def test_ns(): 105 | rcd_type = "NS" 106 | rcd_value = "ns.example.com." 107 | value = nbdns._format_rdata(rcd_type, rcd_value) 108 | 109 | assert value == "ns.example.com." 110 | 111 | 112 | def test_ptr(): 113 | rcd_type = "PTR" 114 | rcd_value = "host.example.com." 115 | value = nbdns._format_rdata(rcd_type, rcd_value) 116 | 117 | assert value == "host.example.com." 118 | 119 | 120 | def test_spf(): 121 | pass # not supported 122 | 123 | 124 | def test_srv(): 125 | rcd_type = "SRV" 126 | rcd_value = r"0 5 25565 mc.example.com" 127 | value = nbdns._format_rdata(rcd_type, rcd_value) 128 | 129 | assert value == { 130 | "priority": 0, 131 | "weight": 5, 132 | "port": 25565, 133 | "target": "mc.example.com.", 134 | } 135 | 136 | 137 | def test_sshfp(): 138 | rcd_type = "SSHFP" 139 | rcd_value = "4 2 123456789abcdef67890123456789abcdef67890123456789abcdef123456789" 140 | value = nbdns._format_rdata(rcd_type, rcd_value) 141 | 142 | assert value == { 143 | "algorithm": 4, 144 | "fingerprint_type": 2, 145 | "fingerprint": "123456789abcdef67890123456789abcdef67890123456789abcdef123456789", 146 | } 147 | 148 | 149 | def test_tlsa(): 150 | pass # not supported 151 | 152 | 153 | def test_txt1(): 154 | rcd_type = "TXT" 155 | rcd_value = "v=TLSRPTv1; rua=mailto:tlsrpt@example.com" 156 | value = nbdns._format_rdata(rcd_type, rcd_value) 157 | 158 | assert value == r"v=TLSRPTv1\; rua=mailto:tlsrpt@example.com" 159 | 160 | 161 | def test_txt2(): 162 | rcd_type = "TXT" 163 | rcd_value = r"v=TLSRPTv1\; rua=mailto:tlsrpt@example.com" 164 | value = nbdns._format_rdata(rcd_type, rcd_value) 165 | 166 | assert value == r"v=TLSRPTv1\\; rua=mailto:tlsrpt@example.com" 167 | 168 | 169 | def test_txt3(): 170 | rcd_type = "TXT" 171 | rcd_value = r"v=DKIM1; k=rsa; p=/0f+sikE+k9ZKbn1BJu0/soWht/+Zd/nc/+Gy//mQ1B5sCKYKgAmYTSWkxRjFzkc6KAQhi+/IzaFogEV050wcscdC8Rc8lAQzDUFrMs2ZZK1vFtkwIDAQAB" 172 | value = nbdns._format_rdata(rcd_type, rcd_value) 173 | 174 | assert ( 175 | value 176 | == r"v=DKIM1\; k=rsa\; p=/0f+sikE+k9ZKbn1BJu0/soWht/+Zd/nc/+Gy//mQ1B5sCKYKgAmYTSWkxRjFzkc6KAQhi+/IzaFogEV050wcscdC8Rc8lAQzDUFrMs2ZZK1vFtkwIDAQAB" 177 | ) 178 | 179 | 180 | def test_txt4(): 181 | rcd_type = "TXT" 182 | rcd_value = r"t=y\;o=~\;" 183 | value = nbdns._format_rdata(rcd_type, rcd_value) 184 | 185 | assert value == r"t=y\\;o=~\\;" 186 | 187 | 188 | def test_txt5(): 189 | rcd_type = "TXT" 190 | rcd_value = r"t=y;o=~;" 191 | value = nbdns._format_rdata(rcd_type, rcd_value) 192 | 193 | assert value == r"t=y\;o=~\;" 194 | 195 | 196 | def test_urlfwd(): 197 | pass # not supported 198 | --------------------------------------------------------------------------------