1
0
mirror of https://github.com/oskar456/dzonegit.git synced 2024-05-11 05:55:41 +00:00

13 Commits

Author SHA1 Message Date
Ondřej Caletka
08e2ea93fb version 0.4 2018-08-15 14:04:05 +02:00
Ondřej Caletka
45046429d1 Update docs 2018-08-15 14:03:15 +02:00
Ondřej Caletka
293bf930a1 Better multi-call support
Also supports calling by secondary argument (kind-of busybox
stylekind-of). Drop support for multicall setuptools wrapper – when
installed using setuptools, the user should call explicit function
instead.
2018-08-15 13:32:17 +02:00
Ondřej Caletka
c9026ff21b Revert "Drop dzonegit multi-call executable"
This reverts commit 9a521350d3.
Since there is no Python dependency, it may be actually handy to call
the script as is.
2018-08-15 13:17:33 +02:00
Ondřej Caletka
b608c25372 Revert "Add Experimental filters for smudging/cleaning the SOA serial"
This reverts commit 331df2a4ec.
Increasing serial during the cleaning phase does not work as expected.
Maybe the way is to smudge serial to current unix time during checkout
on the server.
2018-08-15 13:08:08 +02:00
Ondřej Caletka
27ae5ff210 version 0.3 2018-08-10 15:49:29 +02:00
Ondřej Caletka
331df2a4ec Add Experimental filters for smudging/cleaning the SOA serial 2018-08-10 15:48:02 +02:00
Ondřej Caletka
1f79f52b1a Allow wildcards in zone blacklists and whitelists 2018-08-10 12:51:21 +02:00
Ondřej Caletka
9a521350d3 Drop dzonegit multi-call executable 2018-08-10 12:30:51 +02:00
Ondřej Caletka
5988fd005e version 0.2 2018-07-23 14:05:25 +02:00
Ondřej Caletka
7e6376ffb2 Add template wildcard matches 2018-07-23 14:04:46 +02:00
Ondřej Caletka
c3a181be14 Fix Knot DNS template example 2018-07-19 16:38:48 +02:00
Ondřej Caletka
6f23c066bc Add Travis CI config 2018-07-19 10:50:06 +02:00
5 changed files with 129 additions and 44 deletions

13
.travis.yml Normal file
View File

@@ -0,0 +1,13 @@
before_install:
- sudo apt-get install -y bind9utils
language: python
python:
- "3.5"
- "3.6"
- "nightly"
install:
- pip install -e .
- pip install pytest
script:
- pytest
sudo: false

View File

@@ -17,36 +17,47 @@ Main features
- enforce updating serial number when zone content is changed - enforce updating serial number when zone content is changed
- both ``pre-commit`` and ``pre-receive``/``update`` hooks to enforce similar checks in the remote repository - both ``pre-commit`` and ``pre-receive``/``update`` hooks to enforce similar checks in the remote repository
- ``post-receive`` hook to checkout the working copy from a bare repository, generate config snippets for various DNS server software and reload them - ``post-receive`` hook to checkout the working copy from a bare repository, generate config snippets for various DNS server software and reload them
- only Python standard library is used - only Python 3.5+ standard library is used
Requirements Requirements
------------ ------------
- Python 3.5+ - Python 3.5+
- `named-compilezone(8)`_ (part of BIND9 package) - `named-compilezone(8)`_ (part of `bind9utils` package)
- git - git
Instalation and usage Simple instalation (especially for workstations)
--------------------- ------------------------------------------------
- install required dependencies Since there is no other Python dependency than the standard library, you can
- install ``dzonegit`` package using your favourite tool (``virtualenvwrapper``, simply download the `dzonegit.py` file, make it executable and rename/symlink
``venv``, ``pipenv``, etc.) it to an appropriate hook location inside the Git repository
`.git/hooks/pre-commit`. This is especially handy for the end users not
experienced with Python packaging ecosystem.
Full instalation and usage
--------------------------
- install all the requirements
- install ``dzonegit`` Python package using your
favourite tool (``virtualenvwrapper``, ``venv``, ``pipenv``, etc.)
- in the local repository, create a symlink for the ``pre-commit`` hook: - in the local repository, create a symlink for the ``pre-commit`` hook:
``$ ln -s $(which dzonegit-pre-commit) /path/to/repo/.git/hooks/pre-commit`` ``$ ln -s $(which dzonegit-pre-commit) /path/to/repo/.git/hooks/pre-commit``
- on the server, install some git repository management software, preferrably Gitolite_ - on the server, install some git repository management software,
- on the server, install either ``pre-receive`` or ``update`` hook (both do the same) as preferably Gitolite_
well as ``post-receive`` hook. See `Gitolite documentation on how to add custom hooks`_ - on the server, install either ``pre-receive`` or ``update`` hook
- on the server set up the configuration options for each repository (both do the same) as well as the ``post-receive`` hook. See `Gitolite
documentation on how to add custom hooks`_
- on the server, set up the configuration options for each repository
Configuration options Configuration options
--------------------- ---------------------
All configuration options are stored in `git-config(1)`_ in section named ``dzonegit``. All configuration options are stored in `git-config(1)`_ in the section
All boolean options default to *False*. named ``dzonegit``. All boolean options default to *False*.
*dzonegit.ignorewhitespaceerrors* *dzonegit.ignorewhitespaceerrors*
@@ -76,7 +87,7 @@ All boolean options default to *False*.
can be provided by appending single digit from 1 to 9 to this option. can be provided by appending single digit from 1 to 9 to this option.
*dzonegit.zonereloadcmd* *dzonegit.zonereloadcmd*
A command to run for each zone, whose zone file has been modified. Zone A command to run for each zone, where zone file has been modified. Zone
name is automatically appended as the last argument. Should do something name is automatically appended as the last argument. Should do something
like ``rndc reload``. More commands can be provided by appending single digit like ``rndc reload``. More commands can be provided by appending single digit
from 1 to 9 to this option. from 1 to 9 to this option.
@@ -84,20 +95,27 @@ All boolean options default to *False*.
*dzonegit.zoneblacklist* *dzonegit.zoneblacklist*
Path to a text file containing list of zone names without trailing dots, Path to a text file containing list of zone names without trailing dots,
one per line. If zone is found on the blacklist, it is ignored when one per line. If zone is found on the blacklist, it is ignored when
``post-receive`` hook generates configuration. ``post-receive`` hook generates configuration. Wildcards can be used as
well, see `JSON template`_ below.
*dzonegit.zonewhitelist* *dzonegit.zonewhitelist*
Path to a text file containing list of zone names without trailing dots, Path to a text file containing list of zone names without trailing dots,
one per line. If not empty and zone is not found on the whitelist, one per line. If not empty and zone is not found on the whitelist,
it is ignored when ``post-receive`` hook generates configuration. it is ignored when ``post-receive`` hook generates configuration. Wildcards
can be used as well, see `JSON template`_ below.
JSON template JSON template
------------- -------------
The DNS server configuration snippets are generated using a simple JSON-based The DNS server configuration snippets are generated using a simple JSON-based
template. All keys are optional but please make sure the file is a valid template. All keys are optional but please make sure the file is a valid JSON
JSON file. It is possible to define a zone-specific options, for instance for file. It is possible to define a zone-specific options, for instance for
changing DNSSEC parameters per zone. changing DNSSEC parameters per zone. Those zone-specific options allow usage of
wildcards; if an exact match of zone name is not found, the leftmost label is
substituted with `*`. If still no match is found, the leftmost label is dropped
and the second one is again substituted with `*`. In the end, a single `*` is
checked. Only if even this key is not found, the value of *defaultvar* is used
as the zone-specific option.
Valid keys are: Valid keys are:
@@ -112,19 +130,22 @@ Valid keys are:
*defaultvar* *defaultvar*
A string that would template variable ``$zonevar`` expand to if there is not A string that would template variable ``$zonevar`` expand to if there is not
a zone-specific variable defined. a zone-specific variable defined, nor any wildcard matched.
*zonevars* *zonevars*
An object mapping zone names (without the final dot) to a zone-specific An object mapping zone names (without the final dot) to a zone-specific
variable to which template variable ``$zonevar`` would expand to. variable to which template variable ``$zonevar`` would expand to. Using
wildcards is possible by replacing the leftmost label with `*`. Ultimately,
a key with label `*` will match every single zone (making *defaultvar*
option litte bit pointless)
In the template strings, these placeholders are supported: In the template strings, these placeholders are supported:
``$datetime`` ``$datetime``
Current timestamp Current date and time in human readable format
``$zonename`` ``$zonename``
Zone name, without trailing dot Zone name, without the trailing dot
``$zonefile`` ``$zonefile``
Full path to the zone file Full path to the zone file
@@ -138,12 +159,14 @@ Example JSON template for Knot DNS
.. code-block:: json .. code-block:: json
{ {
"header": "# Managed by dzonegit, do not edit.\n", "header": "# Managed by dzonegit, do not edit.\nzone:",
"footer": "", "footer": "",
"item": " - zone: \"$zonename\"\n file: \"$zonefile\"\n $zonevar\n", "item": " - domain: \"$zonename\"\n file: \"$zonefile\"\n $zonevar\n",
"defaultvar": "template: default", "defaultvar": "template: default",
"zonevars": { "zonevars": {
"example.com": "template: signed" "example.com": "template: signed",
"*.cz": "template: czdomains",
"*.in-addr.arpa": "template: ipv4reverse"
} }
} }

