Created
February 15, 2018 14:03
-
-
Save jeanfrancoisgratton/9b8c4e2ffd0d24995bec9bce8efde469 to your computer and use it in GitHub Desktop.
dns updater daemon
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
#!/usr/bin/env python3 | |
import datetime | |
import getpass | |
import ipaddress | |
import logging | |
import shutil | |
import subprocess | |
import sys | |
import socket | |
from P4 import P4, P4Exception | |
# v1.10 (2018.01.26) : initial version | |
# ./dnsupd_srv [-c configfile] [-l loglevel] [-?] [-v] | |
# showHelp() : shows the allowed switches server-side | |
def showHelp(): | |
print("dnsupdsrv.py [-c conf_file]") | |
print("-c conf_file : read the config file 'conf_file'") | |
print("-v: version") | |
print("-?: this help") | |
sys.exit(0) | |
# showVersion() : displays CHANGELOG & version info | |
def showVersion(): | |
print("dnsupdsrv.py") | |
print("============") | |
print("1.10 : 2018.01.26 -> added logging facility") | |
print("1.00 : 2018.01.14 -> initial version") | |
sys.exit(0) | |
# incrementSerial() : increment the DNS serial # | |
def incrementSerial(ligne): | |
serialnum = int(ligne[-2:]) | |
cdate = datetime.date.today().strftime('%Y%m%d') | |
if cdate != ligne[:8]: | |
return cdate+'01' | |
else: | |
++serialnum | |
if serialnum<10: | |
return ligne[:8]+'0'+str(serialnum) | |
else: | |
return ligne[:8]+str(serialnum) | |
# changeIPaddressInZone() modifies the serial # and the IP address in zonefile | |
def changeIPaddressInZone(logger, p4client, p4port, dnsfile, clientroot, newip): | |
if p4client != '': | |
p4 = P4() | |
p4.client = p4client | |
p4.port = p4port | |
p4.connect() | |
p4.run('sync', '-f', dnsfile) | |
p4.run('edit', dnsfile) | |
newzonefile = dnsfile.replace('//puppet/', clientroot + '/puppet/') | |
try: | |
with open(newzonefile, 'r') as infile: | |
with open('/tmp/newfile', 'w') as outfile: | |
for line in infile: | |
if line.endswith('; serial'): | |
line = incrementSerial(line) + '\t; serial' | |
if line.startswith('oslo'): | |
line = 'oslo\t\t\tIN\tA\t'+newip+'\n' | |
outfile.write(line) | |
shutil.move('/tmp/newfile', newzonefile) | |
except OSError as x: | |
logger.error('Exception: ' + int(x[0]) + ' => ' + x[1]) | |
if p4client !='': | |
try: | |
p4chg = p4.fetch_change() | |
p4chg._description = 'DNSUPDATER: ' + str(datetime.datetime.now().strftime('%Y/%m/%d %H.%M.%S')) + ": " + newip | |
p4chg._files = dnsfile | |
p4.run_submit(p4chg) | |
except P4Exception as p4x: | |
logger.error('Perforce error: ' + p4.error) | |
# readConfigFile() : reads the various params to be used by the daemon | |
def readConfigFile(logger, configfile = '/opt/dnsupdater/server.conf'): | |
logger.debug('Entering readConfigFile()') | |
try: | |
with open(configfile) as cfgf: | |
for line in cfgf.readlines(): | |
if not line.startswith('#') and not line.startswith(' ') and not line.startswith('\n'): | |
if line.startswith('bindport: '): | |
bindport = int(line.rsplit(' ', 1)[1].strip('\n')) | |
logger.debug('bindport: ' + str(bindport)) | |
if line.startswith('p4user: '): | |
p4user = line.rsplit(' ', 1)[1].strip('\n') | |
logger.debug('Perforce user: ' + p4user) | |
if line.startswith('p4client: '): | |
p4client = line.rsplit(' ', 1)[1].strip('\n') | |
logger.debug('Perforce workspace (p4client): '+ p4client) | |
if line.startswith('p4port: '): | |
p4port = line.rsplit(' ', 1)[1].strip('\n') | |
logger.debug('Perforce server: ' + p4port) | |
if line.startswith('dnsfile: '): | |
dnsfile = line.rsplit(' ', 1)[1].strip('\n') | |
logger.debug('P4 path to zone file (dnsfile): ' + dnsfile) | |
if line.startswith('clientroot: '): | |
clientroot = line.rsplit(' ', 1)[1].strip('\n') | |
logger.debug('P4 client workspace depot root: ' + clientroot) | |
if line.startswith('loglevel: '): | |
loglevel = int(line.rsplit(' ', 1)[1].strip('\n')) | |
logger.debug('log level: ' + str(loglevel)) | |
if loglevel < 1 or loglevel > 5: | |
loglevel = 3 | |
logger.setLevel(loglevel * 10) | |
logger.info('Successfully read ' + configfile) | |
except OSError as msg: | |
logger.error('Error with file' + configfile + '. Error code = ' + str(msg[0]) + ':' + msg[1]) | |
logger.debug('Leaving readConfigFile()') | |
def main(): | |
# check who is running this script ? (only devops is allowed) | |
##if getpass.getuser()!='devops': | |
## print("This must be run by user 'devops'") | |
## sys.exit(-1) | |
# Overridden variables | |
configfile = newip = p4client = '' | |
clientroot='/repos' | |
bindport = 2009 | |
loglevel = 3 | |
dnsfile = '//puppet/code/environments/production/modules/dns/files/famillegratton.net.zone' | |
p4port='perforce.cloud.famillegratton.net:1818' | |
shutdown = False | |
# Set logging facilities (https://docs.python.org/2.3/lib/node304.html) | |
# Logging levels : 0 = NOTSET, 10 = DEBUG, 20 = INFO, 30 = WARNING, 40 = ERROR, 50 = CRITICAL | |
logger = logging.getLogger(__name__) | |
lgf = logging.FileHandler('/var/log/dnsupdater/dnsupdater.log') | |
lgfformat = logging.Formatter('%(asctime)s %(levelname)s %(message)s', datefmt='%Y.%m.%d %H:%M:%S') | |
lgf.setFormatter(lgfformat) | |
logger.addHandler(lgf) | |
logger.setLevel(logging.INFO) | |
logger.info('=====================') | |
logger.info('MARK MARK MARK') | |
logger.info('=====================') | |
logger.info('===> Daemon startup') | |
# Parse command line | |
if '-c' in sys.argv and len(sys.argv) - sys.argv.index('-c') >= 2: | |
configfile = sys.argv[sys.argv.index('-c') + 1] | |
if '-?' in sys.argv or '-h' in sys.argv: | |
showHelp() | |
if '-v' in sys.argv: | |
showVersion() | |
if '-l' in sys.argv and len(sys.argv) - sys.argv.index('-l') >= 2: | |
loglevel = int(sys.argv.index('-l')) + 1 | |
if loglevel < 1 or loglevel > 5: | |
loglevel = 3 | |
# Set logging level past logfile header | |
# (ie: we wanted a startup message in the logfile, now we log according to config) | |
logger.setLevel(loglevel * 10) | |
readConfigFile(logger, configfile) | |
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
try: | |
logger.debug('binding on port ' + str(bindport)) | |
s.bind(('', bindport)) | |
except socket.error as msg: | |
logger.error('Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]) | |
sys.exit() | |
# Start listening on socket | |
logger.debug('listening.....') | |
s.listen(1) | |
# now keep talking with the client | |
while 1: | |
connection, client_address = s.accept() | |
source = client_address[0] + ' tcp/' + str(client_address[1]) | |
logger.info('Connection from ' + source) | |
try: | |
while True: | |
data = connection.recv(256) | |
if not data: | |
break | |
finally: | |
logger.info('Closing connection from ' + source) | |
connection.close() | |
recv = str(data) | |
if recv.endswith('<-Q'): | |
logger.warning('Received daemon shutdown request from' + source) | |
recv=recv[:-3] | |
shutdown = True | |
try: | |
newip = ipaddress.ip_address(recv) | |
except ValueError: | |
newip='' | |
if newip != '': | |
changeIPaddressInZone(logger, p4client, p4port, dnsfile, clientroot, newip) | |
with open('/var/log/dnsupdater/ip.log', 'a') as logfile: | |
try: | |
logfile.write(datetime.datetime.now().strftime('%Y/%m/%d %H.%M.%S') + ': ' + newip) | |
except OSError as msg: | |
logger.warning('Unable to write /var/log/dnsupdater/ip.log : ' + msg[1]) | |
subprocess.call('systemctl reload named') | |
if shutdown == True: | |
logger.info('---> Daemon is shutting down') | |
sys.exit(1) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment