Python API - Credential Manager

My 1st python project inspired by the discussion here https://thwack.solarwinds.com/product-forums/the-orion-platform/f/orion-sdk/90708/how-to-update-settingvalue-in-nodesettings-table

The script provides the following options:

1. Create a new SNMPv2 credential.
2. Update existing SNMPv2 credential.
3. Create a new username/password credential.
4. Update an existing username/password credential.
5. Assign a username/password credential to node(s)
6. Create a new SNMPv3 credential set
7. Update an existing SNMPv3 credential set
8. Assign an existing SNMPv3 credential to node(s)
9. List All Credentials (Option to save as csv)
10. List All Nodes and assigned credentials (Option to save as csv)

__author__ = "Tony Johnson"
import requests
import socket
from orionsdk import SwisClient
from prettytable import PrettyTable
import csv

requests.packages.urllib3.disable_warnings()

orion_server= '10.199.20.200'
username = 'Admin'
password = 'Password1'

class bcolors:
    OK = '\033[92m' #GREEN
    WARNING = '\033[93m' #YELLOW
    FAIL = '\033[91m' #RED
    RESET = '\033[0m' #RESET COLOR
    WHITE_BOLD = '\033[1;37m'

#https://github.com/solarwinds/OrionSDK/wiki/Credential-Management
#Dictionary Setups for SNMPv3 credential sets
AuthMethods ={
        1 : 'None',
        2 : 'MD5',
        3 : 'SHA1' 
        }
PrivacyEncryptionMethods={
    1 : 'None',
    2 : 'MD5',
    3 : 'AES128',
    4 : 'AES192',
    5:  'AES256'
    }
TrueFalse={
    '1' : bool(True),
    '2': bool(False)   
    }
print(bcolors.WHITE_BOLD + "-- SolarWinds Orion Credential Management --" + bcolors.RESET)
try:
    swis = SwisClient(orion_server, username, password)
    results = swis.query("select top 1 engineid from orion.engines")
    print(bcolors.OK +"Connected to Orion server." +bcolors.RESET)
except:
    print(bcolors.FAIL + "Could not connect to the Orion instance with the provided details." +bcolors.RESET)
    exit(0)

def default():
    print(bcolors.WARNING + "Invalid Selection" +bcolors.RESET)
    main()
    
def main():
    try:
        option = int(input(bcolors.WHITE_BOLD + "\nChoose an Option:"+ bcolors.RESET +"\n1. Create a new SNMPv2 credential.\n2. Update an existing SNMPv2 credential.\n3. Create a new username/password credential.\n4. Update an existing username/password credential.\n5. Assign a username/password credential to node(s)\n6. Create a new SNMPv3 credential set\n7. Update an existing SNMPv3 credential set \n8. Assign an existing SNMPv3 credential to node(s)\n9. List All Credentials\n10. List All Nodes and assigned credentials\nSelect Option:"))
    except:
        main()
    Options = {
        1 : CreateSNMPCredentials,
        2 : UpdateSNMPCredentials,
        3 : CreateNewUserPass,
        4 : UpdateUserNamePass,
        5 : AssignUserNamePassToNodes,   
        6 : CreateNewSNMPv3,
        7 : UpdateSNMPv3Credentials,
        8 : AssignSNMPv3ToNodes,
        9:  PrintCredentials,
        10: PrintAllNodesCreds
        }
    Options.get(option,default)()

def CreateSNMPCredentials():
    print(bcolors.WHITE_BOLD +"\nCreate a new SNMPv2 credential."+bcolors.RESET)
    cn = input("Credential Name:")
    cs = input("Community String:") 
    try:
        result = swis.invoke("Orion.Credential","CreateSNMPCredentials",cn,cs,"Orion")
    except Exception as e:
        print(bcolors.FAIL,e,bcolors.RESET)
        main()
    print(bcolors.OK + "Created a new SNMP credential {} with ID {}".format(cn,result)+bcolors.RESET)
    input("Press any key to continue..")
    main()

