Skip to content

Instantly share code, notes, and snippets.

@dmtucker
Forked from corny/dynv6.sh
Last active December 4, 2024 17:02
Show Gist options
  • Save dmtucker/1203ab0b676283385f657bb83531a8c7 to your computer and use it in GitHub Desktop.
Save dmtucker/1203ab0b676283385f657bb83531a8c7 to your computer and use it in GitHub Desktop.
Update script for dynv6.com to set your IPv6 address
#!/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
#!/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())
[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'
{
"_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"
}
}
}
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