update-dns-pi.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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 CLEUCreds
  33. PI = '10.100.253.22'
  34. DNS_BASE = 'https://dc1-dns.ciscolive.local:8443/web-services/rest/resource/'
  35. DOMAIN = 'ciscolive.local'
  36. CNR_HEADERS = {
  37. 'authorization': CLEUCreds.JCLARKE_BASIC,
  38. 'accept': 'application/json',
  39. 'content-type': 'application/json'
  40. }
  41. PAGE_SIZE = 1000
  42. def get_devs():
  43. global PI, PAGE_SIZE, DOMAIN
  44. url = "https://{}/webacs/api/v1/data/Devices.json?.full=true&.maxResults={}".format(
  45. PI, PAGE_SIZE)
  46. headers = {
  47. 'Connection': 'close'
  48. }
  49. devices = []
  50. done = False
  51. first = 0
  52. while not done:
  53. code = 401
  54. i = 0
  55. nurl = url + "&.firstResult=" + str(first * PAGE_SIZE)
  56. while code != 200 and i < 10:
  57. response = requests.request(
  58. "GET", nurl, auth=(CLEUCreds.PI_USER, CLEUCreds.PI_PASS), headers=headers, verify=False)
  59. code = response.status_code
  60. if code != 200:
  61. i += 1
  62. time.sleep(3)
  63. if code == 200:
  64. j = json.loads(response.text)
  65. if int(j['queryResponse']['@last']) + 1 == int(j['queryResponse']['@count']):
  66. done = True
  67. else:
  68. first += 1
  69. for dev in j['queryResponse']['entity']:
  70. dev_dic = {}
  71. if 'deviceName' in dev['devicesDTO']:
  72. dev_dic['name'] = dev['devicesDTO']['deviceName']
  73. else:
  74. continue
  75. if not re.search(r'^0', dev_dic['name']):
  76. continue
  77. dev_dic['ip'] = dev['devicesDTO']['ipAddress']
  78. nparts = dev_dic['name'].split('-')
  79. if len(nparts) == 3:
  80. dev_dic['aliases'] = []
  81. dev_dic['name'] = dev_dic['name'].replace(
  82. '.{}'.format(DOMAIN), '')
  83. dev_dic['aliases'].append('-'.join(nparts[0:2]) + '.{}.'.format(DOMAIN))
  84. dev_dic['aliases'].append(nparts[2] + '.{}.'.format(DOMAIN))
  85. devices.append(dev_dic)
  86. return devices
  87. def add_entry(url, hname, dev):
  88. global CNR_HEADERS, DOMAIN
  89. aliases = []
  90. if 'aliases' in dev:
  91. aliases = dev['aliases']
  92. try:
  93. host_obj = {
  94. 'addrs': {
  95. 'stringItem': [
  96. dev['ip']
  97. ]
  98. },
  99. 'aliases': {
  100. 'stringItem': [
  101. ]
  102. },
  103. 'name': hname,
  104. 'zoneOrigin': DOMAIN
  105. }
  106. for alias in aliases:
  107. host_obj['aliases']['stringItem'].append(alias)
  108. response = requests.request(
  109. 'PUT', url, headers=CNR_HEADERS, json=host_obj, verify=False)
  110. response.raise_for_status()
  111. print('Added entry for {} ==> {} with aliases {}'.format(
  112. hname, dev['ip'], str(aliases)))
  113. except Exception as e:
  114. sys.stderr.write(
  115. 'Error adding entry for {}: {}\n'.format(hname, e))
  116. if __name__ == '__main__':
  117. devs = get_devs()
  118. for dev in devs:
  119. hname = dev['name'].replace('.{}'.format(DOMAIN), '')
  120. url = DNS_BASE + 'CCMHost' + '/{}'.format(hname)
  121. response = requests.request('GET', url, headers=CNR_HEADERS, params={
  122. 'zoneOrigin': DOMAIN}, verify=False)
  123. if response.status_code == 404:
  124. iurl = DNS_BASE + 'CCMHost'
  125. response = requests.request('GET', iurl, params={'zoneOrigin': DOMAIN, 'addrs': dev[
  126. 'ip'] + '$'}, headers=CNR_HEADERS, verify=False)
  127. cur_entry = []
  128. if response.status_code != 404:
  129. cur_entry = response.json()
  130. if len(cur_entry) > 0:
  131. print('Found entry for {}: {}'.format(
  132. dev['ip'], response.status_code))
  133. cur_entry = response.json()
  134. if len(cur_entry) > 1:
  135. print(
  136. 'ERROR: Found multiple entries for IP {}'.format(dev['ip']))
  137. continue
  138. print('Found old entry for IP {} => {}'.format(
  139. dev['ip'], cur_entry[0]['name']))
  140. durl = DNS_BASE + 'CCMHost' + \
  141. '/{}'.format(cur_entry[0]['name'])
  142. try:
  143. response = requests.request('DELETE', durl, params={
  144. 'zoneOrigin': DOMAIN}, headers=CNR_HEADERS, verify=False)
  145. response.raise_for_status()
  146. except Exception as e:
  147. sys.stderr.write('Failed to delete stale entry for {} ({})\n'.format(
  148. cur_entry[0]['name'], dev['ip']))
  149. continue
  150. add_entry(url, hname, dev)
  151. else:
  152. cur_entry = response.json()
  153. create_new = True
  154. for addr in cur_entry['addrs']['stringItem']:
  155. if addr == dev['ip']:
  156. if 'aliases' in dev and 'aliases' in cur_entry:
  157. if (len(dev['aliases']) > 0 and 'stringItem' not in cur_entry['aliases']) or (len(dev['aliases']) != len(cur_entry['aliases']['stringItem'])):
  158. break
  159. common = set(dev['aliases']) & set(
  160. cur_entry['aliases']['stringItem'])
  161. if len(common) != len(dev['aliases']):
  162. break
  163. create_new = False
  164. break
  165. elif ('aliases' in dev and 'aliases' not in cur_entry) or ('aliases' in cur_entry and 'aliases' not in dev):
  166. break
  167. else:
  168. create_new = False
  169. break
  170. if create_new:
  171. print('Deleting entry for {}'.format(hname))
  172. try:
  173. response = requests.request('DELETE', url, headers=CNR_HEADERS, params={
  174. 'zoneOrigin': DOMAIN}, verify=False)
  175. response.raise_for_status()
  176. except Exception as e:
  177. sys.stderr.write(
  178. 'Error deleting entry for {}: {}\n'.format(hname, e))
  179. add_entry(url, hname, dev)
  180. else:
  181. print('Not creating a new entry for {} as it already exists'.format(
  182. dev['name']))