Initial version
This commit is contained in:
25
.editorconfig
Normal file
25
.editorconfig
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# EditorConfig is awesome: http://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Unix-style newlines with a newline ending every file
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
# 4 space indentation
|
||||||
|
[*.{py,java,r,R}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
# 2 space indentation
|
||||||
|
[*.{js,json,y{a,}ml,html,cwl}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.{md,Rmd,rst}]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
indent_style = space
|
||||||
162
.gitignore
vendored
Normal file
162
.gitignore
vendored
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
*.swp
|
||||||
|
config
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
1
.idea/.name
generated
Normal file
1
.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
docker-nsupdate-ddns
|
||||||
8
.idea/docker-nsupdate-ddns.iml
generated
Normal file
8
.idea/docker-nsupdate-ddns.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="jdk" jdkName="Pipenv (docker-nsupdate-ddns)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
4
.idea/misc.xml
generated
Normal file
4
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Pipenv (docker-bind-ddns)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/../docker-nsupdate-ddns/.idea/docker-nsupdate-ddns.iml" filepath="$PROJECT_DIR$/../docker-nsupdate-ddns/.idea/docker-nsupdate-ddns.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
Pipfile
Normal file
14
Pipfile
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[[source]]
|
||||||
|
name = "pypi"
|
||||||
|
url = "https://pypi.org/simple"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[dev-packages]
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
docker = "*"
|
||||||
|
python-dotenv = "*"
|
||||||
|
dnspython = "*"
|
||||||
|
|
||||||
|
[requires]
|
||||||
|
python_version = "3.6"
|
||||||
89
Pipfile.lock
generated
Normal file
89
Pipfile.lock
generated
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"_meta": {
|
||||||
|
"hash": {
|
||||||
|
"sha256": "2a9a9c218218349a7c550a97bb3bc3b6113227211c48e027755bdc8baf5822a1"
|
||||||
|
},
|
||||||
|
"pipfile-spec": 6,
|
||||||
|
"requires": {
|
||||||
|
"python_version": "3.6"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "pypi",
|
||||||
|
"url": "https://pypi.org/simple",
|
||||||
|
"verify_ssl": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"certifi": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
|
||||||
|
"sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
|
||||||
|
],
|
||||||
|
"version": "==2022.6.15"
|
||||||
|
},
|
||||||
|
"charset-normalizer": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
|
||||||
|
"sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3'",
|
||||||
|
"version": "==2.0.12"
|
||||||
|
},
|
||||||
|
"dnspython": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e",
|
||||||
|
"sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.2.1"
|
||||||
|
},
|
||||||
|
"docker": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:7a79bb439e3df59d0a72621775d600bc8bc8b422d285824cb37103eab91d1ce0",
|
||||||
|
"sha256:d916a26b62970e7c2f554110ed6af04c7ccff8e9f81ad17d0d40c75637e227fb"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==5.0.3"
|
||||||
|
},
|
||||||
|
"idna": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
|
||||||
|
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3'",
|
||||||
|
"version": "==3.3"
|
||||||
|
},
|
||||||
|
"python-dotenv": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f",
|
||||||
|
"sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.20.0"
|
||||||
|
},
|
||||||
|
"requests": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61",
|
||||||
|
"sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"
|
||||||
|
],
|
||||||
|
"version": "==2.27.1"
|
||||||
|
},
|
||||||
|
"urllib3": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec",
|
||||||
|
"sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6"
|
||||||
|
],
|
||||||
|
"version": "==1.26.10"
|
||||||
|
},
|
||||||
|
"websocket-client": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:074e2ed575e7c822fc0940d31c3ac9bb2b1142c303eafcf3e304e6ce035522e8",
|
||||||
|
"sha256:6278a75065395418283f887de7c3beafb3aa68dada5cacbe4b214e8d26da499b"
|
||||||
|
],
|
||||||
|
"version": "==1.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"develop": {}
|
||||||
|
}
|
||||||
100
README.md
Normal file
100
README.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# docker-nsupdate-ddns
|
||||||
|
|
||||||
|
This script pushes container/ip information from a Docker instance to a DNS server via the DNS update mechanism desribed in RFC 2136 (nsupdate). nsupdate is implemented by ISC Bind9.
|
||||||
|
|
||||||
|
Every REFRESH_INTERVAL seconds, it queries all the Docker containers on the local host, finds their IP and pushes it with the name to the DNS server.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
The script takes environment variables or a config file. A sample config file is provided in `config.sample`. The file name of the config file can be passed as argument, but defaults to `config`.
|
||||||
|
|
||||||
|
The names of the environment variables is the same as in the config file. Environment variables have precendence over the config file.
|
||||||
|
|
||||||
|
### Environment variables
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
-e DOMAIN=int.mtak.nl \
|
||||||
|
-e HOSTNAME_LABEL=nl.mtak.docker-bind-ddns.hostname \
|
||||||
|
-e DEFAULT_NETWORK=10.100.0.192/26 \
|
||||||
|
-e REFRESH_INTERVAL=5 \
|
||||||
|
-e ONE_SHOT=True \
|
||||||
|
-e NAMESERVER=10.100.0.11 \
|
||||||
|
-e TSIG_NAME=dck1 \
|
||||||
|
-e TSIG_KEY=SyYXDCJ4kIs3qhvI= \
|
||||||
|
merijntjetak/docker-bind/ddns:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Config file
|
||||||
|
```bash
|
||||||
|
cat <<EOF >configfile
|
||||||
|
DOMAIN=int.mtak.nl
|
||||||
|
HOSTNAME_LABEL=nl.mtak.docker-bind-ddns.hostname
|
||||||
|
DEFAULT_NETWORK=10.100.0.192/26
|
||||||
|
REFRESH_INTERVAL=5
|
||||||
|
ONE_SHOT=True
|
||||||
|
NAMESERVER=10.100.0.11
|
||||||
|
TSIG_NAME=dck1
|
||||||
|
TSIG_KEY=SyYXDCJ4kIs3qhvI=
|
||||||
|
EOF
|
||||||
|
|
||||||
|
docker run -d \
|
||||||
|
-v `pwd`/configfile:/configfile
|
||||||
|
merijntjetak/docker-bind-ddns:latest /configfile
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- `DOMAIN` - Sets the domain in which the records are created. Needs to match the Bind zone.
|
||||||
|
- `HOSTNAME_LABEL` - Docker label to override the default record name with. Use with `docker --label=nl.mtak.docker-bind-ddns.hostname=nginx` to get `nginx.int.mtak.nl`
|
||||||
|
- `DEFAULT_NETWORK` - Preferred network to find IP for, in case there are multiple networks
|
||||||
|
- `REFRESH_INTERVAL` - Interval between updates
|
||||||
|
- `ONE_SHOT` - Set to True for the script to update once and immediately quit (nice for debugging)
|
||||||
|
- `NAMESERVER` - Nameserver to push updates to
|
||||||
|
- `TSIG_NAME` - Name of the TSIG key
|
||||||
|
- `TSIG_KEY` - TSIG-KEY
|
||||||
|
|
||||||
|
### Bind9 integration
|
||||||
|
|
||||||
|
1. Generate a key
|
||||||
|
|
||||||
|
`tsig-keygen clientname >/etc/bind/keys/clientname.key`
|
||||||
|
|
||||||
|
2. Include keys in your Bind9 configuration
|
||||||
|
|
||||||
|
`include "/etc/bind/keys/*";`
|
||||||
|
|
||||||
|
3. Allow updates to your zone:
|
||||||
|
|
||||||
|
```
|
||||||
|
zone "int.mtak.nl" {
|
||||||
|
type master;
|
||||||
|
file "/etc/bind/db/int.mtak.nl.zone";
|
||||||
|
update-policy {
|
||||||
|
grant clientname zonesub ANY;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Design
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- Eventual redunancy (Bind9 zone transfers to secondary)
|
||||||
|
- Support for multiple individual Docker servers
|
||||||
|
- IPv6 support
|
||||||
|
- Detect hostname in decreasing order of priority:
|
||||||
|
- label
|
||||||
|
- Container name
|
||||||
|
- Forward-only
|
||||||
|
- Clean up DNS (DNS is stateful but the script isn't, so there might be a mismatch)
|
||||||
|
|
||||||
|
### Todo
|
||||||
|
|
||||||
|
- Make event-driven with [Ahab](https://github.com/instacart/ahab)
|
||||||
|
- Add tests
|
||||||
|
|
||||||
|
### Alternatives
|
||||||
|
|
||||||
|
CoreDNS didn't fit the requirements, because zone transfers out of a CoreDNS server do not
|
||||||
|
include the records from the coredns-dockerdiscovery plugin.
|
||||||
|
|
||||||
8
config.sample
Normal file
8
config.sample
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
DOMAIN=int.mtak.nl
|
||||||
|
HOSTNAME_LABEL=nl.mtak.docker-bind-ddns.hostname
|
||||||
|
DEFAULT_NETWORK=10.100.0.192/26
|
||||||
|
REFRESH_INTERVAL=10
|
||||||
|
ONE_SHOT=True
|
||||||
|
NAMESERVER=10.100.0.11
|
||||||
|
TSIG_NAME=dck1
|
||||||
|
TSIG_KEY=SyYhvI=
|
||||||
1
lib/__init__.py
Normal file
1
lib/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
__all__ = ["container", "nsupdate"]
|
||||||
57
lib/container.py
Normal file
57
lib/container.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import docker
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
|
||||||
|
def get_container_name(container):
|
||||||
|
"""
|
||||||
|
Get name of container, try in the following order:
|
||||||
|
- Check if hostname_label is set
|
||||||
|
- Fall back to container Name
|
||||||
|
"""
|
||||||
|
x = container.attrs['Name'][1:]
|
||||||
|
|
||||||
|
if config['hostname_label'] in container.attrs['Config']['Labels']:
|
||||||
|
x = container.attrs['Config']['Labels'][config['hostname_label']]
|
||||||
|
|
||||||
|
x = x.replace("_", "-") # Be compliant with RFC1035
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def get_container_ip(container):
|
||||||
|
"""
|
||||||
|
Get IP of container. Try in the following order
|
||||||
|
- default_network
|
||||||
|
- First found network
|
||||||
|
- Fall back to ['NetworkSettings']['IPAddress']
|
||||||
|
"""
|
||||||
|
x = container.attrs['NetworkSettings']['IPAddress']
|
||||||
|
|
||||||
|
if next(iter(container.attrs['NetworkSettings']['Networks'])):
|
||||||
|
network_name = next(iter(container.attrs['NetworkSettings']['Networks']))
|
||||||
|
x = container.attrs['NetworkSettings']['Networks'][network_name]['IPAddress']
|
||||||
|
|
||||||
|
if config['default_network'] in container.attrs['NetworkSettings']['Networks']:
|
||||||
|
x = container.attrs['NetworkSettings']['Networks'][config['default_network']]
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def generate_container_list():
|
||||||
|
client = docker.from_env()
|
||||||
|
|
||||||
|
container_list = client.containers.list()
|
||||||
|
ipam4 = {}
|
||||||
|
|
||||||
|
for container in container_list:
|
||||||
|
container_name = get_container_name(container)
|
||||||
|
container_ip = get_container_ip(container)
|
||||||
|
if container_ip:
|
||||||
|
ipam4[container_name] = container_ip
|
||||||
|
|
||||||
|
return ipam4
|
||||||
|
|
||||||
|
|
||||||
|
def init(_config):
|
||||||
|
global config
|
||||||
|
config = _config
|
||||||
37
lib/nsupdate.py
Normal file
37
lib/nsupdate.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import dns.update
|
||||||
|
import dns.query
|
||||||
|
import dns.tsigkeyring
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
|
||||||
|
def add_records(records):
|
||||||
|
keyring = dns.tsigkeyring.from_text({config['tsig_name']: config['tsig_key']})
|
||||||
|
for hostname, ip in records.items():
|
||||||
|
print("Adding record for " + hostname + "(" + ip + ")")
|
||||||
|
|
||||||
|
rrtype = "A"
|
||||||
|
address = ipaddress.ip_address(ip)
|
||||||
|
if isinstance(address, ipaddress.IPv4Address):
|
||||||
|
rrtype = "A"
|
||||||
|
if isinstance(address, ipaddress.IPv6Address):
|
||||||
|
rrtype = "AAAA"
|
||||||
|
|
||||||
|
update = dns.update.Update(config['domain'], keyring=keyring)
|
||||||
|
update.add(hostname, 60, rrtype, ip)
|
||||||
|
dns.query.tcp(update, config['nameserver'], timeout=2)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_records(records):
|
||||||
|
keyring = dns.tsigkeyring.from_text({config['tsig_name']: config['tsig_key']})
|
||||||
|
for hostname, ip in records.items():
|
||||||
|
print("Deleting record for " + hostname + "(" + ip + ")")
|
||||||
|
update = dns.update.Update(config['domain'], keyring=keyring)
|
||||||
|
update.delete(hostname)
|
||||||
|
dns.query.tcp(update, config['nameserver'], timeout=2)
|
||||||
|
|
||||||
|
|
||||||
|
def init(_config):
|
||||||
|
global config
|
||||||
|
config = _config
|
||||||
64
main.py
Executable file
64
main.py
Executable file
@@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import time
|
||||||
|
|
||||||
|
from lib import *
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
ipam4_old = {}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
get_config()
|
||||||
|
|
||||||
|
if not config['one_shot']:
|
||||||
|
while True:
|
||||||
|
loop()
|
||||||
|
time.sleep(config['refresh_interval'])
|
||||||
|
|
||||||
|
loop()
|
||||||
|
|
||||||
|
|
||||||
|
def loop():
|
||||||
|
container.init(config)
|
||||||
|
ipam4 = container.generate_container_list()
|
||||||
|
global ipam4_old
|
||||||
|
|
||||||
|
additions4 = determine_additions(ipam4, ipam4_old)
|
||||||
|
deletions4 = determine_deletions(ipam4, ipam4_old)
|
||||||
|
|
||||||
|
nsupdate.init(config)
|
||||||
|
nsupdate.delete_records(deletions4)
|
||||||
|
nsupdate.add_records(additions4)
|
||||||
|
|
||||||
|
ipam4_old = ipam4
|
||||||
|
|
||||||
|
|
||||||
|
def get_config():
|
||||||
|
config_file = sys.argv[1] if len(sys.argv) >= 2 else 'config'
|
||||||
|
load_dotenv(config_file)
|
||||||
|
|
||||||
|
config['domain'] = os.environ.get("DOMAIN")
|
||||||
|
config['hostname_label'] = os.environ.get("HOSTNAME_LABEL")
|
||||||
|
config['default_network'] = os.environ.get("DEFAULT_NETWORK")
|
||||||
|
config['refresh_interval'] = int(os.environ.get("REFRESH_INTERVAL"))
|
||||||
|
config['one_shot'] = os.environ.get("ONE_SHOT").lower() in ['true', 'yes']
|
||||||
|
config['nameserver'] = os.environ.get("NAMESERVER")
|
||||||
|
config['tsig_name'] = os.environ.get("TSIG_NAME")
|
||||||
|
config['tsig_key'] = os.environ.get("TSIG_KEY")
|
||||||
|
|
||||||
|
|
||||||
|
def determine_additions(ipam, ipam_old):
|
||||||
|
return {k: v for k, v in ipam.items() if k not in ipam_old}
|
||||||
|
|
||||||
|
|
||||||
|
def determine_deletions(ipam, ipam_old):
|
||||||
|
return {k: v for k, v in ipam_old.items() if k not in ipam}
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user