def UpdateSNMPCredentials():
    print(bcolors.WHITE_BOLD +"\nUpdate an existing SNMPv2 credential."+bcolors.RESET)
    AllowedSNMPv2CredIDs =[]
    Creds = GetListOfCredentials()
    SNMPV2s = list(filter(lambda x: ((x['CredentialType']) == "SnmpCredentialsV2"), Creds['results']))  
    print("Found the following SNMPv2 credentials.")
    t = PrettyTable()
    t.field_names = ["Credential ID","Credential Name" ]
    for row in SNMPV2s:       
        t.add_row([row['ID'], row['Name']])
        AllowedSNMPv2CredIDs.append(row['ID'])
    print(t)
    try:
        SNMPv2CredIDToUpdate = int(input("Enter the ID of the credential to update:"))
    except:
        UpdateSNMPCredentials()     
    if SNMPv2CredIDToUpdate not in AllowedSNMPv2CredIDs:
        print(bcolors.WARNING + "Entered ID is not valid. Must be a valid SNMPv2 credential ID" +bcolors.RESET)
        UpdateSNMPCredentials()
    print("Updating Credential ID:",SNMPv2CredIDToUpdate )
    cn = input("Credential Name:")
    cs = input("Community String:") 
    try:
        result = swis.invoke("Orion.Credential","UpdateSNMPCredentials",SNMPv2CredIDToUpdate,cn,cs)
    except Exception as e:
        print(bcolors.FAIL,e,bcolors.RESET)
        main()
    print(bcolors.OK + "Updated SNMPv2 credential {}".format(cn)+bcolors.RESET)
    input("Press any key to continue..")
    main()

def CreateNewUserPass():
    print(bcolors.WHITE_BOLD +"Create a new user and password credential."+bcolors.RESET)
    cn = input("Credential Name:")
    un = input("Username:") 
    pw = input("Password:")
    try:
        result = swis.invoke("Orion.Credential","CreateUsernamePasswordCredentials",cn,un,pw,"Orion")
    except Exception as e:
        print(bcolors.FAIL,e,bcolors.RESET)
    print(bcolors.OK + "Created new username/password credential {} with ID {}".format(cn,result)+bcolors.RESET)
    input("Press any key to continue..")
    main()

def UpdateUserNamePass():
    print(bcolors.WHITE_BOLD +"\nUpdate an existing username/password credential."+bcolors.RESET)
    AllowedCredIDs =[]
    Creds = GetListOfCredentials()
    SNMPV2s = list(filter(lambda x: ((x['CredentialType']) == "UsernamePasswordCredential"), Creds['results']))  
    print("Found the following Username/Password credentials")
    t = PrettyTable()
    t.field_names = ["Credential ID","Credential Name" ]
    for row in SNMPV2s:       
        t.add_row([row['ID'], row['Name']])
        AllowedCredIDs.append(row['ID'])
    print(t)
    try:
        CredIDToUpdate = int(input("Enter the ID of the credential to update:"))
    except:
        UpdateUserNamePass()
    if CredIDToUpdate not in AllowedCredIDs:
        print(bcolors.WARNING + "Entered ID is not valid. Must be a valid username/password credential ID" +bcolors.RESET)
        UpdateUserNamePass()
    print("Updating Credential ID:",CredIDToUpdate )
    cn = input("Credential Name:")
    un = input("Username:") 
    pw = input("Password:")
    try:
        result = swis.invoke("Orion.Credential","UpdateUsernamePasswordCredentials",CredIDToUpdate,cn,un,pw)
    except Exception as e:
        print(bcolors.FAIL,e,bcolors.RESET)
        main()
    print(bcolors.OK + "Updated username/password credential {}".format(cn)+bcolors.RESET)
    input("Press any key to continue..")
    main()

