update-dns-tool.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. #!/usr/bin/env python2
  2. #
  3. # Copyright (c) 2017-2018 Joe Clarke <jclarke@cisco.com>
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions
  8. # are met:
  9. # 1. Redistributions of source code must retain the above copyright
  10. # notice, this list of conditions and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. # notice, this list of conditions and the following disclaimer in the
  13. # documentation and/or other materials provided with the distribution.
  14. #
  15. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  16. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  18. # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  19. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  21. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  22. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  23. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  24. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  25. # SUCH DAMAGE.
  26. import requests
  27. from requests.packages.urllib3.exceptions import InsecureRequestWarning
  28. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  29. import json
  30. import sys
  31. import re
  32. import os
  33. import CLEUCreds
  34. TOOL = 'tool.ciscolive.local'
  35. DNS_BASE = 'https://dc1-dns.ciscolive.local:8443/web-services/rest/resource/'
  36. DOMAIN = 'ciscolive.local'
  37. CNR_HEADERS = {
  38. 'authorization': CLEUCreds.JCLARKE_BASIC,
  39. 'accept': 'application/json',
  40. 'content-type': 'application/json'
  41. }
  42. CACHE_FILE = 'dns_records.dat'
  43. def get_devs():
  44. global DOMAIN
  45. url = "http://{}/get/switches/json".format(TOOL)
  46. devices = []
  47. response = requests.request('GET', url)
  48. code = response.status_code
  49. if code == 200:
  50. j = response.json()
  51. for dev in j:
  52. dev_dic = {}
  53. if dev['IPAddress'] == '0.0.0.0':
  54. continue
  55. if not re.search(r'^0', dev['Hostname']):
  56. continue
  57. dev_dic['name'] = dev['Hostname']
  58. dev_dic['aliases'] = [unicode('{}.{}.'.format(
  59. dev['Name'], DOMAIN)), unicode('{}.{}.'.format(dev['AssetTag'], DOMAIN))]
  60. dev_dic['ip'] = dev['IPAddress']
  61. devices.append(dev_dic)
  62. return devices
  63. def add_entry(url, hname, dev):
  64. global CNR_HEADERS, DOMAIN
  65. try:
  66. host_obj = {
  67. 'addrs': {
  68. 'stringItem': [
  69. dev['ip']
  70. ]
  71. },
  72. 'aliases': {
  73. 'stringItem': [
  74. ]
  75. },
  76. 'name': hname,
  77. 'zoneOrigin': DOMAIN
  78. }
  79. for alias in dev['aliases']:
  80. host_obj['aliases']['stringItem'].append(alias)
  81. response = requests.request(
  82. 'PUT', url, headers=CNR_HEADERS, json=host_obj, verify=False)
  83. response.raise_for_status()
  84. print('Added entry for {} ==> {} with aliases {}'.format(
  85. hname, dev['ip'], str(dev['aliases'])))
  86. except Exception as e:
  87. sys.stderr.write(
  88. 'Error adding entry for {}: {}\n'.format(hname, e))
  89. if __name__ == '__main__':
  90. prev_records = []
  91. if os.path.exists(CACHE_FILE):
  92. fd = open(CACHE_FILE, 'r')
  93. prev_records = json.load(fd)
  94. fd.close()
  95. devs = get_devs()
  96. for record in prev_records:
  97. found_record = False
  98. for dev in devs:
  99. hname = dev['name'].replace('.{}'.format(DOMAIN), '')
  100. if record == hname:
  101. found_record = True
  102. break
  103. if found_record:
  104. continue
  105. url = DNS_BASE + 'CCMHost' + '/{}'.format(record)
  106. try:
  107. response = requests.request('DELETE', url, headers=CNR_HEADERS, params={
  108. 'zoneOrigin': DOMAIN}, verify=False)
  109. response.raise_for_status()
  110. except Exception as e:
  111. sys.stderr.write('Failed to delete entry for {}'.format(record))
  112. records = []
  113. for dev in devs:
  114. hname = dev['name'].replace('.{}'.format(DOMAIN), '')
  115. records.append(hname)
  116. url = DNS_BASE + 'CCMHost' + '/{}'.format(hname)
  117. response = requests.request('GET', url, headers=CNR_HEADERS, params={
  118. 'zoneOrigin': DOMAIN}, verify=False)
  119. if response.status_code == 404:
  120. iurl = DNS_BASE + 'CCMHost'
  121. response = requests.request('GET', iurl, params={'zoneOrigin': DOMAIN, 'addrs': dev[
  122. 'ip'] + '$'}, headers=CNR_HEADERS, verify=False)
  123. cur_entry = []
  124. if response.status_code != 404:
  125. cur_entry = response.json()
  126. if len(cur_entry) > 0:
  127. print('Found entry for {}: {}'.format(
  128. dev['ip'], response.status_code))
  129. cur_entry = response.json()
  130. if len(cur_entry) > 1:
  131. print(
  132. 'ERROR: Found multiple entries for IP {}'.format(dev['ip']))
  133. continue
  134. print('Found old entry for IP {} => {}'.format(
  135. dev['ip'], cur_entry[0]['name']))
  136. durl = DNS_BASE + 'CCMHost' + \
  137. '/{}'.format(cur_entry[0]['name'])
  138. try:
  139. response = requests.request('DELETE', durl, params={
  140. 'zoneOrigin': DOMAIN}, headers=CNR_HEADERS, verify=False)
  141. response.raise_for_status()
  142. except Exception as e:
  143. sys.stderr.write('Failed to delete stale entry for {} ({})\n'.format(
  144. cur_entry[0]['name'], dev['ip']))
  145. continue
  146. add_entry(url, hname, dev)
  147. else:
  148. cur_entry = response.json()
  149. create_new = True
  150. for addr in cur_entry['addrs']['stringItem']:
  151. if addr == dev['ip']:
  152. if 'aliases' in dev and 'aliases' in cur_entry:
  153. if (len(dev['aliases']) > 0 and 'stringItem' not in cur_entry['aliases']) or (len(dev['aliases']) != len(cur_entry['aliases']['stringItem'])):
  154. break
  155. common = set(dev['aliases']) & set(
  156. cur_entry['aliases']['stringItem'])
  157. if len(common) != len(dev['aliases']):
  158. break
  159. create_new = False
  160. break
  161. elif ('aliases' in dev and 'aliases' not in cur_entry) or ('aliases' in cur_entry and 'aliases' not in dev):
  162. break
  163. else:
  164. create_new = False
  165. break
  166. if create_new:
  167. print('Deleting entry for {}'.format(hname))
  168. try:
  169. response = requests.request('DELETE', url, headers=CNR_HEADERS, params={
  170. 'zoneOrigin': DOMAIN}, verify=False)
  171. response.raise_for_status()
  172. except Exception as e:
  173. sys.stderr.write(
  174. 'Error deleting entry for {}: {}\n'.format(hname, e))
  175. add_entry(url, hname, dev)
  176. else:
  177. print('Not creating a new entry for {} as it already exists'.format(
  178. dev['name']))
  179. fd = open(CACHE_FILE, 'w')
  180. json.dump(records, fd, indent=4)
  181. fd.close()