The unified diff between revisions [1fc0ef33..] and [5b5ff777..] is displayed below. It can also be downloaded as a raw diff.

#
#
# add_file "switchconf.py"
#  content [c61bc683468cace06cad14d1f4ff8c714c776e87]
#
#   set "switchconf.py"
#  attr "mtn:execute"
# value "true"
#
============================================================
--- switchconf.py	c61bc683468cace06cad14d1f4ff8c714c776e87
+++ switchconf.py	c61bc683468cace06cad14d1f4ff8c714c776e87
@@ -0,0 +1,201 @@
+#!/usr/bin/python
+
+import getpass
+import urllib
+import time
+import sys
+import re
+import os
+
+# configurable variables
+tftp_path = '/var/tftp'
+template_path = './templates/'
+dhcp_config_file = '/etc/dhcpd.conf'
+tftp_server_address = '192.168.0.1'
+
+class ConfigLibrary:
+	def __init__(self, template_path):
+		self.template_path = template_path
+		self.update()
+	def load_module(self, module_path, module_name):
+		orig_path = sys.path
+		sys.path.insert(0, module_path)
+		mod = __import__(module_name, globals(), locals(), [''])
+		sys.path = orig_path
+		return mod
+	def get_required_attributes(self, config_file):
+		rv = []
+		for line in open(config_file):
+			l = line.strip().split('_', 1)
+			while True:
+				if len(l) != 2 or not l: break
+				chaff, interesting = l
+				m = re.match(r'^([A-Za-z0-9]+)_', interesting)
+				if m:
+					attr = m.groups()[0]
+					rv.append(attr)
+					interesting = interesting[len(attr):]
+				l = interesting.split('_', 1)
+		return rv
+	def update(self):
+		self.templates = {}
+		for template in os.listdir(template_path):
+			attrs = self.templates[template] = {}
+			config_file = os.path.join(template_path, template, 'config.txt')
+			if os.access(config_file, os.R_OK):
+				attrs['config_file'] = config_file
+			attrs['required'] = self.get_required_attributes(config_file)
+			module_path = os.path.join(template_path, template)
+			module_file = os.path.join(template_path, template, 'commands.py')
+			if os.access(module_file, os.R_OK):
+				attrs['module'] = self.load_module(module_path, 'commands')
+	def get_config(self, template_name, attrs):
+		if not self.templates.has_key(template_name): return None
+		template = self.templates[template_name]
+		data = open(template['config_file']).read()
+		for attr in attrs.keys():
+			data = data.replace('_'+attr+'_', urllib.unquote(attrs[attr]))
+		return data
+
+class HostLibrary:
+	def __init__(self, config_file=dhcp_config_file):
+		self.config_file = config_file
+		self.update()
+	def update(self):
+		def add_host(h):
+			if not h.has_key('attrs'):
+				raise Exception("Host without attributes.")
+			if not h.has_key('ethernet_addr'):
+				raise Exception("Host without ethernet address.")
+			if not h['attrs'].has_key('template'):
+				raise Exception("Host without template attributes (# template=...)")
+			self.hosts[h['ethernet_addr']] = h
+		self.hosts, in_host = {}, False
+		for line in open(self.config_file):
+			line = line.strip()
+			if in_host:
+				if line == '}':
+					in_host = False
+					add_host(host)
+				if line.startswith('#'):
+					key, val = line[2:].split('=', 1)
+					host.setdefault('attrs', {})[key] = urllib.unquote(val)
+				else:
+					m = re.match(r'hardware ethernet ([0-9a-fA-F:]+);$', line)
+					if m:
+						host['ethernet_addr'] = m.groups()[0]
+						continue
+			elif line.startswith('host switchconf_'):
+				in_host = True
+				host = {}
+
+def tail_file(fname):
+	fd = open(fname)
+	fd.seek(0, 2) # end of the file
+	while 1:
+		where = fd.tell()
+		line = fd.readline()
+		if not line:
+			time.sleep(1)
+			fd.seek(where)
+		else:
+			yield line
+
+class DHCPWatcher:
+	def __init__(self, log_file='/var/log/syslog'):
+		self.log_file = log_file
+	def watch(self):
+		# Aug 12 13:27:10 localhost dhcpd: DHCPACK on 192.168.0.2 to 00:14:6a:a1:82:40 via eth0
+		dhcpre = re.compile(r'^.*DHCPACK on (\d+\.\d+\.\d+\.\d+) to (.*) via (.*)$')
+		for line in tail_file(self.log_file):
+			m = dhcpre.match(line)
+			if m: yield m.groups()
+
+
+def daemon():
+	cl = ConfigLibrary(template_path)
+	hl = HostLibrary()
+	print "Configuration for %d hosts and %d host types loaded." % (len(hl.hosts.keys()), len(cl.templates.keys()))
+	username = raw_input("Username: ")
+	password = getpass.getpass("Password: ")
+	enable = getpass.getpass("Enable password: ")
+	print "Watching for DHCP requests."
+	for ip_address, mac_address, interface in DHCPWatcher().watch():
+		print ip_address, mac_address, interface
+		print hl.hosts.keys()
+		if hl.hosts.has_key(mac_address):
+			print "Match!", hl.hosts[mac_address]
+			template_name = hl.hosts[mac_address]['attrs']['template']
+			template = cl.templates[template_name]
+			if template.has_key('module'):
+				print "Running configuration code."
+				try:
+					template['module'].configure(hl.hosts[mac_address], ip_address, mac_address, interface, username, password, enable)
+				except:
+					print "Some exception running this code."
+					raise
+		else:
+			print "No match, this is not one of our hosts."
+
+def config():
+	class ConfigException(Exception):
+		def __init__(self, val):
+			self.args = val
+	def read_host_details():
+		print
+		print "Enter the details as prompted, and the host will be added"
+		print "to the database."
+		print
+		mac_address = raw_input('MAC address: ').strip().lower()
+		if len(mac_address) != 17 or not re.match(r'^[A-Fa-f\d:]+$', mac_address):
+			raise ConfigException('Invalid MAC address')
+		print "Select a template:"
+		templates = cl.templates.keys()
+		if len(templates) > 1:
+			for idx, item in enumerate(templates):
+				print "  %s: %s" % (idx, item)
+			idx = raw_input('Selection: ').strip()
+			idx = int(idx)
+		else: idx = 0
+		if idx < 0 or idx > len(templates) - 1:
+			raise ConfigException("Invalid selection.")
+		template_name = templates[idx]
+		template = cl.templates[template_name]
+		attrs = {'template' : template_name}
+		print "Please enter required attributes."
+		for attr in template['required']:
+			val = raw_input(attr + ': ')
+			attrs[attr] = urllib.quote(val)
+
+		# output
+		id = mac_address.replace(':', '')
+		tftp_fname = id + '.conf'
+		stanza = "\nhost switchconf_" + id + " {\n"
+		for attr in attrs: stanza += "   # %s=%s\n" % (attr, urllib.quote(attrs[attr]))
+		stanza += '   hardware ethernet ' + mac_address.lower() + ';\n'
+		stanza += '   server-name "%s";\n' % tftp_server_address
+		stanza += '   option tftp-server-name "%s";\n' % tftp_server_address
+		stanza += '   filename "' + tftp_fname + '";\n'
+		stanza += '}\n'
+		open(dhcp_config_file, 'a').write(stanza)
+		open(os.path.join(tftp_path, tftp_fname), 'w').write(cl.get_config(template_name, attrs))
+		os.system('/etc/init.d/dhcp restart')
+		print "** Restart the daemon if it is running."
+
+	cl = ConfigLibrary(template_path)
+	hl = HostLibrary()
+	while 1:
+		try:
+			host = read_host_details()
+		except ConfigException, e:
+			print e
+			continue
+
+modes = { 'daemon' : daemon, 'config' : config }
+
+if __name__ == '__main__':
+	if len(sys.argv) != 2 or not modes.has_key(sys.argv[1]):
+		sys.stderr.write("Usage: %s <%s>\n" % (sys.argv[0], '|'.join(modes.keys())))
+		sys.exit(1)
+	modes[sys.argv[1]]()
+