def AssignUserNamePassToNodes():
    print(bcolors.WHITE_BOLD + "\nAssign an existing username/password credential to node(s)" + bcolors.RESET)
    AllowedCredIDs =[]
    creds = GetListOfCredentials()
    SNMPv2s = list(filter(lambda x: ((x['CredentialType']) == "UsernamePasswordCredential"), creds['results']))  
    print("Found the following username/passsword credentials.")
    t = PrettyTable()
    t.field_names = ["Credential ID","Credential Name" ]
    for row in SNMPv2s:       
        t.add_row([row['ID'], row['Name']])
        AllowedCredIDs.append(row['ID'])
    print(t) 
    try:  
        IDToAssign = int(input("Enter the ID of the credential to assign to node(s):"))
    except Exception as e:
        AssignUserNamePassToNodes()
    if IDToAssign not in AllowedCredIDs:
        print(bcolors.WARNING + "Entered ID is not valid. Must be a valid username/password credential ID" +bcolors.RESET)
        AssignUserNamePassToNodes()
    nodeips = input("Enter a comma seperated list of I.Ps which will be updated:")
    listofips = nodeips.split (",")
    print("Checking validity of input for {} values".format(len(listofips)))
    for ip in listofips:
        try:
            socket.inet_aton(ip.strip())
            #legal
        except socket.error:
            #Not legal
            print(bcolors.WARNING + "Removing {} from list of I.Ps".format(ip)+bcolors.RESET)
            listofips.remove(ip.strip())
    print("Starting with a list of {} valid I.P(s)".format(len(listofips)))  
    NoUri=[]
    IpNotFound=[]
    Updated=0
    for ip in listofips:
        try:
            NodeIDs = swis.query("select NodeID, Caption from Orion.Nodes where ipaddress='{}'".format(ip.strip()))
            if not NodeIDs['results']:
                print(bcolors.WARNING + "SKIPPING: No node found with I.P address:" + bcolors.RESET,ip.strip())
                IpNotFound.append(ip.strip())                
        except Exception as e:
            print(bcolors.FAIL,e,bcolors.RESET)
            main()
        for NodeID in NodeIDs['results']:
            print("Found NodeID {} with caption {} for I.P Address {} ".format(NodeID['NodeID'],NodeID['Caption'],ip.strip()))
            query = "select Uri from Orion.nodesettings where settingname='WMICredential'and NodeID={}".format(NodeID["NodeID"])
            uris = swis.query(query)
            if not uris['results']:
                print(bcolors.WARNING + "SKIPPING: No Uri found in NodeSettings for NodeID {} with caption {}".format(NodeID['NodeID'],NodeID['Caption']) +bcolors.RESET)
                NoUri.append(ip)             
            else:
                try:
                    urisstr = [row['Uri'] for row in uris['results']]
                    result = swis.update(urisstr[0], SettingName='WMICredential', SettingValue=IDToAssign)
                    if result is None:
                        print(bcolors.OK + "SUCCESS! Updated assigned credential on NodeID {} with caption {}".format(NodeID['NodeID'],NodeID['Caption']) +bcolors.RESET)
                        Updated +=1
                except Exception as e:
                    print(bcolors.FAIL,e,bcolors.RESET)
    print(bcolors.OK + "DONE: Processesd list of {} I.Ps".format(len(listofips))+bcolors.RESET)
    if IpNotFound:
        print(bcolors.WARNING + "{} were not found as managed Orion nodes.".format(len(IpNotFound)) +bcolors.RESET)
        if input("Show list of I.Ps not found? Y/N:").capitalize() =='Y':        
            for i in IpNotFound:
                print(i)
    if NoUri:
        print(bcolors.WARNING + "{} were skipped as the required NodeSetting was not found. (Updated credential type must match existing credential type)".format(len(NoUri)) +bcolors.RESET)
        if input("Show list of I.Ps not found? Y/N:").capitalize() =='Y':        
            for i in NoUri:
                print(i)  
    
    input("Press any key to continue..")
    main()

