Initial test
This commit is contained in:
parent
2d9a070514
commit
312d872adc
5 changed files with 171 additions and 13 deletions
|
|
@ -2,17 +2,20 @@ import click
|
||||||
|
|
||||||
from ip_listener.main import detect
|
from ip_listener.main import detect
|
||||||
|
|
||||||
DEFAULT_RESOLVERS = ["@resolver1.opendns.com", "@resolver2.opendns.com"]
|
DEFAULT_DNS = "myip.opendns.com"
|
||||||
|
DEFAULT_DNS_NAME = "@resolver1.opendns.com"
|
||||||
DEFAULT_API_URL = "https://api.transip.nl/v6"
|
DEFAULT_API_URL = "https://api.transip.nl/v6"
|
||||||
|
|
||||||
|
|
||||||
@click.command()
|
@click.command()
|
||||||
@click.argument("domains", envvar="DOMAIN", nargs=-1)
|
@click.argument("domains", envvar="DOMAINS", nargs=-1)
|
||||||
@click.argument("token", envvar="TOKEN")
|
@click.argument("token", envvar="TOKEN")
|
||||||
@click.option(
|
@click.option("--dns", envvar="DNS", default=DEFAULT_DNS)
|
||||||
"--resolvers", envvar="RESOLVERS", default=DEFAULT_RESOLVERS, multiple=True
|
@click.option("--dns-name", envvar="DNS_NAME", default=DEFAULT_DNS_NAME)
|
||||||
)
|
|
||||||
@click.option("--api-url", envvar="API_URL", default=DEFAULT_API_URL)
|
@click.option("--api-url", envvar="API_URL", default=DEFAULT_API_URL)
|
||||||
@click.option("--read-only/--write", envvar="READ_ONLY", default=False)
|
@click.option("--read-only/--write", envvar="READ_ONLY", default=False)
|
||||||
def run(*args):
|
def run(domains, token, dns, dns_name, api_url, read_only):
|
||||||
detect(*args)
|
if not domains:
|
||||||
|
raise ValueError("No domain(s) specified")
|
||||||
|
|
||||||
|
detect(domains, (dns, dns_name), api_url, token, read_only)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
@ -16,6 +17,8 @@ def _get_ip(resolvers):
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
raise OSError("Unable to retrieve current IP") from e
|
raise OSError("Unable to retrieve current IP") from e
|
||||||
|
|
||||||
|
return output.decode("utf-8").strip()
|
||||||
|
|
||||||
|
|
||||||
def _get_domain(domain, token, api_url):
|
def _get_domain(domain, token, api_url):
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
|
@ -43,7 +46,7 @@ def _get_domain_data(domains, token, api_url):
|
||||||
yield {"domain": domain, **response.json()}
|
yield {"domain": domain, **response.json()}
|
||||||
|
|
||||||
|
|
||||||
def _update_domain(domain, payload, token, api_url):
|
def _update_domain(domain, payload, api_url, token):
|
||||||
headers = {"Authorization": f"Bearer {token}"}
|
headers = {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
return requests.put(
|
return requests.put(
|
||||||
|
|
@ -51,13 +54,13 @@ def _update_domain(domain, payload, token, api_url):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _update_domains(updated_domains, token, read_only):
|
def _update_domains(updated_domains, api_url, token, read_only):
|
||||||
if read_only:
|
if read_only:
|
||||||
return
|
return
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=10) as executor:
|
with ThreadPoolExecutor(max_workers=10) as executor:
|
||||||
futures = {
|
futures = {
|
||||||
executor.submit(_update_domain, domain, entries, token): domain
|
executor.submit(_update_domain, domain, entries, api_url, token): domain
|
||||||
for domain, entries in updated_domains.items()
|
for domain, entries in updated_domains.items()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +77,7 @@ def _update_domains(updated_domains, token, read_only):
|
||||||
logger.info(f"Updated domain {domain}")
|
logger.info(f"Updated domain {domain}")
|
||||||
|
|
||||||
|
|
||||||
def detect(domains, resolvers, token, api_url, read_only):
|
def detect(domains, resolvers, api_url, token, read_only):
|
||||||
ip = _get_ip(resolvers)
|
ip = _get_ip(resolvers)
|
||||||
domain_data = _get_domain_data(domains, token, api_url)
|
domain_data = _get_domain_data(domains, token, api_url)
|
||||||
updated_domains = {}
|
updated_domains = {}
|
||||||
|
|
@ -97,4 +100,4 @@ def detect(domains, resolvers, token, api_url, read_only):
|
||||||
|
|
||||||
updated_domains[domain] = {"dnsEntries": updated_entries}
|
updated_domains[domain] = {"dnsEntries": updated_entries}
|
||||||
|
|
||||||
_update_domains(updated_domains, token, api_url, read_only)
|
_update_domains(updated_domains, api_url, token, read_only)
|
||||||
|
|
|
||||||
76
ip_listener/tests.py
Normal file
76
ip_listener/tests.py
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import json
|
||||||
|
from unittest import TestCase
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from ip_listener.cli import DEFAULT_API_URL, run
|
||||||
|
|
||||||
|
|
||||||
|
class RunTestCase(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
patcher = patch("ip_listener.main.subprocess.check_output")
|
||||||
|
self.mocked_dns = patcher.start()
|
||||||
|
|
||||||
|
patcher = patch("ip_listener.main.requests.get")
|
||||||
|
self.mocked_get = patcher.start()
|
||||||
|
|
||||||
|
patcher = patch("ip_listener.main.requests.put")
|
||||||
|
self.mocked_put = patcher.start()
|
||||||
|
|
||||||
|
def test_simple(self):
|
||||||
|
self.mocked_dns.return_value = b"111.420\n"
|
||||||
|
self.mocked_get.return_value.json.return_value = {
|
||||||
|
"dnsEntries": [
|
||||||
|
{
|
||||||
|
"name": "@",
|
||||||
|
"expire": 60,
|
||||||
|
"type": "A",
|
||||||
|
"content": "111.421",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_links": [
|
||||||
|
{
|
||||||
|
"rel": "self",
|
||||||
|
"link": "https://api.transip.nl/v6/domains/foobar.com/dns",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rel": "domain",
|
||||||
|
"link": "https://api.transip.nl/v6/domains/foobar.com",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
with self.assertLogs("ip_listener.main", level="INFO") as logger:
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(run, ["foobar.com", "TOKEN"])
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
logger.output, ["INFO:ip_listener.main:Updated domain foobar.com"]
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result.exit_code, 0)
|
||||||
|
|
||||||
|
self.mocked_get.assert_called_with(
|
||||||
|
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
|
||||||
|
headers={"Authorization": "Bearer TOKEN"},
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_json = json.dumps(
|
||||||
|
{
|
||||||
|
"dnsEntries": [
|
||||||
|
{
|
||||||
|
"name": "@",
|
||||||
|
"expire": 60,
|
||||||
|
"type": "A",
|
||||||
|
"content": "111.420",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mocked_put.assert_called_with(
|
||||||
|
f"{DEFAULT_API_URL}/domains/foobar.com/dns",
|
||||||
|
data=expected_json,
|
||||||
|
headers={"Authorization": "Bearer TOKEN"},
|
||||||
|
)
|
||||||
77
poetry.lock
generated
77
poetry.lock
generated
|
|
@ -39,6 +39,22 @@ toml = ">=0.10.1"
|
||||||
typed-ast = ">=1.4.0"
|
typed-ast = ">=1.4.0"
|
||||||
typing-extensions = ">=3.7.4"
|
typing-extensions = ">=3.7.4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2020.12.5"
|
||||||
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chardet"
|
||||||
|
version = "4.0.0"
|
||||||
|
description = "Universal encoding detector for Python 2 and 3"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "7.1.2"
|
version = "7.1.2"
|
||||||
|
|
@ -47,6 +63,14 @@ category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "2.10"
|
||||||
|
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "isort"
|
name = "isort"
|
||||||
version = "5.6.4"
|
version = "5.6.4"
|
||||||
|
|
@ -103,6 +127,24 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests"
|
||||||
|
version = "2.25.1"
|
||||||
|
description = "Python HTTP for Humans."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
|
||||||
|
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
certifi = ">=2017.4.17"
|
||||||
|
chardet = ">=3.0.2,<5"
|
||||||
|
idna = ">=2.5,<3"
|
||||||
|
urllib3 = ">=1.21.1,<1.27"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
|
|
@ -127,10 +169,23 @@ category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urllib3"
|
||||||
|
version = "1.26.2"
|
||||||
|
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
brotli = ["brotlipy (>=0.6.0)"]
|
||||||
|
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
|
||||||
|
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.0"
|
lock-version = "1.0"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
content-hash = "94aba70614582f8f382aa70da21bde6f0b851c650646027f17cb04012b2082bd"
|
content-hash = "c5bce676d27fa0d95f442535e7493e2f9ceba0b7af3cbde1603b41044fc9723a"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
appdirs = [
|
appdirs = [
|
||||||
|
|
@ -143,10 +198,22 @@ autoflake = [
|
||||||
black = [
|
black = [
|
||||||
{file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
|
{file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"},
|
||||||
]
|
]
|
||||||
|
certifi = [
|
||||||
|
{file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
|
||||||
|
{file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
|
||||||
|
]
|
||||||
|
chardet = [
|
||||||
|
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
|
||||||
|
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
|
||||||
|
]
|
||||||
click = [
|
click = [
|
||||||
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
|
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
|
||||||
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
|
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
|
||||||
]
|
]
|
||||||
|
idna = [
|
||||||
|
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
||||||
|
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
|
||||||
|
]
|
||||||
isort = [
|
isort = [
|
||||||
{file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"},
|
{file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"},
|
||||||
{file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"},
|
{file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"},
|
||||||
|
|
@ -210,6 +277,10 @@ regex = [
|
||||||
{file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"},
|
{file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"},
|
||||||
{file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"},
|
{file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"},
|
||||||
]
|
]
|
||||||
|
requests = [
|
||||||
|
{file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
|
||||||
|
{file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
|
||||||
|
]
|
||||||
toml = [
|
toml = [
|
||||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||||
|
|
@ -242,3 +313,7 @@ typing-extensions = [
|
||||||
{file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
|
{file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
|
||||||
{file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
|
{file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"},
|
||||||
]
|
]
|
||||||
|
urllib3 = [
|
||||||
|
{file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"},
|
||||||
|
{file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"},
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ license = "GPL-3.0"
|
||||||
python = "^3.7"
|
python = "^3.7"
|
||||||
click = "^7.1.2"
|
click = "^7.1.2"
|
||||||
python-dotenv = "^0.15.0"
|
python-dotenv = "^0.15.0"
|
||||||
|
requests = "^2.25.1"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = "^20.8b1"
|
black = "^20.8b1"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue