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

38 Commits
v0.2 ... v0.14

Author SHA1 Message Date
Ondřej Caletka
8f952086aa Version 0.14 2020-05-26 11:12:38 +02:00
Ondřej Caletka
83a4049821 Fix tests when user.name and user.email are not defined
Fixes #15
2020-05-26 11:11:23 +02:00
Ondřej Caletka
15cdae67ee Merge pull request #14 from oskar456/zonerelfile 2020-05-17 11:27:54 +02:00
Ondřej Caletka
12fb932711 Version 0.13
Signed-off-by: Ondřej Caletka <ondrej@caletka.cz>
2020-05-17 11:24:29 +02:00
Ondřej Caletka
cb543514ac Document $zonerelfile template macro
Signed-off-by: Ondřej Caletka <ondrej@caletka.cz>
2020-05-17 11:23:51 +02:00
Przemyslaw Sztoch
f9c6a52357 New macro $zonerelfile in template file. 2020-05-17 11:18:06 +02:00
Ondřej Caletka
24d992d999 Version 0.12 2020-04-13 22:28:24 +02:00
Rob Seastrom
7cb7c42d76 change named-compilezone to use /usr/bin/env rather than absolute path 2020-04-13 22:20:54 +02:00
Ondřej Caletka
3dd346294a Merge pull request #9 from oskar456/travis
Use travis-ci.org as .com was not enabled yet
2019-08-19 15:34:31 +02:00
Ondřej Caletka
03fde74ede Use travis-ci.org as .com was not enabled yet 2019-08-19 15:30:34 +02:00
Michal Halenka
3769dd22fb Include Travis CI status in README 2019-08-19 15:00:49 +02:00
Michal Halenka
8d15bb531c Update .travis.yml
Add new python 3.7 release
Fix indentation
2019-08-19 15:00:26 +02:00
Ondřej Caletka
e2e4a3daf7 Version 0.11 2018-09-17 13:50:47 +02:00
Ondřej Caletka
4efef8be9e Do not template line breaks when header and footer are missing 2018-09-17 13:48:52 +02:00
Ondřej Caletka
ef059861b7 Version 0.10 - switch to Beta status 2018-08-28 12:45:08 +02:00
Ondřej Caletka
94461383e8 Add pre-commit check for missing trailing dot in PTR records. 2018-08-28 12:43:51 +02:00
Ondřej Caletka
3e09833ec1 Better handling of empty commit objects 2018-08-28 10:58:47 +02:00
Ondřej Caletka
9ad1e74a88 Fix no reconfig command issued on zone file rename. 2018-08-27 23:13:29 +02:00
Ondřej Caletka
a7d693253d version 0.9 (skipping version 0.8 as it was mistakenly published before) 2018-08-23 10:46:43 +02:00
Ondřej Caletka
3777453d2f Better handling of replace serial failure. 2018-08-23 10:38:56 +02:00
Ondřej Caletka
023906177a version 0.7 2018-08-20 16:37:49 +02:00
Ondřej Caletka
e79bb901f3 $UNIXTIME doc update 2018-08-20 16:37:08 +02:00
Ondřej Caletka
f07c84aa32 Fix no reload on very first push to the repository 2018-08-20 16:33:00 +02:00
Ondřej Caletka
62e35c59d0 version 0.6 2018-08-20 15:30:45 +02:00
Ondřej Caletka
806976ca6e Add smudge filter to replace $UNIXTIME directive on checkout 2018-08-20 15:30:45 +02:00
Ondřej Caletka
17f771bca6 Support custom $UNIXTIME directive in place of SOA serial 2018-08-20 15:30:45 +02:00
Ondřej Caletka
03cf26bbbe Add allowfancynames option 2018-08-20 15:30:40 +02:00
Ondřej Caletka
9923df14b7 Fix crash of post-receive when checkout directory does not exist 2018-08-16 10:35:07 +02:00
Ondřej Caletka
8d99e86222 Travis: allow failures of the nightly build 2018-08-15 14:09:57 +02:00
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
5 changed files with 296 additions and 81 deletions

View File

@@ -2,12 +2,16 @@ before_install:
- sudo apt-get install -y bind9utils
language: python
python:
- "3.5"
- "3.6"
- "nightly"
- "3.5"
- "3.6"
- "3.7"
- "nightly"
matrix:
allow_failures:
- python: "nightly"
install:
- pip install -e .
- pip install pytest
- pip install -e .
- pip install pytest
script:
- pytest
- pytest
sudo: false

View File

@@ -1,3 +1,6 @@
.. image:: https://travis-ci.org/oskar456/dzonegit.svg?branch=master
:target: https://travis-ci.org/oskar456/dzonegit
Git hooks to manage a repository of DNS zones
=============================================
@@ -15,9 +18,10 @@ Main features
- check if zone file compiles properly using `named-compilezone(8)`_
- autodetect zone name from file name or ``$ORIGIN`` directive
- enforce updating serial number when zone content is changed
- optional ``smudge`` filter to replace ``$UNIXTIME`` directive with current UNIX time
- 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
- only Python standard library is used
- only Python 3.5+ standard library is used
Requirements
@@ -28,34 +32,74 @@ Requirements
- git
Instalation and usage
---------------------
Simple instalation (especially for workstations)
------------------------------------------------
- install required dependencies
- install ``dzonegit`` package using your favourite tool (``virtualenvwrapper``,
``venv``, ``pipenv``, etc.)
Since there is no other Python dependency than the standard library, you can
simply download the `dzonegit.py` file, make it executable and rename/symlink
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:
``$ 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 either ``pre-receive`` or ``update`` hook (both do the same) as
well as ``post-receive`` hook. See `Gitolite documentation on how to add custom hooks`_
- on the server set up the configuration options for each repository
- on the server, install some git repository management software,
preferably Gitolite_
- on the server, install either ``pre-receive`` or ``update`` hook
(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
Support for $UNIXTIME directive
-------------------------------
If you want to use ``$UNIXTIME`` in your zone files instead of serial number,
you have to install a `smudge` filter on the server, that will replace the
directive with current unix time on every checkout. First, set up the filter
in the Git configuration:
.. code-block:: shell
$ git config --global filter.dzonegit.smudge $(which dzonegit-smudge-serial)
Then, apply the filter on all zone files using either ``.git/info/attributes``
or directly ``.gitattributes`` file inside the repository:
.. code-block::
*.zone filter=dzonegit
Configuration options
---------------------
All configuration options are stored in `git-config(1)`_ in section named ``dzonegit``.
All boolean options default to *False*.
All configuration options are stored in `git-config(1)`_ in the section
named ``dzonegit``. All boolean options default to *False*.
*dzonegit.ignorewhitespaceerrors*
Ignore white space errors in ``pre-commit`` and ``pre-receive``/``update`` hooks.
*dzonegit.allowfancynames*
In ``pre-commit`` and ``pre-receive``/``update`` hooks, do not enforce zone
file name to be similar to the name of the zone.
*dzonegit.noserialupdate*
Do not try to automatically update zone serial number if necessary.
Valid only in the ``pre-commit`` hook.
*dzonegit.nomissingdotcheck*
Do not check for forgotten final dot on the right-hand side of PTR records.
Valid only in the ``pre-commit`` hook.
*dzonegit.checkoutpath*
Path to a writable directory, to which ``post-receive`` hook checks out
current *HEAD* after each update.
@@ -76,7 +120,7 @@ All boolean options default to *False*.
can be provided by appending single digit from 1 to 9 to this option.
*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
like ``rndc reload``. More commands can be provided by appending single digit
from 1 to 9 to this option.
@@ -84,12 +128,14 @@ All boolean options default to *False*.
*dzonegit.zoneblacklist*
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
``post-receive`` hook generates configuration.
``post-receive`` hook generates configuration. Wildcards can be used as
well, see `JSON template`_ below.
*dzonegit.zonewhitelist*
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,
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
-------------
@@ -98,7 +144,7 @@ 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 JSON
file. It is possible to define a zone-specific options, for instance for
changing DNSSEC parameters per zone. Those zone-specific options allow usage of
wildcards; if exact match of zone name is not found, the leftmost label is
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
@@ -129,14 +175,17 @@ Valid keys are:
In the template strings, these placeholders are supported:
``$datetime``
Current timestamp
Current date and time in human readable format
``$zonename``
Zone name, without trailing dot
Zone name, without the trailing dot
``$zonefile``
Full path to the zone file
``$zonerelfile``
Path to the zone file, relative to checkout path (useful for chroot environments)
``$zonevar``
Per-zone specific variable, see above

View File

@@ -41,17 +41,17 @@ class HookException(ValueError):
return "".join(r)
def get_head():
r = subprocess.run(
["git", "rev-parse", "--verify", "HEAD"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
if r.returncode == 0:
return r.stdout.decode("utf-8").strip()
else:
# Initial commit: diff against an empty tree object
return "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
def get_head(empty=False):
if not empty:
r = subprocess.run(
["git", "rev-parse", "--verify", "HEAD"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
if r.returncode == 0:
return r.stdout.decode("ascii").strip()
# Initial commit: diff against an empty tree object
return "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
def check_whitespace_errors(against, revision=None):
@@ -83,14 +83,44 @@ def get_file_contents(path, revision=None):
return r.stdout
def compile_zone(zonename, zonedata):
def unixtime_directive(zonedata, unixtime=None):
""" Filter binary zone data. Replace $UNIXTIME with current unix time. """
if unixtime is None:
unixtime = int(time.time())
return re.sub(
br'\$UNIXTIME\b',
str(unixtime).encode("ascii"),
zonedata,
flags=re.IGNORECASE,
)
def check_missing_trailing_dot(zonename, compiled_zonedata):
badlines = []
for line in compiled_zonedata.splitlines():
if re.search(
r"\sPTR\s+[^\s]*\.{}.$".format(zonename).encode("ascii"),
line,
re.I,
):
badlines.append(line.decode("utf-8"))
if badlines:
raise HookException(
"Possibly missing trailing dot after PTR records:\n{}".format(
"\n".join(badlines),
),
fname=zonename,
)
def compile_zone(zonename, zonedata, unixtime=None, missing_dot=False):
""" Compile the zone. Return tuple with results."""
CompileResults = namedtuple(
"CompileResults", "success, serial, zonehash, stderr",
)
r = subprocess.run(
["/usr/sbin/named-compilezone", "-o", "-", zonename, "/dev/stdin"],
input=zonedata,
["/usr/bin/env", "named-compilezone", "-o", "-", zonename, "/dev/stdin"],
input=unixtime_directive(zonedata, unixtime),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
@@ -98,6 +128,8 @@ def compile_zone(zonename, zonedata):
m = re.search(r"^zone.*loaded serial ([0-9]*)$", stderr, re.MULTILINE)
if r.returncode == 0 and m:
serial = m.group(1)
if missing_dot:
check_missing_trailing_dot(zonename, r.stdout)
zonehash = sha256(r.stdout).hexdigest()
return CompileResults(True, serial, zonehash, stderr)
else:
@@ -134,7 +166,7 @@ def get_altered_files(against, diff_filter=None, revision=None):
If revision is None, list changes between staging area and
revision. Otherwise differences between two revisions are computed.
"""
cmd = ["git", "diff", "--name-only", "-z"]
cmd = ["git", "diff", "--name-only", "-z", "--no-renames"]
if diff_filter:
cmd.append("--diff-filter={}".format(diff_filter))
if revision:
@@ -173,15 +205,15 @@ def get_zone_origin(zonedata):
def get_zone_name(path, zonedata):
"""
Try to guess zone name from either filename or the first $ORIGIN.
Throw a HookException if filename and zone ORIGIN differ more than
in slashes.
Unless disabled, throw a HookException if filename and zone ORIGIN differ
more than in slashes.
"""
stemname = Path(path).stem.lower()
originname = get_zone_origin(zonedata)
if originname:
tt = str.maketrans("", "", "/_,:-+*%^&#$")
sn, on = [s.translate(tt) for s in [stemname, originname]]
if sn != on:
if sn != on and not get_config("dzonegit.allowfancynames", bool):
raise HookException(
"Zone origin {o} differs from zone file.".format(o=originname),
fname=path,
@@ -191,15 +223,21 @@ def get_zone_name(path, zonedata):
return stemname
def check_updated_zones(against, revision=None, autoupdate_serial=False):
def check_updated_zones(
against,
revision=None,
autoupdate_serial=False,
missing_dot=False,
):
""" Check whether all updated zone files compile. """
unixtime = int(time.time())
for f in get_altered_files(against, "AMCR", revision):
if not f.suffix == ".zone":
continue
print("Checking file {f}".format(f=f))
zonedata = get_file_contents(f, revision)
zname = get_zone_name(f, zonedata)
rnew = compile_zone(zname, zonedata)
rnew = compile_zone(zname, zonedata, unixtime, missing_dot)
if not rnew.success:
raise HookException(
"New zone version does not compile",
@@ -208,7 +246,7 @@ def check_updated_zones(against, revision=None, autoupdate_serial=False):
try:
zonedata = get_file_contents(f, against)
zname = get_zone_name(f, zonedata)
rold = compile_zone(zname, zonedata)
rold = compile_zone(zname, zonedata, unixtime-1)
if (rold.success and rold.zonehash != rnew.zonehash and not
is_serial_increased(rold.serial, rnew.serial)):
@@ -219,9 +257,11 @@ def check_updated_zones(against, revision=None, autoupdate_serial=False):
if autoupdate_serial:
newserial = get_increased_serial(rnew.serial)
replace_serial(f, rnew.serial, newserial)
errmsg += " Serial has been automatically increased."
errmsg += " Check and recommit."
if replace_serial(f, rnew.serial, newserial):
errmsg += " Serial has been automatically increased."
errmsg += " Check and recommit."
else:
errmsg += " Autoupdate of serial number failed."
raise HookException(
errmsg,
fname=f,
@@ -264,8 +304,9 @@ def replace_serial(path, oldserial, newserial):
flags=re.DOTALL | re.IGNORECASE | re.MULTILINE,
)
if count != 1:
raise HookException("Cannot update zone serial number")
return False
path.write_text(updated)
return True
def get_zone_wildcards(name):
@@ -317,16 +358,20 @@ def template_config(checkoutpath, template, blacklist=set(), whitelist=set()):
out = list()
zones = dict()
mapping = {"datetime": datetime.datetime.now().strftime("%c")}
out.append(headertpl.substitute(mapping))
if headertpl.template:
out.append(headertpl.substitute(mapping))
for f in sorted(Path(checkoutpath).glob("**/*.zone")):
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(
"WARNING: Ignoring zone {} - not whitelisted for "
"this repository.".format(zonename),
)
continue
if zonename in blacklist:
if any(n in blacklist for n in get_zone_wildcards(zonename)):
print(
"WARNING: Ignoring zone {} - blacklisted for "
"this repository.".format(zonename),
@@ -350,9 +395,10 @@ def template_config(checkoutpath, template, blacklist=set(), whitelist=set()):
zonevar = defaultvar
out.append(itemtpl.substitute(
mapping, zonename=zonename,
zonefile=str(f), zonevar=zonevar,
zonefile=str(f), zonerelfile=str(f.relative_to(checkoutpath)), zonevar=zonevar,
))
out.append(footertpl.substitute(mapping))
if footertpl.template:
out.append(footertpl.substitute(mapping))
return "\n".join(out)
@@ -366,13 +412,19 @@ def load_set_file(path):
}
def do_commit_checks(against, revision=None, autoupdate_serial=False):
def do_commit_checks(
against,
revision=None,
autoupdate_serial=False,
missing_dot=False,
):
try:
if not get_config("dzonegit.ignorewhitespaceerrors", bool):
check_whitespace_errors(against, revision=revision)
check_updated_zones(
against, revision=revision,
autoupdate_serial=autoupdate_serial,
missing_dot=missing_dot,
)
except HookException as e:
print(e)
@@ -382,7 +434,12 @@ def do_commit_checks(against, revision=None, autoupdate_serial=False):
def pre_commit():
against = get_head()
autoupdate_serial = not get_config("dzonegit.noserialupdate", bool)
do_commit_checks(against, autoupdate_serial=autoupdate_serial)
missing_dot = not get_config("dzonegit.nomissingdotcheck", bool)
do_commit_checks(
against,
autoupdate_serial=autoupdate_serial,
missing_dot=missing_dot,
)
def update(argv=sys.argv):
@@ -395,7 +452,7 @@ def update(argv=sys.argv):
refname, against, revision = argv[1:4]
if against == "0000000000000000000000000000000000000000":
against = get_head() # Empty commit
against = get_head(True) # Empty commit
if refname != "refs/heads/master":
raise SystemExit("Nothing else than master branch is accepted here")
@@ -413,7 +470,7 @@ def pre_receive(stdin=sys.stdin):
"is accepted here",
)
if against == "0000000000000000000000000000000000000000":
against = get_head() # Empty commit
against = get_head(True) # Empty commit
do_commit_checks(against, revision)
@@ -431,6 +488,7 @@ def post_receive(stdin=sys.stdin):
raise SystemExit("Checkout path not defined. Nothing to do.")
print("Checking out repository into {}".format(checkoutpath))
Path(checkoutpath).mkdir(parents=True, exist_ok=True)
subprocess.run(
["git", "checkout", "-f", "master"],
check=True,
@@ -462,7 +520,7 @@ def post_receive(stdin=sys.stdin):
if refname != "refs/heads/master":
continue
if against == "0000000000000000000000000000000000000000":
against = get_head() # Empty commit
against = get_head(True) # Empty commit
should_reconfig = [
f for f in get_altered_files(against, "ACDRU", revision)
if f.suffix == ".zone"
@@ -492,17 +550,36 @@ def post_receive(stdin=sys.stdin):
subprocess.run(cmd)
def smudge_serial(
bstdin=sys.stdin.buffer,
bstdout=sys.stdout.buffer,
unixtime=None,
):
"""Replace all $UNIXTIME directives with current unix time."""
bstdout.write(unixtime_directive(bstdin.read(), unixtime))
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
if "smudge" in name:
return smudge_serial
def main():
name = Path(sys.argv[0]).name
print(name)
if name == "pre-commit":
pre_commit()
elif name == "update":
update()
elif name == "pre-receive":
pre_receive()
elif name == "post-receive":
post_receive()
action = get_action()
if action is None and len(sys.argv) > 1:
sys.argv.pop(0)
action = get_action()
if action:
action()
else:
sys.exit("No valid command found")

View File

@@ -5,7 +5,7 @@ readme = Path(__file__).with_name("README.rst").read_text()
setup(
name="dzonegit",
version="0.2",
version="0.14",
description="Git hooks to manage a repository of DNS zones",
long_description=readme,
long_description_content_type="text/x-rst",
@@ -19,15 +19,15 @@ setup(
tests_require=["pytest"],
entry_points={
"console_scripts": [
"dzonegit = dzonegit:main",
"dzonegit-pre-commit = dzonegit:pre_commit",
"dzonegit-pre-receive = dzonegit:pre_receive",
"dzonegit-post-receive = dzonegit:post_receive",
"dzonegit-update = dzonegit:update",
"dzonegit-smudge-serial = dzonegit:smudge_serial",
],
},
classifiers=[
"Development Status :: 3 - Alpha",
"Development Status :: 4 - Beta",
"Environment :: Console",
"Intended Audience :: System Administrators",
"License :: OSI Approved :: MIT License",

View File

@@ -4,7 +4,7 @@ import subprocess
import time
import datetime
import os
from io import StringIO
from io import StringIO, BytesIO
from pathlib import Path
import dzonegit
@@ -15,6 +15,8 @@ def git_dir(tmpdir_factory):
d = tmpdir_factory.getbasetemp()
d.chdir()
subprocess.call(["git", "init"])
subprocess.call(["git", "config", "user.name", "dzonegit pytest"])
subprocess.call(["git", "config", "user.email", "nonexistent@example.com"])
return d
@@ -62,16 +64,47 @@ $ORIGIN example.com.
60 IN NS ns
ns.example.com. 60 IN A 192.0.2.1
"""
r = dzonegit.compile_zone("example.org", testzone)
r = dzonegit.compile_zone("example.org", testzone, missing_dot=True)
assert not r.success
assert r.zonehash is None
assert r.stderr
r = dzonegit.compile_zone("example.com", testzone)
r = dzonegit.compile_zone("example.com", testzone, missing_dot=True)
assert r.success
assert r.serial == "1234567890"
assert r.zonehash
r2 = dzonegit.compile_zone("example.com", testzone + b"\n\n; some comment")
assert r.zonehash == r2.zonehash
testzone += b"1 60 IN PTR www\n"
dzonegit.compile_zone("example.com", testzone, missing_dot=False)
with pytest.raises(ValueError):
dzonegit.compile_zone("example.com", testzone, missing_dot=True)
def test_compile_unsmudged_zone():
testzone = b"""
$ORIGIN example.com.
@ 60 IN SOA ns hostmaster (
$UNIXTIME ; serial
3600 ; refresh (1 hour)
900 ; retry (15 minutes)
1814400 ; expire (3 weeks)
60 ; minimum (1 minute)
)
60 IN NS ns
ns.example.com. 60 IN A 192.0.2.1
"""
replaced = dzonegit.unixtime_directive(testzone)
assert b"$UNIXTIME" not in replaced
r = dzonegit.compile_zone("example.com", testzone, 123456)
assert r.success
assert r.serial == str(123456)
def test_smudge_serial():
bstdin = BytesIO(b"something $UNIXTIME something")
bstdout = BytesIO()
dzonegit.smudge_serial(bstdin, bstdout, 123456)
assert b"something 123456 something" == bstdout.getvalue()
def test_is_serial_increased():
@@ -131,6 +164,8 @@ ns.example.com. 60 IN A 192.0.2.1
)
with pytest.raises(ValueError):
dzonegit.get_zone_name("zones/example.org.zone", testzone)
subprocess.call(["git", "config", "dzonegit.allowfancynames", "TRUE"])
dzonegit.get_zone_name("zones/example.org.zone", testzone)
testzone = b"""
$ORIGIN 240/28.2.0.192.in-addr.arpa.
@ 60 IN SOA ns hostmaster 1 60 60 60 60
@@ -148,12 +183,12 @@ def test_replace_serial(git_dir):
@ 60 IN SOA ns hm 1 61 60 60 60
60 NS ns.example.org.
""")
dzonegit.replace_serial(Path("dummy.zone"), "1", "60")
assert dzonegit.replace_serial(Path("dummy.zone"), "1", "60")
assert git_dir.join("dummy.zone").read() == """
@ 60 IN SOA ns hm 60 61 60 60 60
60 NS ns.example.org.
"""
dzonegit.replace_serial(Path("dummy.zone"), "60", "61")
assert dzonegit.replace_serial(Path("dummy.zone"), "60", "61")
assert git_dir.join("dummy.zone").read() == """
@ 60 IN SOA ns hm 61 61 60 60 60
60 NS ns.example.org.
@@ -168,7 +203,7 @@ def test_replace_serial(git_dir):
)
60 NS ns.example.org.
""")
dzonegit.replace_serial(Path("dummy.zone"), "60", "6000000")
assert dzonegit.replace_serial(Path("dummy.zone"), "60", "6000000")
assert git_dir.join("dummy.zone").read() == """
@ 60 IN SOA ns hm (
6000000 ; serial
@@ -179,6 +214,7 @@ def test_replace_serial(git_dir):
)
60 NS ns.example.org.
"""
assert not dzonegit.replace_serial(Path("dummy.zone"), "0", "60")
def test_check_updated_zones(git_dir):
@@ -226,6 +262,29 @@ $ORIGIN dummy.
dzonegit.check_updated_zones("HEAD", autoupdate_serial=True)
subprocess.call(["git", "add", "dummy.zone"])
dzonegit.check_updated_zones(dzonegit.get_head())
git_dir.join("dummy.zone").write("""
$ORIGIN dummy.
@ 60 IN SOA ns hm $UNIXTIME 61 60 60 60
60 NS ns.example.org.
""")
subprocess.call(["git", "add", "dummy.zone"])
dzonegit.check_updated_zones(dzonegit.get_head())
subprocess.call(["git", "commit", "-m", "dummy.zone with $UNIXTIME"])
git_dir.join("dummy.zone").write("""
$ORIGIN dummy.
@ 60 IN SOA ns hm 1 60 60 60 60
60 NS ns.example.org.
""")
subprocess.call(["git", "add", "dummy.zone"])
with pytest.raises(ValueError):
dzonegit.check_updated_zones(dzonegit.get_head())
git_dir.join("dummy.zone").write("""
$ORIGIN dummy.
@ 60 IN SOA ns hm $UNIXTIME 60 60 60 60
60 NS ns.example.org.
""")
subprocess.call(["git", "add", "dummy.zone"])
dzonegit.check_updated_zones(dzonegit.get_head())
subprocess.call(["git", "commit", "-m", "final dummy.zone"])
dzonegit.check_updated_zones("HEAD~", "HEAD")
@@ -276,11 +335,11 @@ def test_post_receive(git_dir):
git_dir.chdir()
head = dzonegit.get_head()
revisions = "{} {} refs/heads/master\n".format(
"4b825dc642cb6eb9a060e54bf8d69288fbee4904",
"0000000000000000000000000000000000000000",
head,
)
stdin = StringIO(revisions)
codir = git_dir.mkdir("co")
codir = git_dir.join("co")
subprocess.call(["git", "config", "dzonegit.checkoutpath", str(codir)])
subprocess.call([
"git", "config", "dzonegit.reconfigcmd",
@@ -289,6 +348,17 @@ def test_post_receive(git_dir):
dzonegit.post_receive(stdin)
assert codir.join("dummy.zone").check()
assert codir.join("test").read() == "TEST\n"
# Test reconfig after renaming the file
codir.join("test").write("")
subprocess.call(["git", "mv", "dummy.zone", "dummy.zone.old"])
subprocess.call(["git", "commit", "-m", "rename dummy zone"])
revisions = "{} {} refs/heads/master\n".format(
head,
dzonegit.get_head(),
)
stdin = StringIO(revisions)
dzonegit.post_receive(stdin)
assert codir.join("test").read() == "TEST\n"
def test_template_config(git_dir):
@@ -313,6 +383,14 @@ def test_template_config(git_dir):
whitelist=set("a"),
)
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
output = dzonegit.template_config(str(git_dir), "{}")
assert len(output) == 0
def test_load_set_file(git_dir):
@@ -326,3 +404,10 @@ def test_get_zone_wildcards():
"a.long.zone.name", "*.long.zone.name",
"*.zone.name", "*.name", "*",
]
def test_missing_trailing_dot():
zonename = "example.com"
zonedata = b"something.example.com. IN PTR s.example.com."
with pytest.raises(ValueError):
dzonegit.check_missing_trailing_dot(zonename, zonedata)