-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Charly Caulet
committed
Sep 8, 2012
0 parents
commit 601b06a
Showing
4 changed files
with
176 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
#!/usr/bin/python3 | ||
import sys | ||
import getopt | ||
import xmlrpc.client | ||
import logging | ||
import ipretriever | ||
import ipretriever.adapter | ||
|
||
API_KEY = '' | ||
DOMAIN_NAME = 'mydomain.com' | ||
TTL = 300 | ||
|
||
RECORD = {'type':'A', 'name':'@'} | ||
|
||
LOG_LEVEL = logging.INFO | ||
LOG_FILE = 'gandyn.log' | ||
|
||
class GandiDomainUpdater( object ): | ||
"""Updates a gandi DNS record value.""" | ||
def __init__(self, api_key, domain_name, record): | ||
"""Constructor | ||
Keyword arguments: | ||
api_key -- The gandi XML-RPC api key. You have to activate it on gandi website. | ||
domain_name -- The domain whose record will be updated | ||
record -- Filters that match the record to update | ||
""" | ||
self.api_key = api_key | ||
self.domain_name = domain_name | ||
self.record = record | ||
self.__api = xmlrpc.client.ServerProxy('https://rpc.gandi.net/xmlrpc/') | ||
self.__zone_id = None | ||
|
||
def __get_active_zone_id( self ): | ||
"""Retrieve the domain active zone id.""" | ||
if self.__zone_id == None : | ||
self.__zone_id = self.__api.domain.info( | ||
self.api_key, | ||
self.domain_name | ||
)['zone_id'] | ||
return self.__zone_id | ||
|
||
def get_record_value( self ): | ||
"""Retrieve current value for the record to update.""" | ||
zone_id = self.__get_active_zone_id() | ||
return self.__api.domain.zone.record.list( | ||
self.api_key, | ||
zone_id, | ||
0, | ||
self.record | ||
)[0]['value'] | ||
|
||
def update_record_value( self, new_value, ttl=300 ): | ||
"""Updates record value. | ||
Update is done on a new zone version. If an error occurs, | ||
that new zone is deleted. Else, it is activated. | ||
This is an attempt of rollback mechanism. | ||
""" | ||
new_zone_version = None | ||
zone_id = self.__get_active_zone_id() | ||
try: | ||
#create new zone version | ||
new_zone_version = self.__api.domain.zone.version.new( | ||
self.api_key, | ||
zone_id | ||
) | ||
logging.debug('DNS working on a new zone (version %s)', new_zone_version) | ||
#get record id | ||
a_record_id = self.__api.domain.zone.record.list( | ||
self.api_key, | ||
zone_id, | ||
new_zone_version, | ||
self.record | ||
)[0]['id'] | ||
|
||
#update record value | ||
new_record = self.record.copy() | ||
new_record.update({'value': new_value, 'ttl': ttl}) | ||
updated_record = self.__api.domain.zone.record.update( | ||
self.api_key, | ||
zone_id, | ||
new_zone_version, | ||
{'id': a_record_id}, | ||
new_record | ||
) | ||
except xmlrpc.client.Fault as e: | ||
#delete updated zone | ||
if new_zone_version != None : | ||
self.__api.domain.zone.version.delete( | ||
self.api_key, | ||
zone_id, | ||
new_zone_version | ||
) | ||
raise | ||
else: | ||
#activate updated zone | ||
self.__api.domain.zone.version.set( | ||
self.api_key, | ||
zone_id, | ||
new_zone_version | ||
) | ||
|
||
def usage(argv): | ||
print(argv[0],' [[-c | --config] <config file>] [-h | --help]') | ||
print('\t-c --config <config file> : Path to the config file') | ||
print('\t-h --help : Displays this text') | ||
|
||
def main(argv, global_vars, local_vars): | ||
options, remainder = getopt.getopt(argv[1:], 'c:h', ['config=', 'help']) | ||
for opt, arg in options: | ||
if opt in ('-c', '--config'): | ||
config_file = arg | ||
#load config file | ||
exec( | ||
compile(open(config_file).read(), config_file, 'exec'), | ||
global_vars, | ||
local_vars | ||
) | ||
elif opt in ('-h', '--help'): | ||
usage(argv) | ||
|
||
try: | ||
logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%a, %d %b %Y %H:%M:%S', level=LOG_LEVEL, filename=LOG_FILE) | ||
public_ip_retriever = ipretriever.adapter.IfConfig() | ||
gandi_updater = GandiDomainUpdater(API_KEY, DOMAIN_NAME, RECORD) | ||
|
||
#get DNS record ip address | ||
previous_ip_address = gandi_updater.get_record_value() | ||
logging.debug('DNS record IP address : %s', previous_ip_address) | ||
|
||
#get current ip address | ||
current_ip_address = public_ip_retriever.get_public_ip() | ||
logging.debug('Current public IP address : %s', current_ip_address) | ||
|
||
if current_ip_address != previous_ip_address: | ||
#update record value | ||
gandi_updater.update_record_value( current_ip_address, TTL ) | ||
logging.info('DNS updated') | ||
else: | ||
logging.debug('Public IP address unchanged. Nothing to do.') | ||
except xmlrpc.client.Fault as e: | ||
logging.error('An error occured using Gandi API : %s ', e) | ||
except ipretriever.Fault as e: | ||
logging.error('An error occured retrieving public IP address : %s', e) | ||
|
||
main(sys.argv, globals(), locals()) | ||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
class Fault( Exception ) : | ||
def __init__(self, value): | ||
self.value = value | ||
def __str__(self): | ||
return repr(self.value) |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import urllib.request | ||
import re | ||
import ipretriever | ||
|
||
class IfConfig( object ): | ||
def get_public_ip( self ): | ||
"""Returns the current public IP address. Raises an exception if an issue occurs.""" | ||
try: | ||
url_page = 'http://ifconfig.me/ip' | ||
public_ip = None | ||
|
||
f = urllib.request.urlopen(url_page) | ||
data = f.read().decode("utf8") | ||
f.close() | ||
pattern = re.compile('\d+\.\d+\.\d+\.\d+') | ||
result = pattern.search(data, 0) | ||
if result == None: | ||
raise ipretriever.Fault('Service '+url_page+' failed to return the current public IP address') | ||
else: | ||
public_ip = result.group(0) | ||
except urllib.error.URLError as e: | ||
raise ipretriever.Fault(e) | ||
return public_ip |