diff options
author | Michael DeHaan <mdehaan@redhat.com> | 2006-07-12 17:00:49 -0400 |
---|---|---|
committer | Jim Meyering <jim@meyering.net> | 2006-07-12 17:00:49 -0400 |
commit | bea30de4beb585b5c79d10c6164e65713f1611bc (patch) | |
tree | 435799b37baa464c21e42e622914cbdd8b33276f /cobbler | |
parent | f061199e4f8a3ea0e769325e72029ca7d791b206 (diff) | |
download | cobbler-bea30de4beb585b5c79d10c6164e65713f1611bc.tar.gz cobbler-bea30de4beb585b5c79d10c6164e65713f1611bc.tar.xz cobbler-bea30de4beb585b5c79d10c6164e65713f1611bc.zip |
Added templating support through new --ks_meta option which works like --kopts. The parameter is a space delimited list of key=value pairs, which allows the variables entered to be evaluated through Cheetah. Thus kickstarts are now Cheetah templates. All templating errors are ignored so usage of a $ in a template is still legal where it doesn't reference a variable. Error ignoring should be finer grained and this does need some tests. Currently this only works for kickstarts on filesystems, and I'm not sure what the behavior for http and nfs should be. Anyhow, fairly useful stuff.
Diffstat (limited to 'cobbler')
-rw-r--r-- | cobbler/action_sync.py | 135 | ||||
-rwxr-xr-x | cobbler/cobbler.py | 9 | ||||
-rw-r--r-- | cobbler/cobbler_msg.py | 5 | ||||
-rw-r--r-- | cobbler/collection.py | 8 | ||||
-rw-r--r-- | cobbler/collection_distros.py | 3 | ||||
-rw-r--r-- | cobbler/collection_profiles.py | 3 | ||||
-rw-r--r-- | cobbler/collection_systems.py | 3 | ||||
-rw-r--r-- | cobbler/item.py | 14 | ||||
-rw-r--r-- | cobbler/item_distro.py | 6 | ||||
-rw-r--r-- | cobbler/item_profile.py | 14 | ||||
-rw-r--r-- | cobbler/item_system.py | 6 |
11 files changed, 168 insertions, 38 deletions
diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 399a8494..92f84913 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -16,16 +16,16 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import os import shutil import yaml +from Cheetah.Template import Template import utils import cobbler_msg import cexceptions - -""" -Handles conversion of internal state to the tftpboot tree layout -""" - + class BootSync: + """ + Handles conversion of internal state to the tftpboot tree layout + """ def __init__(self,config): """ @@ -92,7 +92,7 @@ class BootSync: """ Delete any previously built pxelinux.cfg tree and xen tree info. """ - for x in ["pxelinux.cfg","images","systems","distros","profiles","kickstarts"]: + for x in ["pxelinux.cfg","images","systems","distros","profiles","kickstarts","kickstarts_sys"]: path = os.path.join(self.settings.tftpboot,x) self.rmtree(path) self.mkdir(path) @@ -129,23 +129,98 @@ class BootSync: (http or ftp), can stay as is. kickstarts referenced by absolute path (i.e. are files path) will be mirrored over http. """ - # ensure all referenced kickstarts exist - # these are served by either NFS, Apache, or some ftpd, so we don't need to copy them - # it's up to the user to make sure they are nicely served by their URLs + + self.validate_kickstarts_per_profile() + self.validate_kickstarts_per_system() + return True + + def validate_kickstarts_per_profile(self): + """ + Koan provisioning (Xen + auto-ks) needs kickstarts + per profile. Validate them as needed. Local kickstarts + get template substitution. Since http:// kickstarts might + get generated via magic URLs, those are *not* substituted. + NFS kickstarts are also not substituted when referenced + by NFS URL's as we don't copy those files over to the cobbler + directories. They are supposed to be live such that an + admin can update those without needing to run 'sync' again. + """ for g in self.profiles: + distro = self.distros.find(g.distro) self.sync_log(cobbler_msg.lookup("sync_mirror_ks")) kickstart_path = utils.find_kickstart(g.kickstart) if kickstart_path and os.path.exists(kickstart_path): # the input is an *actual* file, hence we have to copy it - copy_path = os.path.join(self.settings.tftpboot, "kickstarts", g.name) + copy_path = os.path.join( + self.settings.tftpboot, + "kickstarts", # profile kickstarts go here + g.name + ) self.mkdir(copy_path) dest = os.path.join(copy_path, "ks.cfg") - try: - self.copyfile(g.kickstart, dest) - except: - raise cexceptions.CobblerException("err_kickstart2") - + # FIXME -- uncomment try for now + #try: + meta = self.blend_options(False, ( + distro.ks_meta, + g.ks_meta, + )) + self.apply_template(kickstart_path, meta, dest) + #except: + #msg = "err_kickstart2" % (g.kickstart,dest) + #raise cexceptions.CobblerException(msg) + + def validate_kickstarts_per_system(self): + """ + PXE provisioning needs kickstarts evaluated per system. + Profiles would normally be sufficient, but not in cases + such as static IP, where we want to be able to do templating + on a system basis. + + FIXME: be sure PXE configs reference the new kickstarts_sys path + instead. + """ + + for s in self.systems: + profile = self.profiles.find(s.profile) + distro = self.distros.find(profile.distro) + kickstart_path = utils.find_kickstart(profile.kickstart) + if kickstart_path and os.path.exists(kickstart_path): + copy_path = os.path.join(self.settings.tftpboot, + "kickstarts_sys", # system kickstarts go here + s.name + ) + self.mkdir(copy_path) + dest = os.path.join(copy_path, "ks.cfg") + try: + meta = self.blend_options(False,( + distro.ks_meta, + profile.ks_meta, + s.ks_meta + )) + self.apply_template(kickstart_path, meta, dest) + except: + msg = "err_kickstart2" % (g.kickstart, dest) + raise cexpcetions.CobblerException(msg) + + def apply_template(self, kickstart_input, metadata, out_path): + """ + Take filesystem file kickstart_input, apply metadata using + Cheetah and save as out_path. + """ + fd = open(kickstart_input) + data = fd.read() + fd.close() + print metadata # FIXME: temporary + t = Template( + "#errorCatcher Echo\n%s" % data, + searchList=[metadata], + ) + computed = str(t) + fd = open(out_path, "w+") + fd.write(computed) + fd.close() + def build_trees(self): """ Now that kernels and initrds are copied and kickstarts are all valid, @@ -161,7 +236,7 @@ class BootSync: self.sync_log(cobbler_msg.lookup("sync_processing") % d.name) # TODO: add check to ensure all distros have profiles (=warning) filename = os.path.join(self.settings.tftpboot,"distros",d.name) - d.kernel_options = self.blend_kernel_options(( + d.kernel_options = self.blend_options(True,( self.settings.kernel_options, d.kernel_options )) @@ -174,7 +249,7 @@ class BootSync: filename = os.path.join(self.settings.tftpboot,"profiles",p.name) distro = self.distros.find(p.distro) if distro is not None: - p.kernel_options = self.blend_kernel_options(( + p.kernel_options = self.blend_options(True,( self.settings.kernel_options, distro.kernel_options, p.kernel_options @@ -230,7 +305,7 @@ class BootSync: self.tee(fd,"timeout 1\n") self.tee(fd,"label linux\n") self.tee(fd," kernel %s\n" % kernel_path) - kopts = self.blend_kernel_options(( + kopts = self.blend_options(True,( self.settings.kernel_options, profile.kernel_options, distro.kernel_options, @@ -238,10 +313,10 @@ class BootSync: )) nextline = " append %s initrd=%s" % (kopts,initrd_path) if kickstart_path is not None and kickstart_path != "": - # if kickstart path is local, we've already copied it into + # if kickstart path is on disk, we've already copied it into # the HTTP mirror, so make it something anaconda can get at if kickstart_path.startswith("/"): - kickstart_path = "http://%s/cobbler/kickstarts/%s/ks.cfg" % (self.settings.server, profile.name) + kickstart_path = "http://%s/cobbler/kickstarts_sys/%s/ks.cfg" % (self.settings.server, system.name) nextline = nextline + " ks=%s" % kickstart_path self.tee(fd, nextline) self.close_file(fd) @@ -367,15 +442,19 @@ class BootSync: else: print message - def blend_kernel_options(self, list_of_opts): + def blend_options(self, is_for_kernel, list_of_opts): """ - Given a list of kernel options, take the values used by the + Given a list of options, take the values used by the first argument in the list unless overridden by those in the second (or further on), according to --key=value formats. This is used such that we can have default kernel options in /etc and then distro, profile, and system options with various - levels of configurability. + levels of configurability overriding them. This also works + for template metadata (--ksopts) + + The output when is_for_kernel is true is a space delimited list. + When is_for_kernel is false, it's just a hash (which Cheetah requires). """ internal = {} results = [] @@ -390,17 +469,21 @@ class BootSync: internal[key_value[0]] = "" else: internal[key_value[0]] = key_value[1] - # now go back through the final list and render the single + if not is_for_kernel: + return internal + # the kernel requires a flat string for options, and we want + # to remove certain invalid options. + # go back through the final list and render the single # items AND key/value items for key in internal.keys(): data = internal[key] - if key == "ks" or key == "initrd" or key == "append": + if (key == "ks" or key == "initrd" or key == "append"): # the user REALLY doesn't want to do this... continue if data == "": results.append(key) else: results.append("%s=%s" % (key,internal[key])) - # end result is a new fragment of a kernel options string + # end result is a new fragment of an options string return " ".join(results) diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index 22cb8d31..99498823 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -128,7 +128,8 @@ class BootCLI: '--name' : lambda(a) : sys.set_name(a), '--system' : lambda(a) : sys.set_name(a), '--profile' : lambda(a) : sys.set_profile(a), - '--kopts' : lambda(a) : sys.set_kernel_options(a) + '--kopts' : lambda(a) : sys.set_kernel_options(a), + '--ksmeta' : lambda(a) : sys.set_ksmeta(a) } on_ok = lambda: self.api.systems().add(sys) return self.apply_args(args,commands,on_ok) @@ -147,7 +148,8 @@ class BootCLI: '--kopts' : lambda(a) : profile.set_kernel_options(a), '--xen-name' : lambda(a) : profile.set_xen_name(a), '--xen-file-size' : lambda(a) : profile.set_xen_file_size(a), - '--xen-ram' : lambda(a) : profile.set_xen_ram(a) + '--xen-ram' : lambda(a) : profile.set_xen_ram(a), + '--ksmeta' : lambda(a) : profile.set_ksmeta(a) # the following options are most likely not useful for profiles (yet) # primarily due to not being implemented in koan. # '--xen-mac' : lambda(a) : profile.set_xen_mac(a), @@ -167,7 +169,8 @@ class BootCLI: '--distro' : lambda(a) : distro.set_name(a), '--kernel' : lambda(a) : distro.set_kernel(a), '--initrd' : lambda(a) : distro.set_initrd(a), - '--kopts' : lambda(a) : distro.set_kernel_options(a) + '--kopts' : lambda(a) : distro.set_kernel_options(a), + '--ksmeta' : lambda(a) : distro.set_ksmeta(a) } on_ok = lambda: self.api.distros().add(distro) return self.apply_args(args,commands,on_ok) diff --git a/cobbler/cobbler_msg.py b/cobbler/cobbler_msg.py index f686f794..550cca74 100644 --- a/cobbler/cobbler_msg.py +++ b/cobbler/cobbler_msg.py @@ -15,6 +15,9 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. """ _msg_table = { + "system" : "System", + "profile" : "Profile", + "distribution" : "Distribution", "bad_server" : "The 'server' field in /var/lib/cobbler/settings must be set to something other than localhost, or kickstarting features will not work", "parse_error" : "cobbler could not read %s, replacing...", "no_create" : "cobbler could not create: %s", @@ -39,7 +42,7 @@ _msg_table = { "no_line" : "file '%s' should have a line '%s' somewhere", "no_dir2" : "can't find %s for %s as referenced in /var/lib/cobbler/settings", "bad_param" : "at least one parameter is missing for this function", - "empty_list" : "There are no configured items.", + "empty_list" : "There are no configured %s records.", "err_resolv" : "The system name (%s) did not resolve", "err_kickstart" : "The kickstart (%s) for item (%s) is not valid", "err_kickstart2" : "Error while mirroring kickstart file (%s) to (%s)", diff --git a/cobbler/collection.py b/cobbler/collection.py index ad1ed275..2525d668 100644 --- a/cobbler/collection.py +++ b/cobbler/collection.py @@ -96,7 +96,7 @@ class Collection(serializable.Serializable): if len(values) > 0: return "\n\n".join(results) else: - return cobbler_msg.lookup("empty_list") + return cobbler_msg.lookup("empty_list" % cobbler_msg.lookup(self.collection_type())) def __iter__(self): """ @@ -111,4 +111,10 @@ class Collection(serializable.Serializable): """ return len(self.listing.values()) + def collection_type(self): + """ + Returns the string key for the name of the collection (for use in messages for humans) + """ + return exceptions.NotImplementedError + diff --git a/cobbler/collection_distros.py b/cobbler/collection_distros.py index 9625b58c..a687a6bc 100644 --- a/cobbler/collection_distros.py +++ b/cobbler/collection_distros.py @@ -20,6 +20,9 @@ import cexceptions class Distros(collection.Collection): + def collection_type(self): + return "distribution" + def factory_produce(self,config,seed_data): """ Return a Distro forged from seed_data diff --git a/cobbler/collection_profiles.py b/cobbler/collection_profiles.py index 152e1f8c..93fb1c75 100644 --- a/cobbler/collection_profiles.py +++ b/cobbler/collection_profiles.py @@ -24,6 +24,9 @@ import cexceptions class Profiles(collection.Collection): + def collection_type(self): + return "profile" + def factory_produce(self,config,seed_data): return profile.Profile(config).from_datastruct(seed_data) diff --git a/cobbler/collection_systems.py b/cobbler/collection_systems.py index 39b31190..368c3f64 100644 --- a/cobbler/collection_systems.py +++ b/cobbler/collection_systems.py @@ -22,6 +22,9 @@ import cexceptions class Systems(collection.Collection): + def collection_type(self): + return "system" + def factory_produce(self,config,seed_data): """ Return a system forged from seed_data diff --git a/cobbler/item.py b/cobbler/item.py index 9e8a5ba2..6c55225c 100644 --- a/cobbler/item.py +++ b/cobbler/item.py @@ -34,6 +34,20 @@ class Item(serializable.Serializable): self.kernel_options = options_string return True + def set_ksmeta(self,options_string): + """ + A comma delimited list of key value pairs, like 'a=b,c=d,e=f'. + The meta tags are used as input to the Cheetah templating system + to preprocess kickstart files + """ + self.ks_meta = options_string + tokens = self.ks_meta.split(",") + for t in tokens: + tokens2 = t.split("=") + if len(tokens2) != 2: + return False + return True + def to_datastruct(self): """ Returns an easily-marshalable representation of the collection. diff --git a/cobbler/item_distro.py b/cobbler/item_distro.py index 7491a98e..e15c2b1c 100644 --- a/cobbler/item_distro.py +++ b/cobbler/item_distro.py @@ -37,6 +37,7 @@ class Distro(item.Item): self.kernel = None self.initrd = None self.kernel_options = "" + self.ks_meta = "" def from_datastruct(self,seed_data): """ @@ -46,6 +47,7 @@ class Distro(item.Item): self.kernel = seed_data['kernel'] self.initrd = seed_data['initrd'] self.kernel_options = seed_data['kernel_options'] + self.ks_meta = seed_data['ks_meta'] return self def set_kernel(self,kernel): @@ -88,7 +90,8 @@ class Distro(item.Item): 'name': self.name, 'kernel': self.kernel, 'initrd' : self.initrd, - 'kernel_options' : self.kernel_options + 'kernel_options' : self.kernel_options, + 'ks_meta' : self.ks_meta } def printable(self, id): @@ -109,5 +112,6 @@ class Distro(item.Item): buf = buf + "kernel : %s\n" % kstr buf = buf + "initrd : %s\n" % istr buf = buf + "kernel options : %s\n" % self.kernel_options + buf = buf + "ks metadata : %s\n" % self.ks_meta return buf diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py index 46c79294..e3cecfec 100644 --- a/cobbler/item_profile.py +++ b/cobbler/item_profile.py @@ -33,11 +33,12 @@ class Profile(item.Item): self.distro = None # a name, not a reference self.kickstart = None self.kernel_options = '' + self.ks_meta = '' self.xen_name = 'xenguest' - self.xen_file_size = 5 # GB - self.xen_ram = 2048 # MB - self.xen_mac = '' - self.xen_paravirt = True + self.xen_file_size = 5 # GB. 5 = Decent _minimum_ default for FC5. + self.xen_ram = 512 # MB. Install with 256 not likely to pass + self.xen_mac = '' # allow random generation as default + self.xen_paravirt = True # hvm support is *NOT* in Koan (now) def from_datastruct(self,seed_data): """ @@ -47,6 +48,7 @@ class Profile(item.Item): self.distro = seed_data['distro'] self.kickstart = seed_data['kickstart'] self.kernel_options = seed_data['kernel_options'] + self.ks_meta = seed_data['ks_meta'] self.xen_name = seed_data['xen_name'] if not self.xen_name or self.xen_name == '': self.xen_name = self.name @@ -176,7 +178,8 @@ class Profile(item.Item): 'xen_file_size' : self.xen_file_size, 'xen_ram' : self.xen_ram, 'xen_mac' : self.xen_mac, - 'xen_paravirt' : self.xen_paravirt + 'xen_paravirt' : self.xen_paravirt, + 'ks_meta' : self.ks_meta } def printable(self,id): @@ -187,6 +190,7 @@ class Profile(item.Item): buf = buf + "distro : %s\n" % self.distro buf = buf + "kickstart : %s\n" % self.kickstart buf = buf + "kernel options : %s\n" % self.kernel_options + buf = buf + "ks metadata : %s\n" % self.ks_meta buf = buf + "xen name : %s\n" % self.xen_name buf = buf + "xen file size : %s\n" % self.xen_file_size buf = buf + "xen ram : %s\n" % self.xen_ram diff --git a/cobbler/item_system.py b/cobbler/item_system.py index ab4b5203..68ba1564 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -26,11 +26,13 @@ class System(item.Item): self.name = None self.profile = None # a name, not a reference self.kernel_options = "" + self.ks_meta = "" def from_datastruct(self,seed_data): self.name = seed_data['name'] self.profile = seed_data['profile'] self.kernel_options = seed_data['kernel_options'] + self.ks_meta = seed_data['ks_meta'] return self def set_name(self,name): @@ -69,12 +71,14 @@ class System(item.Item): return { 'name' : self.name, 'profile' : self.profile, - 'kernel_options' : self.kernel_options + 'kernel_options' : self.kernel_options, + 'ks_meta' : self.ks_meta } def printable(self,id): buf = "system %-4s : %s\n" % (id, self.name) buf = buf + "profile : %s\n" % self.profile buf = buf + "kernel options : %s" % self.kernel_options + buf = buf + "ks metadata : %s" % self.ks_meta return buf |