$ add_cobbler_system.py -h ----------------- Cobbler profiles: ----------------- 0. +OL-R5-U11-Server-dvd-x86_64 1. +OL-R5-U11-Server-dvd-xen-x86_64 2. +OL-R5-U11-Server-x86_64_javacon 3. +OL-R6-U10-x86_64 4. +OL-R6-U10-x86_64_SoL 5. +OL-R6-U10-x86_64_javacon - shortened - 26. +OVS-3.3.5-1109-x86_64 27. +OVS-3.4.4-1709-x86_64 28. +OVS-3.4.4-1735-x86_64 29. +OVS-3.4.6-2105-x86_64 usage: add_cobbler_system.py [-h] -s SYSTEM -m MAC -p {0,1,2,3,4,5, shortened ,25,26,27,28,29} [-i ISCSI_LUN] [-k KICKSTART] Add a system (including DHCP reservation) to LOCation Cobbler optional arguments: -h, --help show this help message and exit -s SYSTEM, --system SYSTEM A system to be installed via Cobbler -m MAC, --mac MAC System's MAC (: as delimiter) -p {0,1,2,3,4,5, shortened,25,26,27,28,29}, --profile {0,1,2,3,4,5,shortened 25,26,27,28,29} List of current Cobbler profiles, select one -i ISCSI_LUN, --iscsi_lun ISCSI_LUN iSCSI SAN URI, ex.
:[protocol]:[port]:[LUN]: - ipv4 (required): iSCSI server IPv4 - protocol (optional): ignored and can be left empty - port (optional): TCP port of iSCSI target, leave empty for default port 3260 - LUN (optional): LUN number, leave empty for default value 0 - targetname (required): iSCSI target IQN -k KICKSTART, --kickstart KICKSTART Full path to your custom file, ex. /var/lib/cobbler/kickstarts/users-tmp/ .ks Brought to you by Zarko
2019-11-03 20:07:28,624:DEBUG: 2019-11-03 20:07:28,624:DEBUG: START AT : Mon, 04 Nov 2019 04:07:28 2019-11-03 20:07:28,625:DEBUG: Running by zdudic 2019-11-03 20:07:28,919:DEBUG: Profile ks: /var/lib/cobbler/kickstarts/LabOps_MBR_OL 2019-11-03 20:07:29,240:DEBUG: OK: srbija.dom.com is not in Cobbler, continue with adding it. 2019-11-03 20:07:29,240:DEBUG: Adding srbija.dom.com to Cobbler with profile +OL-R7-U3-x86_64_javacon 2019-11-03 20:07:40,623:DEBUG: Run DHCP reservation for Linux for srbija.dom.com 00:33:28:8e:57:7e 2019-11-03 20:07:41,369:DEBUG: Syntax: OK Shutting down dhcpd: [ OK ] Starting dhcpd: [ OK ] Install type: Linux DHCP reservation has been created: /etc/dhcp/clients/srbija.dom.com 2019-11-03 20:07:41,369:DEBUG: Getting subnet data from http://ca-dhcp-loc.dom.com/subnets/ 2019-11-03 20:07:41,463:DEBUG: Collected subnet data: ['Name', 'Last modified', 'Size', 'Description', 'Parent Directory', '10_122_48_0.24', -shortened-, '10_134_255_0.24'] 2019-11-03 20:07:41,464:DEBUG: 10.129.192.103 is in subnet 10.129.192.0/24 2019-11-03 20:07:41,464:DEBUG: Getting 10_129_192_0.24 data from http://ca-dhcp-loc.dom.com/subnets/10_129_192_0.24 2019-11-03 20:07:41,468:DEBUG: System's netmask: 255.255.255.0 2019-11-03 20:07:41,468:DEBUG: System's router: 10.129.192.1 2019-11-03 20:07:41,472:DEBUG: Adding to ks file: network --device 00:33:28:8e:57:7e --bootproto static --ip 10.129.192.103 --netmask 255.255.255.0 --gateway 10.129.192.1 --nameserver 206.223.27.1,206.223.27.2,10.209.76.197 --hostname srbija.dom.com 2019-11-03 20:07:41,472:DEBUG: Removing older /var/lib/cobbler/kickstarts/users-tmp/srbija.dom.com 2019-11-03 20:07:41,477:DEBUG: Add ks with network info as system's ks file 2019-11-03 20:07:41,891:DEBUG: FINISH AT : Mon, 04 Nov 2019 04:07:41
#!/import/bin/python3.5 # ------------ LOCation --------------------------------------- # Add a system (including DHCP reservation) to LOC Cobbler # see variables below! # ----------------------------------------------------- import os import argparse import logging, logging.handlers from time import gmtime, strftime import datetime from pathlib import Path import socket import netaddr import sys import subprocess import paramiko import getpass import pwd import ipaddress import re from lxml import html import requests # ------------- # variables # ------------ loc_dns = "x.x.x.x,z.z.z.z" loc_dhcp = "ca-dhcp-loc.dom.com" url_subnets = "http://" + loc_dhcp + "/subnets/" # logging destination PROGRAM = os.path.basename(sys.argv[0]) # name of this script LOG_PATH = ("/var/log/" + PROGRAM) # put together "/var/log/" if not os.path.exists(LOG_PATH): # create LOG_PATH if doesn't exists os.makedirs(LOG_PATH) os.chown(LOG_PATH,-1,10) # root(-1 means no change):some DSEE group os.chmod(LOG_PATH,0o775) # drwxrwxr-x def verify_mac(hwaddr): """ To be used as type for MAC argument """ if not netaddr.valid_mac(hwaddr): sys.exit("%s is not valid MAC" % hwaddr) return hwaddr def list_cobbler_profiles(): """ List Cobbler profiles, to be used as choices for -p """ b_profiles = subprocess.check_output(['sudo', 'cobbler', 'profile', 'list']) # returns byte object profiles = b_profiles.decode('ASCII').replace("\n", "").strip() # convert byte object into string l_profiles = profiles.split() # create list from string list_length = len(l_profiles) # number of profiles print("-----------------\n Cobbler profiles: \n-----------------") global dict_profiles dict_profiles = {} # create empty dictionary for prof in l_profiles: print(str(l_profiles.index(prof)) + ". " + prof) dict_profiles[l_profiles.index(prof)] = prof # fill directory with "number. profile" print("\n") return list_length # this will be used in argparse choices parser = argparse.ArgumentParser( formatter_class=argparse.RawTextHelpFormatter, description="Add a system (including DHCP reservation) to LOCation Cobbler", epilog='Brought to you by Zarko') parser.add_argument("-s", "--system", help="A system to be installed via Cobbler", required=True) parser.add_argument("-m", "--mac", help="System's MAC (: as delimiter)", type=verify_mac, required=True) parser.add_argument("-p", "--profile", help="List of current Cobbler profiles, select one", type=int, choices=range(list_cobbler_profiles()), required=True) parser.add_argument("-i", "--iscsi_lun", help="iSCSI SAN URI, ex.
:[protocol]:[port]:[LUN]: \ \n- ipv4 (required): iSCSI server IPv4 \ \n- protocol (optional): ignored and can be left empty \ \n- port (optional): TCP port of iSCSI target, leave empty for default port 3260 \ \n- LUN (optional): LUN number, leave empty for default value 0 \ \n- targetname (required): iSCSI target IQN") parser.add_argument("-k", "--kickstart", help="Full path to your custom file, ex. /var/lib/cobbler/kickstarts/users-tmp/ .ks") args = parser.parse_args() system=args.system mac=args.mac profile=args.profile iscsilun=args.iscsi_lun # --- Define logging LOG_FILE = (LOG_PATH + "/" + system + "_" + datetime.datetime.now().strftime("%m-%d-%Y_%Hh%Mm%Ss")) # create logger (interface that script uses for logging) logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) # create file handler (define log destination) handler = logging.handlers.TimedRotatingFileHandler(LOG_FILE, when='MIDNIGHT', backupCount=50, utc=False) # create formatter (define layout of logs) formatter = logging.Formatter('%(asctime)s:%(levelname)s: %(message)s') handler.setFormatter(formatter) # add handler to logger logger.addHandler(handler) logger.debug("") logger.debug("START AT : " + strftime("%a, %d %b %Y %H:%M:%S", gmtime())) i_am=getpass.getuser() if i_am == "root": logger.debug("Root cannot run this, please run it as yourself") sys.exit("Root cannot run this, please run it as yourself") else: logger.debug("Running by " + i_am) def dns_check(): """ Check if system is registered in DNS """ global ip # need ip for other functions try: ip = socket.gethostbyname(system) except: logger.debug("ERROR: " + system + " is not in DNS") sys.exit("ERROR: " + system + " is not in DNS") if system.lower() != socket.getfqdn(system): logger.debug(system + ". should be in DNS, please enter FQDN, thanks.") sys.exit(system + ". should be in DNS, please enter FQDN, thanks.") def iscsi_server_ip_check(): """ Check validity if IPv4 for iSCSI server """ try: if ipaddress.ip_address(iscsilun.split(":")[0]): pass except: logger.debug(iscsilun.split(":")[0] + " is not valid iSCSI server IPv4") sys.exit(iscsilun.split(":")[0] + " is not valid iSCSI server IPv4") def dhcp_reservation_linux(): """ Create DHCP reservation for Linux """ print("Please enter DSEE LDAP password.") dsee_passwd = getpass.getpass() client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(hostname=loc_dhcp, username=i_am, password=dsee_passwd) try: print("Run DHCP reservation for Linux for " + system + " " + mac) logger.debug("Run DHCP reservation for Linux for " + system + " " + mac) stdin, stdout, stderr = client.exec_command('sudo -S ca-dhcp-reserve.py -l -s %s -m %s' % (system, mac)) stdin.write(dsee_passwd +'\n') stdin.flush() if stdout.channel.recv_exit_status() != 0: logger.debug("DHCP reservation wasn't successful. " + "Is system in DNS? " + "Is system on supported subnet? ") sys.exit("DHCP reservation wasn't successful. " + "Is system in DNS? " + "Is system on supported subnet? ") stdout=stdout.readlines() # this is list #for line in stdout: # if you want to print what's in list # print(line) print(' '.join(stdout)) # print list, shorter logger.debug(' '.join(stdout)) except paramiko.ssh_exception.SSHException as ssherr: logger.debug("SSHException: {0}".format(ssherr)) sys.exit("SSHException: {0}".format(ssherr)) except paramiko.ssh_exception.ChannelException as cherr: logger.debug("paramiko.ssh_exception.ChannelException: {0}".format(cherr)) sys.exit("paramiko.ssh_exception.ChannelException: {0}".format(cherr)) except paramiko.ssh_exception.NoValidConnectionsError as err: logger.debug("NoValidConnectionsError: {0}".format(err)) sys.exit("NoValidConnectionsError: {0}".format(err)) except paramiko.ssh_exception.PartialAuthentication as parerr: logger.debug("PartialAuthentication: {0}".format(parerr)) sys.exit("PartialAuthentication: {0}".format(parerr)) close() def dhcp_reservation_ipxe_install(): """ Create DHCP reservation for iSCSI install """ print("Please enter DSEE LDAP password.") dsee_passwd = getpass.getpass() client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) client.connect(hostname=loc_dhcp, username=i_am, password=dsee_passwd) try: print("Run DHCP reservation for iSCSI install for " + system + " " + mac) logger.debug("Run DHCP reservation for iSCSI install for " + system + " " + mac) stdin, stdout, stderr = client.exec_command('sudo -S ca-dhcp-reserve.py --ipxe_install -s %s -m %s' % (system, mac)) stdin.write(dsee_passwd +'\n') stdin.flush() if stdout.channel.recv_exit_status() != 0: logger.debug("DHCP reservation wasn't successful. " + "Is system in DNS? " + "Is system on supported subnet? ") sys.exit("DHCP reservation wasn't successful. " + "Is system in DNS? " + "Is system on supported subnet? ") stdout=stdout.readlines() # this is list #for line in stdout: # if you want to print what's in list # print(line) print(' '.join(stdout)) # print list, shorter logger.debug(' '.join(stdout)) except paramiko.ssh_exception.SSHException as ssherr: logger.debug("SSHException: {0}".format(ssherr)) sys.exit("SSHException: {0}".format(ssherr)) except paramiko.ssh_exception.ChannelException as cherr: logger.debug("paramiko.ssh_exception.ChannelException: {0}".format(cherr)) sys.exit("paramiko.ssh_exception.ChannelException: {0}".format(cherr)) except paramiko.ssh_exception.NoValidConnectionsError as err: logger.debug("NoValidConnectionsError: {0}".format(err)) sys.exit("NoValidConnectionsError: {0}".format(err)) except paramiko.ssh_exception.PartialAuthentication as parerr: logger.debug("PartialAuthentication: {0}".format(parerr)) sys.exit("PartialAuthentication: {0}".format(parerr)) close() def create_tftpboot_gpxe_clients_file(): """ In Cobbler server, create file /tftpboot/gpxe/clients/ The file is used for iPXE boot from ISCSI LUN """ logger.debug("Create in Cobbler /tftpboot/gpxe/clients/" + system + " using " + iscsilun) if os.path.exists("/tftpboot/gpxe/clients/" + system): logger.debug("Removing older /tftpboot/gpxe/clients/" + system ) try: subprocess.check_output(["rm -f /tftpboot/gpxe/clients/%s" % system], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) try: f = open("/tftpboot/gpxe/clients/" + system, "w+") f.write("#!gpxe \n") f.write("dhcp || echo \"Cannot configure interface via DHCP\" \n") f.write("echo Starting " + system + " \n") f.write("echo Boot filename is ${filename} \n") f.write("sanboot iscsi:" + iscsilun + " || echo \"Command sanboot fails\" \n") f.write("boot || echo \"Cannot download and boot an executable image\" \n") f.close() except IOError as err: logger.debug("IOError: {0}".format(err)) sys.exit("IOError: {0}".format(err)) def get_profile_ks(): """ Get profile's kickstart file, The 'network' for a system will be added to this profile's ks file """ try: b_profile_ks = subprocess.check_output(["sudo cobbler profile report --name=%s | \ grep -w Kickstart | awk '{print $3}'" % dict_profiles[profile]], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) global s_profile_ks s_profile_ks = b_profile_ks.decode('ASCII').replace("\n", "").replace(":", "").strip() # convert byte into string logger.debug("Profile ks: " + s_profile_ks) def system_in_cobbler(): """ Check if system is in Cobbler? """ try: cobblersystem = subprocess.check_output(["sudo cobbler system find --name=%s" % system], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) # see below howto convert bytes (cobblersystem) into string if str(cobblersystem.strip(), 'utf-8') == system: logger.debug(system + " exists in Cobbler, remove it first with *cobbler system remove*, then run this again.") sys.exit(system + " exists in Cobbler, remove it first with *cobbler system remove*, then run this again.") logger.debug("OK: " + system + " is not in Cobbler, continue with adding it.") def add_system_to_cobbler(): """ This uses "cobbler system add" to add system to Cobbler """ print("Adding " + system + " to Cobbler with profile " + dict_profiles[profile]) logger.debug("Adding " + system + " to Cobbler with profile " + dict_profiles[profile]) this_cobbler = socket.gethostname() try: subprocess.call(['sudo cobbler system add --name=%s --mac=%s --profile=%s \ --interface=eth0 --kopts=\"ks=http://%s/cblr/svc/op/ks/system/%s\"' \ % (system, mac, dict_profiles[profile], this_cobbler, system)], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) def add_vnc_into_profile_ks(): """ Add vnc to profile ks, this is for iSCSI install, and associate this new ks to a system """ this_cobbler = socket.gethostname() logger.debug("Adding vnc to profile " + dict_profiles[profile] + " ks file, for iPXE install") try: global profile_vnc_ks profile_vnc_ks = ("/var/lib/cobbler/kickstarts/users-tmp/" + dict_profiles[profile] + "_vnc.ks") if os.path.exists(profile_vnc_ks): logger.debug("Removing old " + profile_vnc_ks) try: subprocess.check_output(["rm -f %s" % profile_vnc_ks], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) open(profile_vnc_ks, 'w').write("vnc \n" + open(s_profile_ks, 'r').read()) except IOError as err: logger.debug("IOError: {0}".format(err)) sys.exit("IOError: {0}".format(err)) logger.debug("Associating " + profile_vnc_ks + " with " + system ) try: subprocess.call(['sudo cobbler system edit --name=%s \ --kickstart=\"%s\" \ --kopts=\"ks=http://%s/cblr/svc/op/ks/system/%s\"' \ % (system, profile_vnc_ks, this_cobbler, system)], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) def create_system_ks_with_network_info(profileks): """ Add network informaiton for new system's ks file, by adding 'network' line to profile's ks file. System's netmask and gateway is calculated from DHCP server. Argument: profile's ks file """ # first access subnet's URL and get data try: logger.debug("Getting subnet data from " + url_subnets) subnet_page = requests.get(url_subnets) #print("Return status code is " + str(subnet_page.status_code) + " \n") #print("Content is " + subnet_page.text + " \n") #print("Content is " + str(subnet_page.content) + " \n") except ConnectionError as con_err: logger.debug("Connection Error: {0}".format(con_err)) # no exit in case of error except Exception as err: logger.debug("Other Error: {0}".format(err)) tree = html.fromstring(subnet_page.content) # create tree, later we use xpath to locate tree's elements #example: <a href="10_132_21_0.24"> 10_132_21_0.24 </a> # help: https://www.w3schools.com/xml/xpath_syntax.asp #subnets = tree.xpath('//td') #list of table rows #subnets = tree.xpath('//td') #list of table cells #subnets = tree.xpath('//a[@href="10_134_3_0.24"]/text()') #list of element with attribut href="10_134_3_0.24" #subnets = tree.xpath('//a') #list of a element full_list = tree.xpath('//a[@href]/text()') #list of a element with attribut href logger.debug("Collected subnet data: " + str(full_list)) # example of full_list is : # ['Name', 'Last modified', 'Size', 'Description', 'Parent Directory', '1', '10_132_21_0.24'] # need to filter only subnets, by looking that 1st character is digit global netmask global router for member in full_list: if member[0].isdigit(): if netaddr.IPAddress(ip) in netaddr.IPNetwork(member.replace(".", "/").replace("_", ".")): logger.debug(ip + " is in subnet " + member.replace(".", "/").replace("_", ".")) # we found in what subnet is system logger.debug("Getting " + member + " data from " + url_subnets + member) correctsubnet = requests.get(url_subnets + member) # now get netmask and router from subnet's config file netmaskindex = correctsubnet.text.split().index('netmask') netmask = re.sub(r"[;{},]", "", correctsubnet.text.split()[netmaskindex + 1]) # remove ;{}, logger.debug("System's netmask: " + netmask) routerindex = correctsubnet.text.split().index('routers') router = re.sub(r"[;{},]", "", correctsubnet.text.split()[routerindex + 1]) logger.debug("System's router: " + router) global netline netline = "network --device %s --bootproto static --ip %s --netmask %s --gateway %s --nameserver %s --hostname %s \n" \ % (mac, ip, netmask, router, loc_dns, system) logger.debug("Adding to ks file: " + netline) try: global system_ks system_ks = ("/var/lib/cobbler/kickstarts/users-tmp/" + system + ".ks") if os.path.exists(system_ks): logger.debug("Removing older " + system_ks) try: subprocess.check_output(["rm -f %s" % system_ks], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) # add netline to profile's ks to create new system's ks file open(system_ks, 'w').write(netline + "\n" + open(profileks, 'r').read()) except IOError as err: logger.debug("IOError: {0}".format(err)) sys.exit("IOError: {0}".format(err)) def add_ks_to_system(): """ Add ks with network info to a system, hence profile's ks is overwritten """ this_cobbler = socket.gethostname() logger.debug("Add ks with network info as system's ks file") try: subprocess.call(['sudo cobbler system edit --name=%s \ --kickstart=\"/var/lib/cobbler/kickstarts/users-tmp/%s.ks\" \ --kopts=\"ks=http://%s/cblr/svc/op/ks/system/%s\"' \ % (system, system, this_cobbler, system)], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) def add_user_provided_ks_to_system(): """ Add user provided ks to a system """ this_cobbler = socket.gethostname() logger.debug("Add user's provided ks as system's ks file") if not Path(args.kickstart).is_file(): print("WARNING: file " + args.kickstart + " doesn't exist. Never mind, a profile's ks file will be used instead.") logger.debug("WARNING: file " + args.kickstart + " doesn't exist. Never mind, a profile's ks file will be used instead.") else: try: subprocess.call(['sudo cobbler system edit --name=%s \ --kickstart=\"%s\" \ --kopts=\"ks=http://%s/cblr/svc/op/ks/system/%s\"' \ % (system, args.kickstart, this_cobbler, system)], shell=True) except subprocess.SubprocessError as err: logger.debug("SubprocessError: {0}".format(err)) sys.exit("SubprocessError: {0}".format(err)) if __name__ == '__main__': dns_check() get_profile_ks() if iscsilun: iscsi_server_ip_check() system_in_cobbler() add_system_to_cobbler() dhcp_reservation_ipxe_install() create_tftpboot_gpxe_clients_file() if args.kickstart: add_user_provided_ks_to_system() else: add_vnc_into_profile_ks() create_system_ks_with_network_info(profile_vnc_ks) add_ks_to_system() else: system_in_cobbler() add_system_to_cobbler() dhcp_reservation_linux() if args.kickstart: add_user_provided_ks_to_system() else: create_system_ks_with_network_info(s_profile_ks) add_ks_to_system() logger.debug("FINISH AT : " + strftime("%a, %d %b %Y %H:%M:%S", gmtime())) sys.exit(0)