diff --git a/.gitignore b/.gitignore index 6156f13..debf005 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,112 @@ -src/config.py -src/config.pyc +## From https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +.static_storage/ +.media/ +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# Custom + +config.py +test.py +.vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fe71598 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.linting.pylintEnabled": false +} \ No newline at end of file diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index 3abc68b..0000000 --- a/src/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/config.py -/test.py diff --git a/src/example.config.py b/src/example.config.py index 039c895..d54914d 100644 --- a/src/example.config.py +++ b/src/example.config.py @@ -36,10 +36,49 @@ + e.g. -+ https://ifconfig.co ++ https://ifconfig.co/ip + http://ifconfig.me/ip + http://whatismyip.akamai.com/ + http://ipinfo.io/ip + many more ... ''' ifconfig = 'choose_from_above_or_run_your_own' + + +''' +Sample logging config +This is optmized for *nix +Permission are needed to write the log file +''' +log_config = { + 'version': 1, + 'formatters': { + 'detailed': { + 'class': 'logging.Formatter', + 'format': '%(asctime)s %(name)-15s %(levelname)-8s %(processName)-10s %(message)s' + }, + 'simple': { + 'class': 'logging.Formatter', + 'format': '%(message)s' + } + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'formatter': 'simple', + }, + 'file': { + 'class': 'logging.handlers.TimedRotatingFileHandler', + 'filename': '/var/log/gandi-live-dns.log', + 'when': 'W6', + 'backupCount': '3', + 'formatter': 'detailed', + }, + }, + 'loggers': { + 'gandi-live-dns': { + 'level': 'DEBUG', + 'handlers': ['console', 'file'] + }, + }, + } diff --git a/src/gandi-live-dns.py b/src/gandi-live-dns.py index c5ad256..13c2ad4 100755 --- a/src/gandi-live-dns.py +++ b/src/gandi-live-dns.py @@ -12,18 +12,24 @@ http://doc.livedns.gandi.net/#api-endpoint -> https://dns.beta.gandi.net/api/v5/ ''' -import requests, json +import json +import logging +import logging.config +import requests import config import argparse +logging.config.dictConfig(config.log_config) +log = logging.getLogger('gandi-live-dns') def get_dynip(ifconfig_provider): ''' find out own IPv4 at home <-- this is the dynamic IP which changes more or less frequently similar to curl ifconfig.me/ip, see example.config.py for details to ifconfig providers ''' + log.debug('Using ifconfig_provider {}'.format(ifconfig_provider)) r = requests.get(ifconfig_provider) - print 'Checking dynamic IP: ' , r._content.strip('\n') - return r.content.strip('\n') + log.info('Checking dynamic IP: {}'.format(r.text.strip('\n'))) + return r.text.strip('\n') def get_uuid(): ''' @@ -33,13 +39,14 @@ def get_uuid(): ''' url = config.api_endpoint + '/domains/' + config.domain + log.debug('Using url {}'.format(url)) u = requests.get(url, headers={"X-Api-Key":config.api_secret}) - json_object = json.loads(u._content) + json_object = u.json() if u.status_code == 200: return json_object['zone_uuid'] else: - print 'Error: HTTP Status Code ', u.status_code, 'when trying to get Zone UUID' - print json_object['message'] + log.error('Error: HTTP Status Code {} when trying to get Zone UUID'.format(u.status_code)) + log.error('{}'.format(json_object['message'])) exit() def get_dnsip(uuid): @@ -52,15 +59,17 @@ def get_dnsip(uuid): ''' url = config.api_endpoint+ '/zones/' + uuid + '/records/' + config.subdomains[0] + '/A' + log.debug('Using url {}'.format(url)) headers = {"X-Api-Key":config.api_secret} + log.debug('Using headers {}'.format(headers)) u = requests.get(url, headers=headers) if u.status_code == 200: - json_object = json.loads(u._content) - print 'Checking IP from DNS Record' , config.subdomains[0], ':', json_object['rrset_values'][0].encode('ascii','ignore').strip('\n') - return json_object['rrset_values'][0].encode('ascii','ignore').strip('\n') + json_object = u.json() + log.info('Checking IP from DNS Record {} : {}'.format(config.subdomains[0], json_object['rrset_values'][0].strip('\n'))) + return json_object['rrset_values'][0].strip('\n') else: - print 'Error: HTTP Status Code ', u.status_code, 'when trying to get IP from subdomain', config.subdomains[0] - print json_object['message'] + log.error('Error: HTTP Status Code {} when trying to get IP from subdomain {}'.format(u.status_code, config.subdomains[0])) + log.error('{}'.format(json_object['message'])) exit() def update_records(uuid, dynIP, subdomain): @@ -74,17 +83,20 @@ def update_records(uuid, dynIP, subdomain): https://dns.beta.gandi.net/api/v5/zones//records// ''' url = config.api_endpoint+ '/zones/' + uuid + '/records/' + subdomain + '/A' + log.debug('Using url {}'.format(url)) payload = {"rrset_ttl": config.ttl, "rrset_values": [dynIP]} + log.debug('Using payload {}'.format(payload)) headers = {"Content-Type": "application/json", "X-Api-Key":config.api_secret} + log.debug('Using headers {}'.format(headers)) u = requests.put(url, data=json.dumps(payload), headers=headers) - json_object = json.loads(u._content) + json_object = u.json() if u.status_code == 201: - print 'Status Code:', u.status_code, ',', json_object['message'], ', IP updated for', subdomain + log.info('Status Code: {}, {}, IP updated for {}'.format(u.status_code, json_object['message'], subdomain)) return True else: - print 'Error: HTTP Status Code ', u.status_code, 'when trying to update IP from subdomain', subdomain - print json_object['message'] + log.error('Error: HTTP Status Code {} when trying to update IP from subdomain {}'.format(u.status_code, subdomain)) + log.error('{}'.format(json_object['message'])) exit() @@ -92,26 +104,30 @@ def update_records(uuid, dynIP, subdomain): def main(force_update, verbosity): if verbosity: - print "verbosity turned on - not implemented by now" + log.setLevel(logging.DEBUG) + log.debug('Verbosity On') - #get zone ID from Account uuid = get_uuid() + log.debug('uuid: {}'.format(uuid)) #compare dynIP and DNS IP dynIP = get_dynip(config.ifconfig) dnsIP = get_dnsip(uuid) + log.debug('dynIP: {}, dnsIP: {}'.format(dynIP, dnsIP)) if force_update: - print "Going to update/create the DNS Records for the subdomains" + log.info("Going to update/create the DNS Records for the subdomains") for sub in config.subdomains: + log.debug('Forcing update on {}'.format(sub)) update_records(uuid, dynIP, sub) else: if dynIP == dnsIP: - print "IP Address Match - no further action" + log.info("IP Address Match - no further action") else: - print "IP Address Mismatch - going to update the DNS Records for the subdomains with new IP", dynIP + log.info("IP Address Mismatch - going to update the DNS Records for the subdomains with new IP", dynIP) for sub in config.subdomains: + log.debug('Updating on {}'.format(sub)) update_records(uuid, dynIP, sub) if __name__ == "__main__": @@ -119,12 +135,5 @@ def main(force_update, verbosity): parser.add_argument('-v', '--verbose', help="increase output verbosity", action="store_true") parser.add_argument('-f', '--force', help="force an update/create", action="store_true") args = parser.parse_args() - - main(args.force, args.verbose) - - - - - \ No newline at end of file