def CreateNewSNMPv3():       
    print(bcolors.WHITE_BOLD + "Create a new SNMPv3 Credential Set." + bcolors.RESET)
    CredName = input("Credential Name: ")
    if not len(CredName) > 0:
        print(bcolors.WARNING + "Credential name cannot be empty!" +bcolors.RESET)
        CreateNewSNMPv3()            
    
    UserName = input("User Name:")
    SNMPv3Context = input("SNMPv3 Context:")   
    try:              
        AuthMethod =int(input("Select Authentication Method. \n 1. None  \n 2. MD5 \n 3. SHA1 \nAuthentication Method: "))
    except:
        CreateNewSNMPv3()
    if AuthMethod not in AuthMethods:     
        print(bcolors.WARNING + "Invalid Option. Enter the Authentication Method Number" +bcolors.RESET)
        CreateNewSNMPv3()  
    AuthPassword = input("Authentication Password:")
    AuthPassIsKey = input("Authentication Password is key?\n 1. Yes\n 2. No\nYour selection:") 
    if AuthPassIsKey not in TrueFalse:
        print(bcolors.WARNING + "Invalid Option." +bcolors.RESET)
        CreateNewSNMPv3()         
    try:
        PrivEncrMethod = int(input("Privacy / Encryption Method. \n 1. None.  \n 2. MD5 \n 3. AES128 \n 4. AES192 \n 5. AES256 \nAuthentication Method Number:")) 
        if PrivEncrMethod not in PrivacyEncryptionMethods:
            print(bcolors.WARNING + "Invalid Input. Enter the option number.")
            CreateNewSNMPv3()
    except Exception as e:
        print(bcolors.WARNING + "Invalid Input. Enter the option number.")
        CreateNewSNMPv3()  
  
    PrivEncrMethodPass = input("Privacy / Encryption Method Password:")
    PrivEncrMethodPassIsKey = input("Privacy / Encryption Method Password is key?\n1. Yes\n2. No\nYour selection:") 
    if PrivEncrMethodPassIsKey not in TrueFalse:
        print(bcolors.WARNING + "Invalid Option." +bcolors.RESET)
        CreateNewSNMPv3()          
    try:
        snmpv3_credential_id = swis.invoke('Orion.Credential', 'CreateSNMPv3Credentials',CredName,UserName,SNMPv3Context,AuthMethods[AuthMethod],AuthPassword,TrueFalse[AuthPassIsKey],PrivacyEncryptionMethods[PrivEncrMethod],PrivEncrMethodPass,TrueFalse[PrivEncrMethodPassIsKey])     
        print(bcolors.OK + "Created new SNMPv3 Credential with ID:" + bcolors.RESET, snmpv3_credential_id)
        input("Press any key to continue..")
        main()
    except Exception as e:
        print(bcolors.FAIL + "Error Creating SNMPv3 Credential Set.\n",e,bcolors.RESET)
        main()

def UpdateSNMPv3Credentials():
    AllowedSNMPv3CredIDs =[]
    creds = GetListOfCredentials()
    SNMPv3s = list(filter(lambda x: ((x['CredentialType']) == "SnmpCredentialsV3"), creds['results']))  
    print("Found the following SNMPv3 credentials")
    t = PrettyTable()
    t.field_names = ["Credential ID","Credential Name" ]
    for row in SNMPv3s:       
        t.add_row([row['ID'], row['Name']])
        AllowedSNMPv3CredIDs.append(row['ID'])
    print(t) 
    try:  
        IDToUpdate = int(input("Enter the ID of the credential to update:"))
    except:
        UpdateSNMPv3Credentials()
    if IDToUpdate not in AllowedSNMPv3CredIDs:
        print(bcolors.WARNING + "Entered ID is not valid. Must be a valid SNMPv3 credential ID" +bcolors.RESET)
        UpdateSNMPv3Credentials()
    print("Updating Credential ID:",IDToUpdate )
    CredentialName = input("Credential Name: ")
    UserName = input("User Name:")
    SNMPv3Context = input("SNMPv3 Context:") 
    try:         
        AuthMethod =int(input("Select Authentication Method. \n 1. None  \n 2. MD5 \n 3. SHA1 \nAuthentication Method: "))
    except:
        UpdateSNMPv3Credentials()
    if AuthMethod not in AuthMethods:     
        print(bcolors.WARNING + "Invalid Option. Enter the Authentication Method Number" +bcolors.RESET)
        UpdateSNMPv3Credentials()  
    AuthPassword = input("Authentication Password:")
    AuthPassIsKey = input("Authentication Password is key?\n 1. Yes\n 2. No\nYour selection:") 
    if AuthPassIsKey not in TrueFalse:
        print(bcolors.WARNING + "Invalid Option." +bcolors.RESET)
        CreateNewSNMPv3()         
    try:
        PrivEncrMethod = int(input("Privacy / Encryption Method. \n 1. None.  \n 2. MD5 \n 3. AES128 \n 4. AES192 \n 5. AES256 \nAuthentication Method Number:")) 
        if PrivEncrMethod not in PrivacyEncryptionMethods:
            print(bcolors.WARNING + "Invalid Input. Enter the option number.")
            CreateNewSNMPv3()
    except Exception as e:
        print(bcolors.WARNING + "Invalid Input. Enter the option number.")
        CreateNewSNMPv3()    
    PrivEncrMethodPass = input("Privacy / Encryption Method Password:")
    PrivEncrMethodPassIsKey = input("Privacy / Encryption Method Password is key?\n1. Yes\n2. No\nYour selection:") 
    if PrivEncrMethodPassIsKey not in TrueFalse:
        print(bcolors.WARNING + "Invalid Option." +bcolors.RESET)
    try:
        swis.invoke('Orion.Credential', 'UpdateSNMPv3Credentials',IDToUpdate,CredentialName,UserName,SNMPv3Context,AuthMethod,AuthPassword,bool(AuthPassIsKey),PrivEncrMethod,PrivEncrMethodPass,bool(PrivEncrMethodPassIsKey))     
        print(bcolors.OK + "Updated SNMPv3 Credntial ID:" +bcolors.RESET,IDToUpdate)
        input("Press any key to continue..")
        main()
    except Exception as e:
        print(bcolors.FAIL, e, bcolors.RESET)
        main()

