diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4acd06b --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +config.py diff --git a/.gitignore b/.gitignore index 6156f13..6732c2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,174 @@ -src/config.py -src/config.pyc +# 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/ +share/python-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/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# 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/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# Custom +config.py +config.pyc diff --git a/.project b/.project deleted file mode 100644 index 79a2389..0000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - gandi_live_dns - - - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - - diff --git a/.pydevproject b/.pydevproject deleted file mode 100644 index 0ebadbb..0000000 --- a/.pydevproject +++ /dev/null @@ -1,8 +0,0 @@ - - - -/${PROJECT_DIR_NAME}/src - -python 2.7 -Default - diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5b727f7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.11 + +LABEL maintainer=https://github.com/dvdme +LABEL version="1.0.0" + +COPY . /usr/src/gandi_live_dns +WORKDIR /usr/src/gandi_live_dns + +# Install script requirements.txt +RUN pip install -r requirements.txt + +ENTRYPOINT ["python3", "gandi_live_dns.py"] + +CMD ["python3", "gandi_live_dns.py", "--help"] diff --git a/README.md b/README.md index 70fc261..a06d770 100644 --- a/README.md +++ b/README.md @@ -1,139 +1,90 @@ -gandi-live-dns ----- +# Gandi Live DNS -This is a simple dynamic DNS updater for the -[Gandi](https://www.gandi.net) registrar. It uses their [LiveDNS REST API](http://doc.livedns.gandi.net/) to update the zone file for a subdomain of a domain to point at the external IPv4 address of the computer it has been run from. +A dynamic DNS updater for the [Gandi](https://www.gandi.net) registrar. It uses Gandi's [LiveDNS REST API](http://doc.livedns.gandi.net/) to update the zone file for one or more subdomains of a domain to point at the external address computer it has been run from. -It has been developed on Debian 8 Jessie and tested on Debian 9 Stretch GNU/Linux using Python 2.7. +## Features -With the new v5 Website, Gandi has also launched a new REST API which makes it easier to communicate via bash/curl or python/requests. +- Supports multiple subdomains; +- IPv4 and IPv6 support, depending on the IP lookup service (see table bellow); +- Creates the subdomain if it not exists; +- Only tries to update if the IP addresses do not match. -### Goal +### IP Lookup Services -You want your homeserver to be always available at `dynamic_subdomain.mydomain.tld`. +Different IP lookup service providers are supported. Not all services support both IPv4 and IPv6. -### Debian Package Requirements +See table bellow: -`apt-get update && apt-get upgrade && apt-get install unzip python-requests python-args python-simplejson` +| Service | IPv4 | IPv6 | +| ----------------------------- | ------------- | -------------| +| http://whatismyip.akamai.com | Yes | No | +| https://ifconfig.co/ip | Yes | Yes | +| http://ifconfig.me/ip | Yes | Yes | +| http://ipinfo.io/ip | Yes | No | -#### API Key -First, you must apply for an API key with Gandi. Visit -https://account.gandi.net/en/ and apply for (at least) the production API -key by following their directions. +## How to run -#### A DNS Record -Create the DNS A Records in the GANDI Webinterface which you want to update if your IP changes. +A Gandi API Key is needed, see how to get it in [https://api.gandi.net/docs/authentication/](https://api.gandi.net/docs/authentication/). -#### Git Clone or Download the Script -Download the Script from here as [zip](https://github.com/cavebeat/gandi-live-dns/archive/master.zip)/[tar.gz](https://github.com/cavebeat/gandi-live-dns/archive/master.tar.gz) and extract it. +- Clone the repository; +- Rename `example.config.py` to `config.py`; +- Set the config with the appropriate values (for reference, see comments in the file); -or clone from git +### From git -`git clone https://github.com/cavebeat/gandi-live-dns.git` +- Create a virtual environment: `python3 -m venv venv`; +- Run it: `python3 gandi_live_dns.py` -#### Script Configuration -Then you'd need to configure the script in the src directory. -Copy `example.config.py` to `config.py`, and put it in the same directory as the script. +### From Docker -Edit the config file to fit your needs. +#### Building locally -##### api_secret -Start by retrieving your API Key from the "Security" section in new [Gandi Account admin panel](https://account.gandi.net/) to be able to make authenticated requests to the API. -api_secret = '---my_secret_API_KEY----' +- `docker build -t gandi-live-dns:local .` +- `docker run --rm -it -v $(pwd)/config.py:/usr/src/gandi_live_dns/config.py gandi-live-dns:local --force` -##### api_endpoint -Gandiv5 LiveDNS API Location -http://doc.livedns.gandi.net/#api-endpoint +### Command line options ``` -api_endpoint = 'https://dns.api.gandi.net/api/v5' +usage: gandi_live_dns.py [-h] [-v] [-f] [-r REPEAT] + +options: + -h, --help show this help message and exit + -v, --verbose increase output verbosity + -f, --force force an update/create + -r REPEAT, --repeat REPEAT + keep running and repeat every N seconds ``` -##### domain -Your domain for the subdomains to be updated +choose one as described in the config file. +### Run continuously -##### subdomains -All subdomains which should be updated. They get created if they do not yet exist. +#### Run with the repeat flag -``` -subdomains = ["subdomain1", "subdomain2", "subdomain3"] -``` -The first subdomain is used to find out the actual IP in the Zone Records. - -#### Run the script -And run the script: - -``` -root@dyndns:~/gandi-live-dns-master/src# ./gandi-live-dns.py -Checking dynamic IP: 127.0.0.1 -Checking IP from DNS Record subdomain1: 127.0.0.1 -IP Address Match - no further action -``` - -If your IP has changed, it will be detected and the update will be triggered. - - -``` -root@dyndns:~/gandi-live-dns-master/src# ./gandi-live-dns.py -Checking dynamic IP: 127.0.0.2 -Checking IP from DNS Record subdomain1: 127.0.0.1 -IP Address Mismatch - going to update the DNS Records for the subdomains with new IP 127.0.0.2 -Status Code: 201 , DNS Record Created , IP updated for subdomain1 -Status Code: 201 , DNS Record Created , IP updated for subdomain2 -Status Code: 201 , DNS Record Created , IP updated for subdomain3 -``` +Use `--repeat ` to run continuously. -#### Command Line Arguments +Example command with Docker to run continuously in the background: -``` -root@dyndns:~/gandi-live-dns-master/src# ./gandi-live-dns.py -h -usage: gandi-live-dns.py [-h] [-f] - -optional arguments: - -h, --help show this help message and exit - -f, --force force an update/create - -``` - -The force option runs the script, even when no IP change has been detected. -It will update all subdomains and even create them if they are missing in the -Zone File/Zone UUID. This can be used if additional/new subdomains get appended to the conig file. +- `docker run --restart unless-stopped -d -it -v $(pwd)/config.py:/usr/src/gandi_live_dns/config.py gandi-live-dns:local --repeat 1800` -### IP address lookup service -There exist several providers for this case, but better is to run your own somewhere. - -#### Poor Mans PHP Solution -On a LAMP Stack, place the file [index.php](https://github.com/cavebeat/gandi-live-dns/blob/master/src/example-index.php) in a directory /ip in your webroot. +#### Cron the script +Run the script every five minutes. ``` -root@laptop:~# curl https://blog.cavebeat.org/ip/ -127.0.0.1 +*/5 * * * * python3 gandi-live-dns.py >/dev/null 2>&1 ``` -This should fit your personal needs and you still selfhost the whole thing. - -#### IP address lookup service https://ifconfig.co -https://github.com/mpolden/ipd A simple service for looking up your IP address. This is the code that powers [https://ifconfig.co](https://ifconfig.co) - -#### use external services -choose one as described in the config file. -### Cron the script +### Issues -Run the script every five minutes. -``` -*/5 * * * * /root/gandi-live-dns-master/src/gandi-live-dns.py >/dev/null 2>&1 -``` -### Limitations -The XML-RPC API has a limit of 30 requests per 2 seconds, so i guess it's safe to update 25 subdomains at once with the REST API. +Use GitHub issues, avoid sending email's to the mail that is in git history as it is not available anymore. +### Acknowledgment -### Upcoming Features -* command line Argument for verbose mode +- First ideia: `https://github.com/cavebeat/gandi-live-dns` -### Inspiration +#### (Past) Inspiration -This DynDNS updater is inspired by https://github.com/jasontbradshaw/gandi-dyndns which worked very well +This DynDNS updater is inspired by https://github.com/jasontbradshaw/gandi-dyndns which worked very well with the classic DNS from Gandiv4 Website and their XML-RPC API. -Gandi has created a new API, i accidently switched to the new DNS Record System, so someone had to start a new updater. +Gandi has created a new API, i accidently switched to the new DNS Record System, so someone had to start a new updater. diff --git a/example.config.py b/example.config.py new file mode 100644 index 0000000..8013f59 --- /dev/null +++ b/example.config.py @@ -0,0 +1,37 @@ +""" +Gandi API key +""" +api_key = "-- API_KEY --" + +""" +Gandiv5 LiveDNS API Location +https://api.gandi.net/v5 +""" +api_endpoint = "https://api.gandi.net/v5" + +""" +Domain to be used +""" +domain = "mydomain.tld" + +""" +Subdomains to be updated. +Subdomains will be created first if not already present +""" +subdomains = ["subdomain1", "subdomain2", "subdomain3"] + +""" +DNS record TTL +300 seconds = 5 minutes +""" +ttl = "300" + +""" +IP address lookup service +"ipclaranet" for "http://ip.clara.net", +"ipinfoio" for "http://ipinfo.io/ip", +"ifconfigme" for "http://ifconfig.me/ip", +"ifconfigco" for "https://ifconfig.co/ip", +"akamai" for "http://whatismyip.akamai.com/" +""" +ifconfig = "choose_from_above_or_run_your_own" diff --git a/gandi_live_dns.py b/gandi_live_dns.py new file mode 100644 index 0000000..b13fa53 --- /dev/null +++ b/gandi_live_dns.py @@ -0,0 +1,125 @@ +import argparse +import logging +import sys +import time + +import requests + +import config +from ip_lookup_services import IPLookupServices + +logger = logging.getLogger(__name__) +LOG_LEVEL = logging.INFO +logging.basicConfig( + stream=sys.stdout, + format="%(levelname)s %(asctime)s %(message)s", + encoding="utf-8", + level=LOG_LEVEL, +) + + +class GandiDynamicDNS: + + IPV4 = "ipv4" + IPV6 = "ipv6" + + def __init__(self, subdomain, domain, force_update=False): + self._subdomain = subdomain + self._domain = domain + self._force_update = force_update + self._headers = { + "authorization": f"Bearer {config.api_key}", + "content-type": "application/json", + } + self._record_exists = {self.IPV4: False, self.IPV6: False} + self._dns_needs_update = {self.IPV4: True, self.IPV6: True} + logger.info(f"Subdomain: {self._subdomain}, Domain: {self._domain}") + self._update_addresses() + self._update_record_exists() + + def _update_addresses(self): + self._addresses = IPLookupServices(config.ifconfig).get_result() + logger.debug(f"Addresses: {self._addresses}") + + def _update_record_exists(self): + for ip_version in [self.IPV4, self.IPV6]: + rtype = "AAAA" if ip_version == self.IPV6 else "A" + res = requests.get( + f"{config.api_endpoint}/livedns/domains/{self._domain}/records/{self._subdomain}/{rtype}", + headers=self._headers, + timeout=10, + ) + self._record_exists[ip_version] = res.status_code == requests.codes.ok + if not self._force_update: + dns_value = ( + res.json()["rrset_values"][0] + if self._record_exists[ip_version] + and len(res.json()["rrset_values"]) > 0 + else None + ) + self._dns_needs_update[ip_version] = ( + not dns_value == self._addresses[ip_version] + ) + logger.debug(f"{ip_version}: {res}") + + def _update_record(self, ip_version, create=False): + verb = "POST" if create else "PUT" + rtype = "AAAA" if ip_version == self.IPV6 else "A" + res = requests.request( + verb, + f"{config.api_endpoint}/livedns/domains/{self._domain}/records/{self._subdomain}/{rtype}", + headers=self._headers, + json={ + "rrset_values": [self._addresses[ip_version]], + "rrset_ttl": config.ttl, + }, + timeout=10, + ) + if res.status_code == requests.codes.created: + logger.info( + f"{'Updated' if verb == 'PUT' else 'Created'} {self._subdomain}.{self._domain} with value {self._addresses[ip_version]}" + ) + else: + logger.error( + f"Failed to {'update' if verb == 'PUT' else 'Creatcreateed'} {self._subdomain}.{self._domain}" + ) + + def execute(self): + for ip_version in [self.IPV4, self.IPV6]: + if self._dns_needs_update[ip_version]: + if self._addresses[ip_version] is not None: + self._update_record( + ip_version, create=not self._record_exists[ip_version] + ) + else: + logger.info( + f"{self._subdomain}.{self._domain} is correct, skipping update" + ) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + 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" + ) + parser.add_argument( + "-r", "--repeat", type=int, help="keep running and repeat every N seconds" + ) + args = parser.parse_args() + if args.verbose: + logger.setLevel(logging.DEBUG) + try: + while True: + for item in config.subdomains: + gdd = GandiDynamicDNS(item, config.domain, force_update=args.force) + gdd.execute() + if args.repeat: + logger.info(f"Sleeping for {args.repeat} seconds") + time.sleep(args.repeat) + else: + break + except KeyboardInterrupt: + sys.exit(0) diff --git a/ip_lookup_services.py b/ip_lookup_services.py new file mode 100644 index 0000000..ff4c9fe --- /dev/null +++ b/ip_lookup_services.py @@ -0,0 +1,94 @@ +from abc import ABC, abstractmethod + +import requests + + +class IPLookupServiceBase(ABC): + + IPV4 = "ipv4" + IPV6 = "ipv6" + _TIMEOUT = 10 + + @property + def result(self): + _result = {} + IPLookupServiceBase.force_ipv4(True) + _addr = self.get() + _result[self.IPV4] = _addr if self.is_ipv4(_addr) else None + IPLookupServiceBase.force_ipv4(False) + _addr = self.get() + _result[self.IPV6] = _addr if self.is_ipv6(_addr) else None + return _result + + def is_ipv4(self, address): + return not self.is_ipv6(address) + + def is_ipv6(self, address): + return ":" in address + + @abstractmethod + def get(self): + pass + + @staticmethod + def force_ipv4(enable): + requests.packages.urllib3.util.connection.HAS_IPV6 = not enable + + +class Akamai(IPLookupServiceBase): + + _SERVICE_ADDRESS = "http://whatismyip.akamai.com/" + + def get(self): + return requests.get(self._SERVICE_ADDRESS, timeout=self._TIMEOUT).text.strip() + + +class IfConfigCo(IPLookupServiceBase): + + _SERVICE_ADDRESS = "https://ifconfig.co/ip" + + def get(self): + return requests.get(self._SERVICE_ADDRESS, timeout=self._TIMEOUT).text.strip() + + +class IfConfigMe(IPLookupServiceBase): + + _SERVICE_ADDRESS = "http://ifconfig.me/ip" + + def get(self): + return requests.get(self._SERVICE_ADDRESS, timeout=self._TIMEOUT).text.strip() + + +class IpInfoIo(IPLookupServiceBase): + + _SERVICE_ADDRESS = "http://ipinfo.io/ip" + + def get(self): + return requests.get(self._SERVICE_ADDRESS, timeout=self._TIMEOUT).text.strip() + + +class IpClaraNet(IPLookupServiceBase): + + _SERVICE_ADDRESS = "http://ip.clara.net" + + def get(self): + return requests.get(self._SERVICE_ADDRESS, timeout=self._TIMEOUT).text.strip() + + +class IPLookupServices: + + _IP_LOOKUP_SERVICES_MAP = { + "ipclaranet": IpClaraNet, + "ipinfoio": IpInfoIo, + "ifconfigme": IfConfigMe, + "ifconfigco": IfConfigCo, + "akamai": Akamai, + } + + def __init__(self, service_name): + self._service_name = service_name.lower() + if self._service_name not in self._IP_LOOKUP_SERVICES_MAP.keys(): + raise ValueError(f"Service {self._service_name} is not known") + + def get_result(self): + return self._IP_LOOKUP_SERVICES_MAP[self._service_name]().result diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests 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-index.php b/src/example-index.php deleted file mode 100644 index 726bc97..0000000 --- a/src/example-index.php +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/example.config.py b/src/example.config.py deleted file mode 100644 index 7ac37fc..0000000 --- a/src/example.config.py +++ /dev/null @@ -1,45 +0,0 @@ -''' -Created on 13 Aug 2017 -@author: cave -Copy this file to config.py and update the settings -''' -#!/usr/bin/env python -# encoding: utf-8 - -''' -Get your API key -Start by retrieving your API Key from the "Security" section in new Account admin panel to be able to make authenticated requests to the API. -https://account.gandi.net/ -''' -api_secret = '---my_secret_API_KEY----' - -''' -Gandiv5 LiveDNS API Location -http://doc.livedns.gandi.net/#api-endpoint -https://dns.api.gandi.net/api/v5/ -''' -api_endpoint = 'https://dns.api.gandi.net/api/v5' - -#your domain with the subdomains in the zone file/UUID -domain = 'mydomain.tld' - -#enter all subdomains to be updated, subdomains must already exist to be updated -subdomains = ["subdomain1", "subdomain2", "subdomain3"] - -#300 seconds = 5 minutes -ttl = '300' - -''' -IP address lookup service -run your own external IP provider: -+ https://github.com/mpolden/ipd -+ - -e.g. -+ 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' diff --git a/src/gandi-live-dns.py b/src/gandi-live-dns.py deleted file mode 100755 index 55e5757..0000000 --- a/src/gandi-live-dns.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 -''' -Gandi v5 LiveDNS - DynDNS Update via REST API and CURL/requests - -@author: cave -License GPLv3 -https://www.gnu.org/licenses/gpl-3.0.html - -Created on 13 Aug 2017 -http://doc.livedns.gandi.net/ -http://doc.livedns.gandi.net/#api-endpoint -> https://dns.gandi.net/api/v5/ -''' - -import requests, json -import config -import argparse - - -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 - ''' - r = requests.get(ifconfig_provider) - print 'Checking dynamic IP: ' , r._content.strip('\n') - return r.content.strip('\n') - -def get_uuid(): - ''' - find out ZONE UUID from domain - Info on domain "DOMAIN" - GET /domains/: - - ''' - url = config.api_endpoint + '/domains/' + config.domain - u = requests.get(url, headers={"X-Api-Key":config.api_secret}) - json_object = json.loads(u._content) - 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'] - exit() - -def get_dnsip(uuid): - ''' find out IP from first Subdomain DNS-Record - List all records with name "NAME" and type "TYPE" in the zone UUID - GET /zones//records//: - - The first subdomain from config.subdomain will be used to get - the actual DNS Record IP - ''' - - url = config.api_endpoint+ '/zones/' + uuid + '/records/' + config.subdomains[0] + '/A' - headers = {"X-Api-Key":config.api_secret} - 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') - else: - print 'Error: HTTP Status Code ', u.status_code, 'when trying to get IP from subdomain', config.subdomains[0] - print json_object['message'] - exit() - -def update_records(uuid, dynIP, subdomain): - ''' update DNS Records for Subdomains - Change the "NAME"/"TYPE" record from the zone UUID - PUT /zones//records//: - curl -X PUT -H "Content-Type: application/json" \ - -H 'X-Api-Key: XXX' \ - -d '{"rrset_ttl": 10800, - "rrset_values": [""]}' \ - https://dns.gandi.net/api/v5/zones//records// - ''' - url = config.api_endpoint+ '/zones/' + uuid + '/records/' + subdomain + '/A' - payload = {"rrset_ttl": config.ttl, "rrset_values": [dynIP]} - headers = {"Content-Type": "application/json", "X-Api-Key":config.api_secret} - u = requests.put(url, data=json.dumps(payload), headers=headers) - json_object = json.loads(u._content) - - if u.status_code == 201: - print 'Status Code:', u.status_code, ',', json_object['message'], ', IP updated for', subdomain - return True - else: - print 'Error: HTTP Status Code ', u.status_code, 'when trying to update IP from subdomain', subdomain - print json_object['message'] - exit() - - - -def main(force_update, verbosity): - - if verbosity: - print "verbosity turned on - not implemented by now" - - - #get zone ID from Account - uuid = get_uuid() - - #compare dynIP and DNS IP - dynIP = get_dynip(config.ifconfig) - dnsIP = get_dnsip(uuid) - - if force_update: - print "Going to update/create the DNS Records for the subdomains" - for sub in config.subdomains: - update_records(uuid, dynIP, sub) - else: - if dynIP == dnsIP: - print "IP Address Match - no further action" - else: - print "IP Address Mismatch - going to update the DNS Records for the subdomains with new IP", dynIP - for sub in config.subdomains: - update_records(uuid, dynIP, sub) - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - 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) - - - - - -