-
-
Save dmtucker/1203ab0b676283385f657bb83531a8c7 to your computer and use it in GitHub Desktop.
Update script for dynv6.com to set your IPv6 address
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
update_via_http () { | |
if [ -z "${token+undefined}" ] || [ "$#" != 2 ] | |
then | |
echo 'usage: token=<your-HTTP-token> update_via_http zone ipv6' 1>&2 | |
exit 1 | |
fi | |
zone="$1" | |
ipv6="$2" | |
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=141323 | |
tmp="$(mktemp)" | |
wget -6O- --no-verbose "https://dynv6.com/api/update?zone=$zone&token=$token&ipv6=$ipv6" 2>"$tmp" | |
code="$?" | |
if [ "$code" = 0 ] | |
then echo | |
else cat "$tmp" | |
fi | |
rm "$tmp" | |
return $code | |
} | |
update_via_ssh () { | |
[ "$#" = 2 ] || { | |
echo 'usage: update_via_ssh zone ipv6addr' 1>&2 | |
exit 1 | |
} | |
zone="$1" | |
ipv6addr="$2" | |
ssh -6 [email protected] hosts "$zone" set ipv6addr "$ipv6addr" | |
} | |
if [ -n "${token+undefined}" ] | |
then update_zone=update_via_http | |
else update_zone=update_via_ssh | |
fi | |
errors=0 | |
for zone in "$@" | |
do | |
host_zone="$(host -t AAAA "$zone")" | |
ipv6="$(echo "$host_zone" | awk '/IPv6/ { print $NF }')" | |
if [ "$ipv6" = '' ] | |
then echo "$host_zone" | |
elif ip -6 address show scope global | grep -q "$ipv6" | |
then continue | |
else echo "$(hostname) does not have IPv6 address $ipv6" | |
fi | |
new_ipv6="$(ip -6 address show scope global primary | awk '/inet6/ { print $2 }' | sort | head -n1 | cut -d/ -f1)" | |
[ "$new_ipv6" = '' ] && new_ipv6=auto | |
if "$update_zone" "$zone" "$new_ipv6" | |
then | |
new_host_zone="$host_zone" | |
while [ "$new_host_zone" = "$host_zone" ] | |
do | |
sleep 1 | |
new_host_zone="$(host -t AAAA "$zone")" | |
echo -n . | |
done | |
echo | |
echo "$new_host_zone" | |
else | |
errors=$((errors + 1)) | |
echo | |
ip -6 address show scope global | |
fi | |
done | |
exit $errors |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# /// script | |
# requires-python = ">=3.10" | |
# /// | |
from __future__ import annotations | |
import argparse | |
import ipaddress | |
import logging | |
import os | |
import socket | |
import subprocess # nosec import_subprocess | |
import sys | |
import typing | |
import urllib.parse | |
import urllib.request | |
LOG_LEVEL = { # compat: python < 3.11 | |
"CRITICAL": logging.CRITICAL, | |
"FATAL": logging.FATAL, | |
"ERROR": logging.ERROR, | |
"WARN": logging.WARNING, | |
"WARNING": logging.WARNING, | |
"INFO": logging.INFO, | |
"DEBUG": logging.DEBUG, | |
"NOTSET": logging.NOTSET, | |
} | |
UPDATE_URL = urllib.parse.urlsplit("https://ipv6.dynv6.com/api/update") | |
def cli() -> argparse.ArgumentParser: | |
"""Get a parser for the command-line interface.""" | |
parser = argparse.ArgumentParser( | |
formatter_class=argparse.ArgumentDefaultsHelpFormatter | |
) | |
parser.add_argument( | |
"--log-level", | |
help="how much output to produce", | |
type=str.upper, | |
choices=LOG_LEVEL.keys(), | |
default="INFO", | |
) | |
parser.add_argument("zone", help="the zone to update") | |
return parser | |
def unordered_list( | |
iterable: typing.Iterable[typing.Any], /, *, prefix: str = "- " | |
) -> str: | |
"""Format an iterable to be pretty-printed.""" | |
return "\n".join(prefix + str(element) for element in iterable) | |
class Dynv6UpdateZoneError(Exception): | |
"""The dynv6.com zone could not be updated.""" | |
class HostnameResolutionError(Dynv6UpdateZoneError): | |
"""There was a problem resolving a hostname.""" | |
def host_addresses(host: str) -> set[ipaddress.IPv6Address]: | |
"""Resolve a hostname.""" | |
logging.debug(f"Resolving {host}...") | |
try: | |
addresses = { | |
ipaddress.IPv6Address(sockaddr[0]) | |
for family, *_, sockaddr in socket.getaddrinfo(host, None) | |
if family == socket.AF_INET6 | |
} | |
except Exception as exc: | |
logging.error(f"{host}: {exc}") | |
raise HostnameResolutionError from exc | |
logging.debug( | |
f"{host} has the following addresses:\n" + unordered_list(addresses) | |
if addresses | |
else f"{host} has no addresses." | |
) | |
return addresses | |
class AddressRetrievalError(Dynv6UpdateZoneError): | |
"""There was a problem getting local addresses.""" | |
def local_addresses(*, network: ipaddress.IPv6Network) -> set[ipaddress.IPv6Address]: | |
"""Get global IP addresses attached to local network devices.""" | |
network = ipaddress.IPv6Network(network) | |
logging.debug(f"Getting local addresses in {network}...") | |
try: | |
# An attacker that can manipulate PATH or the filesystem | |
# will not gain any extra capabilities from this script, | |
# and network is sanitized by ipaddress.IPv6Network above: | |
output = subprocess.check_output( | |
["ip", "address", "show", "scope", "global", "to", str(network)] | |
) # nosec start_process_with_partial_path, subprocess_without_shell_equals_true | |
except Exception as exc: | |
logging.error(exc) | |
raise AddressRetrievalError from exc | |
addresses = { | |
ipaddress.IPv6Address(line.split()[1].partition(b"/")[0].decode()) | |
for line in output.splitlines() | |
if line.strip().startswith(b"inet6") | |
} | |
logging.debug( | |
f"The following local addresses are in {network}:\n" + unordered_list(addresses) | |
if addresses | |
else f"There are no local addresses in {network}." | |
) | |
return addresses | |
class InvalidAddressError(Dynv6UpdateZoneError): | |
"""The zone resolved to an invalid address.""" | |
def zone_needs_update(zone: str) -> bool: | |
"""Check for local addresses in the zone prefix.""" | |
logging.debug(f"Checking {zone}...") | |
try: | |
zone_ipv6_addresses = { | |
address for address in host_addresses(zone) if address.version == 6 | |
} | |
try: | |
needs_update = not zone_ipv6_addresses or any( | |
not local_addresses(network=ipaddress.IPv6Network(f"{address}/64")) | |
for address in zone_ipv6_addresses | |
) | |
except ValueError as exc: | |
logging.error(f"{zone}: {exc}") | |
raise InvalidAddressError from exc | |
except (HostnameResolutionError, InvalidAddressError, AddressRetrievalError): | |
needs_update = True | |
logging.debug( | |
f"{zone} needs to be updated." | |
if needs_update | |
else f"{zone} does not need to be updated." | |
) | |
return needs_update | |
class ZoneUpdateError(Dynv6UpdateZoneError): | |
"""There was a problem updating the zone.""" | |
class MissingHTTPTokenError(ZoneUpdateError): | |
"""There is no HTTP token available for the dynv6.com API.""" | |
def update_zone(zone: str) -> None: | |
"""Make a request to the dynv6.com HTTP Update API.""" | |
logging.debug(f"Updating {zone}...") | |
try: | |
token = os.environ["token"] | |
except KeyError as exc: | |
logging.error(f"No HTTP Token is set for {zone}.") | |
raise MissingHTTPTokenError from exc | |
url = UPDATE_URL._replace( | |
query=urllib.parse.urlencode( | |
{ | |
"ipv6prefix": "auto", | |
"token": token, | |
"zone": zone, | |
} | |
) | |
) | |
try: | |
# The url is manually constructed above, so the scheme is expected: | |
with urllib.request.urlopen(url.geturl()) as response: # nosec urllib_urlopen | |
response_text = response.read().decode( | |
response.headers.get_content_charset() or "utf-8" | |
) | |
except Exception as exc: | |
msg = str(exc) | |
if isinstance(exc, urllib.error.HTTPError): | |
response_text = exc.fp.read().decode( | |
exc.headers.get_content_charset() or "utf-8" | |
) | |
if response_text: | |
msg += f" ({response_text})" | |
elif isinstance(exc, urllib.error.URLError): | |
msg = exc.reason | |
logging.error(f"{url.netloc}: {msg}") | |
raise ZoneUpdateError from exc | |
logging_func = { | |
"addresses unchanged": logging.debug, | |
"addresses updated": logging.info, | |
}.get(response_text, logging.warning) | |
logging_func( | |
f"{zone}: {response_text} (HTTP Status {response.code}: {response.reason})" | |
) | |
def main(argv: list[str] | None = None) -> int: | |
args = cli().parse_args(argv or sys.argv[1:]) | |
logging.basicConfig( | |
format="[%(levelname)s] %(message)s", | |
level=LOG_LEVEL[args.log_level.upper()], | |
) | |
try: | |
if zone_needs_update(args.zone): | |
update_zone(args.zone) | |
except Dynv6UpdateZoneError as exc: | |
logging.error(f"{args.zone} could not be updated.\n{exc}") | |
return 1 | |
logging.debug(f"{args.zone} is up to date.") | |
return 0 | |
if __name__ == "__main__": | |
sys.exit(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[requires] | |
python_version = "3.11" | |
[[source]] | |
url = "https://pypi.org/simple" | |
verify_ssl = true | |
name = "pypi" | |
[packages] | |
[dev-packages] | |
bandit = "*" | |
black = "*" | |
flake8 = "*" | |
pytest = "*" | |
pytest-cov = "*" | |
pytest-randomly = "*" | |
[scripts] | |
tests = 'pytest --cov dynv6_update_zone --cov-branch --cov-fail-under 100 --cov-report term-missing' | |
lint = 'flake8 --max-line-length 88 dynv6_update_zone.py test_dynv6_update_zone.py' | |
sec = 'bandit dynv6_update_zone.py' | |
style = 'black --check dynv6_update_zone.py test_dynv6_update_zone.py' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"_meta": { | |
"hash": { | |
"sha256": "9861e57bf9b98270d2926fbfbd32d77c518b0848002fbe1d0e3155ad12289b28" | |
}, | |
"pipfile-spec": 6, | |
"requires": { | |
"python_version": "3.11" | |
}, | |
"sources": [ | |
{ | |
"name": "pypi", | |
"url": "https://pypi.org/simple", | |
"verify_ssl": true | |
} | |
] | |
}, | |
"default": {}, | |
"develop": { | |
"bandit": { | |
"hashes": [ | |
"sha256:b1a61d829c0968aed625381e426aa378904b996529d048f8d908fa28f6b13e38", | |
"sha256:b5bfe55a095abd9fe20099178a7c6c060f844bfd4fe4c76d28e35e4c52b9d31e" | |
], | |
"index": "pypi", | |
"markers": "python_version >= '3.9'", | |
"version": "==1.8.0" | |
}, | |
"black": { | |
"hashes": [ | |
"sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f", | |
"sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd", | |
"sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea", | |
"sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981", | |
"sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b", | |
"sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7", | |
"sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8", | |
"sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175", | |
"sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", | |
"sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392", | |
"sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad", | |
"sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f", | |
"sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f", | |
"sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b", | |
"sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", | |
"sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3", | |
"sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800", | |
"sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65", | |
"sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2", | |
"sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812", | |
"sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50", | |
"sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e" | |
], | |
"index": "pypi", | |
"markers": "python_version >= '3.9'", | |
"version": "==24.10.0" | |
}, | |
"click": { | |
"hashes": [ | |
"sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", | |
"sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de" | |
], | |
"markers": "python_version >= '3.7'", | |
"version": "==8.1.7" | |
}, | |
"coverage": { | |
"extras": [ | |
"toml" | |
], | |
"hashes": [ | |
"sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5", | |
"sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf", | |
"sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb", | |
"sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638", | |
"sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4", | |
"sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc", | |
"sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed", | |
"sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a", | |
"sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d", | |
"sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649", | |
"sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c", | |
"sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b", | |
"sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4", | |
"sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443", | |
"sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83", | |
"sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee", | |
"sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e", | |
"sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e", | |
"sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3", | |
"sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0", | |
"sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb", | |
"sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076", | |
"sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb", | |
"sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787", | |
"sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1", | |
"sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e", | |
"sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce", | |
"sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801", | |
"sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764", | |
"sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365", | |
"sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf", | |
"sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6", | |
"sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71", | |
"sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002", | |
"sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4", | |
"sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c", | |
"sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8", | |
"sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4", | |
"sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146", | |
"sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc", | |
"sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea", | |
"sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4", | |
"sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad", | |
"sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28", | |
"sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451", | |
"sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50", | |
"sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779", | |
"sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63", | |
"sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e", | |
"sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc", | |
"sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022", | |
"sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d", | |
"sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94", | |
"sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b", | |
"sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d", | |
"sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331", | |
"sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a", | |
"sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0", | |
"sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee", | |
"sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92", | |
"sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a", | |
"sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9" | |
], | |
"markers": "python_version >= '3.9'", | |
"version": "==7.6.8" | |
}, | |
"flake8": { | |
"hashes": [ | |
"sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38", | |
"sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213" | |
], | |
"index": "pypi", | |
"markers": "python_full_version >= '3.8.1'", | |
"version": "==7.1.1" | |
}, | |
"iniconfig": { | |
"hashes": [ | |
"sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", | |
"sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" | |
], | |
"markers": "python_version >= '3.7'", | |
"version": "==2.0.0" | |
}, | |
"markdown-it-py": { | |
"hashes": [ | |
"sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", | |
"sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" | |
], | |
"markers": "python_version >= '3.8'", | |
"version": "==3.0.0" | |
}, | |
"mccabe": { | |
"hashes": [ | |
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", | |
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" | |
], | |
"markers": "python_version >= '3.6'", | |
"version": "==0.7.0" | |
}, | |
"mdurl": { | |
"hashes": [ | |
"sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", | |
"sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" | |
], | |
"markers": "python_version >= '3.7'", | |
"version": "==0.1.2" | |
}, | |
"mypy-extensions": { | |
"hashes": [ | |
"sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", | |
"sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" | |
], | |
"markers": "python_version >= '3.5'", | |
"version": "==1.0.0" | |
}, | |
"packaging": { | |
"hashes": [ | |
"sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", | |
"sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" | |
], | |
"markers": "python_version >= '3.8'", | |
"version": "==24.2" | |
}, | |
"pathspec": { | |
"hashes": [ | |
"sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", | |
"sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" | |
], | |
"markers": "python_version >= '3.8'", | |
"version": "==0.12.1" | |
}, | |
"pbr": { | |
"hashes": [ | |
"sha256:788183e382e3d1d7707db08978239965e8b9e4e5ed42669bf4758186734d5f24", | |
"sha256:a776ae228892d8013649c0aeccbb3d5f99ee15e005a4cbb7e61d55a067b28a2a" | |
], | |
"markers": "python_version >= '2.6'", | |
"version": "==6.1.0" | |
}, | |
"platformdirs": { | |
"hashes": [ | |
"sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", | |
"sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb" | |
], | |
"markers": "python_version >= '3.8'", | |
"version": "==4.3.6" | |
}, | |
"pluggy": { | |
"hashes": [ | |
"sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", | |
"sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" | |
], | |
"markers": "python_version >= '3.8'", | |
"version": "==1.5.0" | |
}, | |
"pycodestyle": { | |
"hashes": [ | |
"sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3", | |
"sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521" | |
], | |
"markers": "python_version >= '3.8'", | |
"version": "==2.12.1" | |
}, | |
"pyflakes": { | |
"hashes": [ | |
"sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", | |
"sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a" | |
], | |
"markers": "python_version >= '3.8'", | |
"version": "==3.2.0" | |
}, | |
"pygments": { | |
"hashes": [ | |
"sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", | |
"sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a" | |
], | |
"markers": "python_version >= '3.8'", | |
"version": "==2.18.0" | |
}, | |
"pytest": { | |
"hashes": [ | |
"sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", | |
"sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761" | |
], | |
"index": "pypi", | |
"markers": "python_version >= '3.8'", | |
"version": "==8.3.4" | |
}, | |
"pytest-cov": { | |
"hashes": [ | |
"sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", | |
"sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0" | |
], | |
"index": "pypi", | |
"markers": "python_version >= '3.9'", | |
"version": "==6.0.0" | |
}, | |
"pytest-randomly": { | |
"hashes": [ | |
"sha256:11bf4d23a26484de7860d82f726c0629837cf4064b79157bd18ec9d41d7feb26", | |
"sha256:8633d332635a1a0983d3bba19342196807f6afb17c3eef78e02c2f85dade45d6" | |
], | |
"index": "pypi", | |
"markers": "python_version >= '3.9'", | |
"version": "==3.16.0" | |
}, | |
"pyyaml": { | |
"hashes": [ | |
"sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", | |
"sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", | |
"sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", | |
"sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", | |
"sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", | |
"sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", | |
"sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", | |
"sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", | |
"sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", | |
"sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", | |
"sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", | |
"sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", | |
"sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", | |
"sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", | |
"sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", | |
"sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", | |
"sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", | |
"sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", | |
"sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", | |
"sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", | |
"sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", | |
"sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", | |
"sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", | |
"sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", | |
"sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", | |
"sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", | |
"sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", | |
"sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", | |
"sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", | |
"sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", | |
"sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", | |
"sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", | |
"sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", | |
"sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", | |
"sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", | |
"sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", | |
"sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", | |
"sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", | |
"sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", | |
"sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", | |
"sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", | |
"sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", | |
"sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", | |
"sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", | |
"sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", | |
"sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", | |
"sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", | |
"sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", | |
"sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", | |
"sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", | |
"sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", | |
"sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", | |
"sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" | |
], | |
"markers": "python_version >= '3.8'", | |
"version": "==6.0.2" | |
}, | |
"rich": { | |
"hashes": [ | |
"sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", | |
"sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90" | |
], | |
"markers": "python_full_version >= '3.8.0'", | |
"version": "==13.9.4" | |
}, | |
"stevedore": { | |
"hashes": [ | |
"sha256:79e92235ecb828fe952b6b8b0c6c87863248631922c8e8e0fa5b17b232c4514d", | |
"sha256:b0be3c4748b3ea7b854b265dcb4caa891015e442416422be16f8b31756107857" | |
], | |
"markers": "python_version >= '3.9'", | |
"version": "==5.4.0" | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ipaddress | |
import socket | |
import textwrap | |
import urllib | |
import pytest | |
import dynv6_update_zone | |
def test_host_addresses(monkeypatch): | |
ipv6 = "2001:db8::" | |
def _getaddrinfo(*args, **kwargs): | |
return [ | |
(socket.AF_INET6, socket.SOCK_STREAM, 0, "", (ipv6, 0, 0, 0)), | |
(socket.AF_INET, socket.SOCK_STREAM, 6, "", ("192.0.2.1", 0)), | |
] | |
monkeypatch.setattr("socket.getaddrinfo", _getaddrinfo) | |
assert dynv6_update_zone.host_addresses("a.b.c") == {ipaddress.IPv6Address(ipv6)} | |
def test_host_addresses_error(monkeypatch): | |
exc = Exception("uh oh!") | |
def _getaddrinfo(*args, **kwargs): | |
raise exc | |
monkeypatch.setattr("socket.getaddrinfo", _getaddrinfo) | |
with pytest.raises(dynv6_update_zone.HostnameResolutionError) as exc_info: | |
dynv6_update_zone.host_addresses("a.b.c") | |
assert exc_info.value.__cause__ is exc | |
def test_local_addresses(monkeypatch): | |
def _check_output(*args, **kwargs): | |
return textwrap.dedent( | |
""" | |
2: wlp0s20f3: <BROADCAST,MULTICAST,UP,LOWER_UP> | |
mtu 1500 qdisc noqueue state UP group default qlen 1000 | |
inet6 2001:db8::1/64 scope global temporary dynamic | |
valid_lft 86149sec preferred_lft 82736sec | |
inet6 2001:db8::2/64 scope global dynamic mngtmpaddr noprefixroute | |
valid_lft 86149sec preferred_lft 86149sec | |
""" | |
).encode() | |
monkeypatch.setattr("subprocess.check_output", _check_output) | |
assert dynv6_update_zone.local_addresses(network="2001:db8::/64") == { | |
ipaddress.IPv6Address("2001:db8::1"), | |
ipaddress.IPv6Address("2001:db8::2"), | |
} | |
def test_local_addresses_error(monkeypatch): | |
exc = Exception("uh oh!") | |
def _check_output(*args, **kwargs): | |
raise exc | |
monkeypatch.setattr("subprocess.check_output", _check_output) | |
with pytest.raises(dynv6_update_zone.AddressRetrievalError) as exc_info: | |
dynv6_update_zone.local_addresses(network="2001:db8::/64") | |
assert exc_info.value.__cause__ is exc | |
def test_unordered_list(): | |
assert ( | |
dynv6_update_zone.unordered_list(["a", "b", "c"], prefix="* ") | |
== "* a\n* b\n* c" | |
) | |
def test_update_gaierror(monkeypatch, caplog): | |
monkeypatch.setenv("token", "abc123") | |
err = -2 | |
msg = "Name or service not known" | |
def _urlopen(*args, **kwargs): | |
raise urllib.error.URLError(socket.gaierror(err, msg)) | |
monkeypatch.setattr("urllib.request.urlopen", _urlopen) | |
with pytest.raises(dynv6_update_zone.ZoneUpdateError): | |
dynv6_update_zone.update_zone("a.b.c") | |
[record] = caplog.records | |
assert record.levelname == "ERROR" | |
assert record.msg == f"{dynv6_update_zone.UPDATE_URL.netloc}: [Errno {err}] {msg}" | |
def test_update_502(monkeypatch, caplog): | |
monkeypatch.setenv("token", "abc123") | |
monkeypatch.setattr( | |
"dynv6_update_zone.UPDATE_URL", | |
urllib.parse.urlsplit("https://httpbin.org/status/502"), | |
) | |
with pytest.raises(dynv6_update_zone.ZoneUpdateError): | |
dynv6_update_zone.update_zone("a.b.c") | |
[record] = caplog.records | |
assert record.levelname == "ERROR" | |
assert ( | |
record.msg | |
== f"{dynv6_update_zone.UPDATE_URL.netloc}: HTTP Error 502: BAD GATEWAY" | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment