From cdc3bda92752115fca5c3b0bee59eda77ec77a77 Mon Sep 17 00:00:00 2001 From: John Poland Date: Wed, 5 Mar 2025 15:22:17 -0500 Subject: [PATCH] update py files --- code/backupviacentral.py | 126 +++++++++++++++++++++++++++++++++++ code/central_devices.py | 64 ++++++++++++++++++ code/centralauth.py | 112 +++++++++++++++++++++++++++++++ code/findbadmgmtaddresses.py | 31 +++++++++ code/newcentral_devices.py | 88 ++++++++++++++++++++++++ 5 files changed, 421 insertions(+) create mode 100644 code/backupviacentral.py create mode 100644 code/central_devices.py create mode 100644 code/centralauth.py create mode 100644 code/findbadmgmtaddresses.py create mode 100644 code/newcentral_devices.py diff --git a/code/backupviacentral.py b/code/backupviacentral.py new file mode 100644 index 0000000..206f21a --- /dev/null +++ b/code/backupviacentral.py @@ -0,0 +1,126 @@ +import centralauth +import requests +import contextlib +import os +import stat +import sys +import tempfile + +import yaml + +@contextlib.contextmanager +def atomic_write(filename, text=True, keep=True, + owner=None, group=None, perms=None, + suffix='.bak', prefix='tmp'): + """Context manager for overwriting a file atomically. + + Usage: + + >>> with atomic_write("myfile.txt") as f: # doctest: +SKIP + ... f.write("data") + + The context manager opens a temporary file for writing in the same + directory as `filename`. On cleanly exiting the with-block, the temp + file is renamed to the given filename. If the original file already + exists, it will be overwritten and any existing contents replaced. + + (On POSIX systems, the rename is atomic. Other operating systems may + not support atomic renames, in which case the function name is + misleading.) + + If an uncaught exception occurs inside the with-block, the original + file is left untouched. By default the temporary file is also + preserved, for diagnosis or data recovery. To delete the temp file, + pass `keep=False`. Any errors in deleting the temp file are ignored. + + By default, the temp file is opened in text mode. To use binary mode, + pass `text=False` as an argument. On some operating systems, this make + no difference. + + The temporary file is readable and writable only by the creating user. + By default, the original ownership and access permissions of `filename` + are restored after a successful rename. If `owner`, `group` or `perms` + are specified and are not None, the file owner, group or permissions + are set to the given numeric value(s). If they are not specified, or + are None, the appropriate value is taken from the original file (which + must exist). + + By default, the temp file will have a name starting with "tmp" and + ending with ".bak". You can vary that by passing strings as the + `suffix` and `prefix` arguments. + """ + t = (uid, gid, mod) = (owner, group, perms) + if any(x is None for x in t): + info = os.stat(filename) + if uid is None: + uid = info.st_uid + if gid is None: + gid = info.st_gid + if mod is None: + mod = stat.S_IMODE(info.st_mode) + path = os.path.dirname(filename) + fd, tmp = tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=path, text=text) + try: + replace = os.replace # Python 3.3 and better. + except AttributeError: + if sys.platform == 'win32': + # FIXME This is definitely not atomic! + # But it's how (for example) Mercurial does it, as of 2016-03-23 + # https://selenic.com/repo/hg/file/tip/mercurial/windows.py + def replace(source, destination): + assert sys.platform == 'win32' + try: + os.rename(source, destination) + except OSError as err: + if err.winerr != 183: + raise + os.remove(destination) + os.rename(source, destination) + else: + # Atomic on POSIX. Not sure about Cygwin, OS/2 or others. + replace = os.rename + try: + with os.fdopen(fd, 'w' if text else 'wb') as f: + yield f + # Perform an atomic rename (if possible). This will be atomic on + # POSIX systems, and Windows for Python 3.3 or higher. + replace(tmp, filename) + tmp = None + os.chown(filename, uid, gid) + os.chmod(filename, mod) + finally: + if (tmp is not None) and (not keep): + # Silently delete the temporary file. Ignore any errors. + try: + os.unlink(tmp) + except Exception: + pass + +centralauth_db=centralauth.get_centralauth() + +inventory = yaml.safe_load(open("centralinv.yml")) + + +headers = { + 'Accept': 'application/json', + 'Authorization': f'Bearer {centralauth_db['access_token']}', + } + +for building in inventory: + for switch in inventory[building]['hosts']: + url = f"{centralauth_db['base_url']}/configuration/v1/devices/{inventory[building]['hosts'][switch]['serial_num']}/configuration" + response=requests.get(url,headers=headers) + #print(response.text) + print(f"/tmp/cfg/{building}/{switch}.cfg") + try: + with atomic_write(f"/tmp/cfg/{building}/{switch}.cfg") as f: + f.write(response.text) + except FileNotFoundError: + with open(f"/tmp/cfg/{building}/{switch}.cfg","w") as f: + f.write(response.text) + +url = f"{centralauth_db['base_url']}/configuration/v1/devices/SG3AKMY253/configuration" + +response=requests.get(url,headers=headers) + +print(response.text) \ No newline at end of file diff --git a/code/central_devices.py b/code/central_devices.py new file mode 100644 index 0000000..785d420 --- /dev/null +++ b/code/central_devices.py @@ -0,0 +1,64 @@ +import centralauth +import requests +import json + +DEBUG = False + +centralauth_db=centralauth.get_centralauth() + +all_text=""" +all: + vars: + ansible_network_os: arubanetworks.aoscx.aoscx + ansible_connection: arubanetworks.aoscx.aoscx # REST API via pyaoscx connection method + ansible_aoscx_validate_certs: False + ansible_aoscx_use_proxy: False + ansible_acx_no_proxy: True + ansible_ssh_common_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" +""" + + +headers = { + 'Accept': 'application/json', + 'Authorization': f'Bearer {centralauth_db['access_token']}', + } + +offset=1 +page_size=300 + +switch_dict={} + +while True: + + url = f"{centralauth_db['base_url']}/monitoring/v1/switches?limit={page_size}&offset={offset}" + + response = requests.get(url, headers=headers) + + #print(response.text) + + j=json.loads(response.text) + + if j['count']==0: + break + + for switch in j['switches']: + if not switch['status'] == 'Down' and not switch['ip_address']=='': + switch_dict[switch['name']]=switch + #print (switch['name'],switch['serial'],switch['ip_address']) + + offset+=page_size + +current_building='' +for switch_name in sorted(switch_dict): + switch=switch_dict[switch_name] + building=switch_name.split("-")[0] + if not building == current_building: + print(f"{building}:") + print( " hosts:") + current_building=building + print(f" {switch_name}:") + print(f" ansible_host: {switch['ip_address']}") + print(f" building_name: {current_building}") + print(f" serial_num: {switch['serial']}") + +print(f"{all_text}") diff --git a/code/centralauth.py b/code/centralauth.py new file mode 100644 index 0000000..3fbe979 --- /dev/null +++ b/code/centralauth.py @@ -0,0 +1,112 @@ +import sqlite3 +import requests +import json + + +def load_kvstore(): + con = sqlite3.connect("centralauth.db") + cur = con.cursor() + res=cur.execute("select key,value from kvstore") + data=res.fetchall() + kvstore={} + for key,value in data: + kvstore[key]=value + con.close() + return(kvstore) + + +def save_kvstore(kvstore): + con = sqlite3.connect("centralauth.db") + cur = con.cursor() + for key in kvstore: + sql=f"INSERT INTO kvstore (key,value) values('{key}','{kvstore[key]}') ON CONFLICT DO update set value=excluded.value" + cur.execute(sql) + con.commit() + con.close() + return(kvstore) + +def get_new_access_token(kvstore): + # step 1 + url=f"{kvstore['base_url']}/oauth2/authorize/central/api/login?client_id={kvstore['client_id']}" + payload = json.dumps({ + 'username':kvstore['central_username'], + 'password':kvstore['central_password'] + }) + headers = { + 'Content-Type': 'application/json' + } + ses = requests.Session() + response=ses.post(url,headers=headers,data=payload) + print (response) + + # step 2 + payload = json.dumps({'customer_id':kvstore['customer_id']}) + + url = f"{kvstore['base_url']}/oauth2/authorize/central/api/?client_id={kvstore['client_id']}&response_type=code&scope=all" + + response=ses.post(url,headers=headers,data=payload) + + response_data=json.loads(response.text) + + # step 3 + + payload = json.dumps({ + "grant_type": "authorization_code", + 'code':response_data['auth_code'], + 'client_id':kvstore['client_id'], + 'client_secret':kvstore['client_secret'], + }) + + url = f"{kvstore['base_url']}/oauth2/token" + + response=ses.post(url,headers=headers,data=payload) + + response_data=json.loads(response.text) + + kvstore['refresh_token']=response_data['refresh_token'] + kvstore['access_token']=response_data['access_token'] + + save_kvstore(kvstore) + return(kvstore) + +def refresh_access_token(kvstore): + headers = { + 'Content-Type': 'application/json' + } + + url=f"{kvstore['base_url']}/oauth2/token?client_id={kvstore['client_id']}&client_secret={kvstore['client_secret']}&grant_type=refresh_token&refresh_token={kvstore['refresh_token']}" + ses = requests.Session() + response=ses.post(url,headers=headers) + response_data=json.loads(response.text) + kvstore['refresh_token']=response_data['refresh_token'] + kvstore['access_token']=response_data['access_token'] + save_kvstore(kvstore) + return(kvstore) + +def get_centralauth(): + kvstore=load_kvstore() + + if 'access_token' not in kvstore: + kvstore=get_new_access_token(kvstore) + + if 'access_token' in kvstore: + kvstore=refresh_access_token(kvstore) + return({'access_token':kvstore['access_token'],'base_url':kvstore['base_url']}) + + +if __name__ == "__main__": + + centralauth=get_centralauth() + + headers = { + 'Accept': 'application/json', + 'Authorization': f'Bearer {centralauth['access_token']}', + } + + url = f"{centralauth['base_url']}/configuration/v1/devices/SG3AL5K03S/configuration" + + url = f"{centralauth['base_url']}/configuration/v1/devices/SG3AKMY253/configuration" + + response=requests.get(url,headers=headers) + + print(response.text) \ No newline at end of file diff --git a/code/findbadmgmtaddresses.py b/code/findbadmgmtaddresses.py new file mode 100644 index 0000000..78e5e61 --- /dev/null +++ b/code/findbadmgmtaddresses.py @@ -0,0 +1,31 @@ +import glob + +filenames=glob.glob("../configs/*/*8360*") + +for filename in filenames: + in_mgmt = False + in_vlan10 = False + vlan10_ip = None + mgmt_ip = None + with open(filename) as f: + for line in f: + if in_mgmt: + if not line[0]==" ": + in_mgmt=False + elif line.startswith(" ip static"): + mgmt_ip = line.strip().split()[2] + if line.startswith("interface mgmt"): + #print(line) + in_mgmt=True + if in_vlan10: + if not line[0]==" ": + in_vlan10=False + elif line.startswith(" ip address"): + vlan10_ip = line.strip().split()[2] + if line.strip()=="interface vlan 10": + #print(line) + in_vlan10=True + #print (line) + if vlan10_ip: + print (filename.split("/")[-1],mgmt_ip,vlan10_ip) + #sys.exit() \ No newline at end of file diff --git a/code/newcentral_devices.py b/code/newcentral_devices.py new file mode 100644 index 0000000..591b2a3 --- /dev/null +++ b/code/newcentral_devices.py @@ -0,0 +1,88 @@ +import requests +import json + +DEBUG = False + +url = "https://sso.common.cloud.hpe.com/as/token.oauth2" + +payload = { + "grant_type": "client_credentials", + "client_id": "65098f0f-747f-4c13-a338-c13028184ecd", + "client_secret": "710067957ef344918e90f8c890655f5e" +} +headers = { + "accept": "application/json", + "content-type": "application/x-www-form-urlencoded" +} + +all_text=""" +all: + vars: + ansible_network_os: arubanetworks.aoscx.aoscx + ansible_connection: arubanetworks.aoscx.aoscx # REST API via pyaoscx connection method + ansible_aoscx_validate_certs: False + ansible_aoscx_use_proxy: False + ansible_acx_no_proxy: True + ansible_ssh_common_args: "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" +""" +response = requests.post(url, data=payload, headers=headers) + +#print(response.text) + +response_json=json.loads(response.text) + +access_token=response_json['access_token'] + +devices={} + +page_count=1 +page_size=100 + +url_base='https://us4.api.central.arubanetworks.com' +while True: + + url = f"{url_base}/network-monitoring/v1alpha1/devices?limit={page_size}&next={page_count}" + #url = f"{url_base}/network-monitoring/v1alpha1/device-inventory?limit={page_size}&next={page_count}" + + headers = { + "accept": "application/json", + "authorization": f"Bearer {access_token}" + } + + response = requests.get(url, headers=headers) + items = json.loads(response.text)['items'] + for d in items: + #print (d['deviceName']) + if d['status']=="ONLINE": + devices[d['deviceName']]=d + if DEBUG: print(d['deviceName']) + + if json.loads(response.text)['next'] is None: + break + page_count+=1 + +for d in devices: + if devices[d]['deployment']=='Stack': + if DEBUG: print(d,'Stack',devices[d]) + headers['site-id']=devices[d]['siteId'] + url = f"{url_base}/network-monitoring/v1alpha1/stack/{devices[d]['id']}/members" + response = requests.get(url, headers=headers) + if DEBUG: print(response.text) + + +current_building='' +#print (devices) +for d in sorted(devices): + building=devices[d]['deviceName'].split("-")[0] + if not building == current_building: + print(f"{building}:") + print( " hosts:") + current_building=building + print(f" {devices[d]['deviceName']}:") + print(f" ansible_host: {devices[d]['ipv4']}") + print(f" building_name: {current_building}") + print(f" serial_num: {devices[d]['serialNumber']}") + + +print(f"{all_text}") +