View File

@@ -268,6 +268,23 @@ def replace_serial(path, oldserial, newserial):
path.write_text(updated) path.write_text(updated)
def get_zone_wildcards(name):
""" A generator of wildcards out of a zone name.
For a DNS name, returns series of:
- the name itself
- the name with first label substitued as *
- the name with first label dropped and second substittuted as *
- ...
- single *
"""
yield name
labels = name.split(".")
while labels:
labels[0] = "*"
yield ".".join(labels)
labels.pop(0)
def template_config(checkoutpath, template, blacklist=set(), whitelist=set()): def template_config(checkoutpath, template, blacklist=set(), whitelist=set()):
""" Recursively find all *.zone files and template config file using """ Recursively find all *.zone files and template config file using
a simple JSON based template like this: a simple JSON based template like this:
@@ -278,7 +295,9 @@ def template_config(checkoutpath, template, blacklist=set(), whitelist=set()):
"item": " - zone: \"$zonename\"\n file: \"$zonefile\"\n $zonevar\n", "item": " - zone: \"$zonename\"\n file: \"$zonefile\"\n $zonevar\n",
"defaultvar": "template: default", "defaultvar": "template: default",
"zonevars": { "zonevars": {
"example.com": "template: signed" "example.com": "template: signed",
"*.com": "template: dotcom",
"*": "template: uberdefault"
} }
} }
@@ -301,13 +320,16 @@ def template_config(checkoutpath, template, blacklist=set(), whitelist=set()):
out.append(headertpl.substitute(mapping)) out.append(headertpl.substitute(mapping))
for f in sorted(Path(checkoutpath).glob("**/*.zone")): for f in sorted(Path(checkoutpath).glob("**/*.zone")):
zonename = get_zone_name(f, f.read_bytes()) zonename = get_zone_name(f, f.read_bytes())
if whitelist and zonename not in whitelist: if whitelist and not any(
n in whitelist
for n in get_zone_wildcards(zonename)
):
print( print(
"WARNING: Ignoring zone {} - not whitelisted for " "WARNING: Ignoring zone {} - not whitelisted for "
"this repository.".format(zonename), "this repository.".format(zonename),
) )
continue continue
if zonename in blacklist: if any(n in blacklist for n in get_zone_wildcards(zonename)):
print( print(
"WARNING: Ignoring zone {} - blacklisted for " "WARNING: Ignoring zone {} - blacklisted for "
"this repository.".format(zonename), "this repository.".format(zonename),
@@ -323,7 +345,12 @@ def template_config(checkoutpath, template, blacklist=set(), whitelist=set()):
) )
continue continue
zones[zonename] = f.relative_to(checkoutpath) zones[zonename] = f.relative_to(checkoutpath)
zonevar = zonevars[zonename] if zonename in zonevars else defaultvar for name in get_zone_wildcards(zonename):
if name in zonevars:
zonevar = zonevars[name]
break
else:
zonevar = defaultvar
out.append(itemtpl.substitute( out.append(itemtpl.substitute(
mapping, zonename=zonename, mapping, zonename=zonename,
zonefile=str(f), zonevar=zonevar, zonefile=str(f), zonevar=zonevar,
@@ -468,17 +495,25 @@ def post_receive(stdin=sys.stdin):
subprocess.run(cmd) subprocess.run(cmd)
def get_action(argv=sys.argv):
name = Path(argv[0]).name
if "pre-commit" in name:
return pre_commit
if "update" in name:
return update
if "pre-receive" in name:
return pre_receive
if "post-receive" in name:
return post_receive
def main(): def main():
name = Path(sys.argv[0]).name action = get_action()
print(name) if action is None and len(sys.argv) > 1:
if name == "pre-commit": sys.argv.pop(0)
pre_commit() action = get_action()
elif name == "update": if action:
update() action()
elif name == "pre-receive":
pre_receive()
elif name == "post-receive":
post_receive()
else: else:
sys.exit("No valid command found") sys.exit("No valid command found")

View File

@@ -5,7 +5,7 @@ readme = Path(__file__).with_name("README.rst").read_text()
setup( setup(
name="dzonegit", name="dzonegit",
version="0.1", version="0.4",
description="Git hooks to manage a repository of DNS zones", description="Git hooks to manage a repository of DNS zones",
long_description=readme, long_description=readme,
long_description_content_type="text/x-rst", long_description_content_type="text/x-rst",
@@ -19,7 +19,6 @@ setup(
tests_require=["pytest"], tests_require=["pytest"],
entry_points={ entry_points={
"console_scripts": [ "console_scripts": [
"dzonegit = dzonegit:main",
"dzonegit-pre-commit = dzonegit:pre_commit", "dzonegit-pre-commit = dzonegit:pre_commit",
"dzonegit-pre-receive = dzonegit:pre_receive", "dzonegit-pre-receive = dzonegit:pre_receive",
"dzonegit-post-receive = dzonegit:post_receive", "dzonegit-post-receive = dzonegit:post_receive",

View File

@@ -298,12 +298,14 @@ def test_template_config(git_dir):
"item": " - zone: \"$zonename\"\n file: \"$zonefile\"\n $zonevar\n", "item": " - zone: \"$zonename\"\n file: \"$zonefile\"\n $zonevar\n",
"defaultvar": "template: default", "defaultvar": "template: default",
"zonevars": { "zonevars": {
"example.com": "template: signed" "example.com": "template: signed",
"*": "template: dummy"
} }
}""" }"""
output = dzonegit.template_config(str(git_dir), template) output = dzonegit.template_config(str(git_dir), template)
assert output.startswith("# Managed by dzonegit") assert output.startswith("# Managed by dzonegit")
assert " - zone: \"dummy\"\n file: \"" in output assert " - zone: \"dummy\"\n file: \"" in output
assert " template: dummy" in output
assert output.endswith("# This is the end") assert output.endswith("# This is the end")
output = dzonegit.template_config( output = dzonegit.template_config(
str(git_dir), str(git_dir),
@@ -311,9 +313,22 @@ def test_template_config(git_dir):
whitelist=set("a"), whitelist=set("a"),
) )
assert " - zone: \"dummy\"\n file: \"" not in output assert " - zone: \"dummy\"\n file: \"" not in output
output = dzonegit.template_config(
str(git_dir),
template,
blacklist=set("*"),
)
assert " - zone: \"dummy\"\n file: \"" not in output
def test_load_set_file(git_dir): def test_load_set_file(git_dir):
git_dir.join("dummy").write("dummy\n\n # Comment") git_dir.join("dummy").write("dummy\n\n # Comment")
s = dzonegit.load_set_file("dummy") s = dzonegit.load_set_file("dummy")
assert s == {"dummy"} assert s == {"dummy"}
def test_get_zone_wildcards():
assert list(dzonegit.get_zone_wildcards("a.long.zone.name")) == [
"a.long.zone.name", "*.long.zone.name",
"*.zone.name", "*.name", "*",
]