def AssignSNMPv3ToNodes():
    print(bcolors.WHITE_BOLD + "\nAssign an existing SNMPv3 credential to node(s)" + bcolors.RESET)
    AllowedSNMPv3CredIDs =[]
    creds = GetListOfCredentials()
    SNMPv3s = list(filter(lambda x: ((x['CredentialType']) == "SnmpCredentialsV3"), creds['results']))  
    print("Found the following credentials")
    t = PrettyTable()
    t.field_names = ["Credential ID","Credential Name" ]
    for row in SNMPv3s:       
        t.add_row([row['ID'], row['Name']])
        AllowedSNMPv3CredIDs.append(row['ID'])
    print(t)
    try:   
        IDToAssign = int(input("Enter the ID of the credential to assign to node(s):"))
    except Exception as e:
        AssignSNMPv3ToNodes()
    if IDToAssign not in AllowedSNMPv3CredIDs:
        print(bcolors.WARNING + "Entered ID is not valid. Must be a valid SNMPv3 credential ID" +bcolors.RESET)
        AssignSNMPv3ToNodes()
    nodeips = input("Enter a comma seperated list of I.Ps which will be updated:")
    listofips = nodeips.split (",")
    print("Checking validity of input for {} values".format(len(listofips)))
    for ip in listofips:
        try:
            socket.inet_aton(ip.strip())
            #legal
        except socket.error:
            #Not legal
            print(bcolors.WARNING + "Removing {} from list of I.Ps".format(ip)+bcolors.RESET)
            listofips.remove(ip)
    print("Starting with a list of {} valid I.P(s)".format(len(listofips)))
    IpNotFound = []
    NoUri =[]
    Updated=0
    for ip in listofips:
        try:
            NodeIDs = swis.query("select NodeID, Caption from Orion.Nodes where ipaddress='{}'".format(ip.strip()))
            if not NodeIDs['results']:
                print(bcolors.WARNING + "SKIPPING: No node found with I.P address:" + bcolors.RESET,ip)
                IpNotFound.append(ip.strip())            
        except Exception as e:
            print(bcolors.FAIL,e,bcolors.RESET)
            main()
        for NodeID in NodeIDs['results']:
            print("Found NodeID {} with caption {} for I.P Address {} ".format(NodeID['NodeID'],NodeID['Caption'],ip.strip()))
            query = "select Uri from Orion.nodesettings where settingname='RWSNMPCredentialID'and NodeID={}".format(NodeID["NodeID"])
            uris = swis.query(query)
            if not uris['results']:
                print(bcolors.WARNING + "SKIPPING: No Uri found in NodeSettings for NodeID {} with caption {}".format(NodeID['NodeID'],NodeID['Caption']) +bcolors.RESET)
                NoUri.append(ip)             
            else:
                try:
                    urisstr = [row['Uri'] for row in uris['results']]
                    result = swis.update(urisstr[0], SettingName='RWSNMPCredentialID', SettingValue=IDToAssign)
                    if result is None:
                        print(bcolors.OK + "SUCCESS! Updated assigned credential on NodeID {} with caption {}".format(NodeID['NodeID'],NodeID['Caption']) +bcolors.RESET)
                        Updated +=1
                except Exception as e:
                    print(bcolors.FAIL,e,bcolors.RESET)
    print("DONE: Processesd list of {} I.Ps".format(len(listofips)))
    if Updated >0:
        print(bcolors.OK + "Updated credentials for {} I.Ps".format(Updated)+bcolors.RESET)
    if IpNotFound:
        print(bcolors.WARNING + "{} I.P(s) not found as managed Orion nodes.".format(len(IpNotFound)) +bcolors.RESET) 
        if input("Show list of I.Ps not found? Y/N:").capitalize() =='Y':        
            for i in IpNotFound:
                print(i)
    if NoUri:
        print(bcolors.WARNING + "{} I.P(s) skipped as the required NodeSetting was not found. (Updated credential type must match existing credential type)".format(len(NoUri)) +bcolors.RESET)
        if input("Show list of I.Ps without entry in NodeSettings? Y/N:").capitalize() =="Y":           
            for i in NoUri:
                print(i)  
    input("Press any key to continue..")
    main()

def PrintCredentials():
    CredentialsList=GetListOfCredentials()
    if not CredentialsList['results']:
        print(bcolors.WARNING + "No credentaials found." + bcolors.RESET)
        main()
    
    t = PrettyTable()
    t.field_names = ["ID", "Name", "CredentialType"]
    for CredentailItem in CredentialsList['results']:
        t.add_row([CredentailItem['ID'], CredentailItem['Name'], CredentailItem['CredentialType']])
    print(t)
    if input("Save to script directory as Orion_Credentials.csv? Y/N:").capitalize() =='Y':
        with open('Orion_Credentials.csv','w',newline='', encoding='utf-8') as f:
            write = csv.writer(f)     
            write.writerow(t.field_names)
            for CredentailItem in CredentialsList['results']:
                    write.writerow([CredentailItem['ID'], CredentailItem['Name'], CredentailItem['CredentialType']])
            print(bcolors.OK + "Saved Orion_Credentials.csv" +bcolors.RESET)
            input("Press any key to continue..")
    main()

def GetListOfCredentials():
    def CleanCred(c):
            bits = c.rpartition(".")
            return bits[2]
    try:
        credentials = swis.query("SELECT ID, Name, CredentialType FROM Orion.Credential where CredentialOwner='Orion'")
    except Exception as e:
        return e
    for c in credentials['results']:
        c['CredentialType'] = CleanCred(c['CredentialType'])
    
    return(credentials)
  
def GetAllNodesCreds():
    try:
        NodeCreds = swis.query("SELECT n.Caption AS [Node Name],n.ip_address as [IP Address],c.Name AS [WMI/SNMPv3 Credential Name], n.Community as [SNMPv2 Community], c.id as [Credential ID]FROM Orion.Nodes n left JOIN Orion.NodeSettings ns ON n.NodeID = ns.NodeID  and ns.SettingName like '%Credential%'LEFT JOIN Orion.Credential c ON ns.SettingValue = c.ID ")
    except Exception as e:
        return e
    return(NodeCreds)

def PrintAllNodesCreds():
    AllNodesList=GetAllNodesCreds()
    if not AllNodesList['results']:
        print(bcolors.WARNING + "No nodes found." + bcolors.RESET)
        main()
    y = PrettyTable()
    y.field_names = ["Node Name", "IP", "WMI/SNMPv3 Credential Name","SNMPv2 Community","Credential ID" ]
    for Node in AllNodesList['results']:       
        y.add_row([Node['Node Name'], Node['IP Address'], Node['WMI/SNMPv3 Credential Name'],Node['SNMPv2 Community'],Node['Credential ID']])
    print(y)
    if input("Save to script directory as Orion_Nodes_Credentials.csv? Y/N:").capitalize() =='Y':
        with open('Orion_Nodes_Credentials.csv','w',newline='', encoding='utf-8') as f:
            write = csv.writer(f)     
            write.writerow(y.field_names)
            for Node in AllNodesList['results']:
                    write.writerow([Node['Node Name'], Node['IP Address'], Node['WMI/SNMPv3 Credential Name'],Node['SNMPv2 Community'],Node['Credential ID']])
            print(bcolors.OK + "Orion_Nodes_Credentials.csv" +bcolors.RESET)
            input("Press any key to continue..")    
    main()    

if __name__ == '__main__':
   main()