From 650fcae97e686108592cff603ad6e95c5dc43c7d Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 11 Apr 2008 15:50:09 -0400 Subject: Now possible to override snippets on a per-profile or per-system basis, as discussed on the mailing list for usage for doing disk/package config, etc --- cobbler/action_litesync.py | 20 ++-- cobbler/action_sync.py | 259 ++++++++------------------------------------- cobbler/templar.py | 189 +++++++++++++++++++++++++++++++++ cobbler/utils.py | 75 +++++++++++-- 4 files changed, 311 insertions(+), 232 deletions(-) create mode 100644 cobbler/templar.py (limited to 'cobbler') diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index a73e7c3..bc7ffb2 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -68,13 +68,13 @@ class BootLiteSync: def remove_single_distro(self, name): bootloc = utils.tftpboot_location() # delete distro YAML file in distros/$name in webdir - self.sync.rmfile(os.path.join(self.settings.webdir, "distros", name)) + utils.rmfile(os.path.join(self.settings.webdir, "distros", name)) # delete contents of images/$name directory in webdir - self.sync.rmtree(os.path.join(self.settings.webdir, "images", name)) + utils.rmtree(os.path.join(self.settings.webdir, "images", name)) # delete contents of images/$name in tftpboot - self.sync.rmtree(os.path.join(bootloc, "images", name)) + utils.rmtree(os.path.join(bootloc, "images", name)) # delete potential symlink to tree in webdir/links - self.sync.rmfile(os.path.join(self.settings.webdir, "links", name)) + utils.rmfile(os.path.join(self.settings.webdir, "links", name)) def add_single_profile(self, name): # get the profile object: @@ -101,9 +101,9 @@ class BootLiteSync: # rebuild profile_list YAML file in webdir self.sync.write_listings() # delete profiles/$name file in webdir - self.sync.rmfile(os.path.join(self.settings.webdir, "profiles", name)) + utils.rmfile(os.path.join(self.settings.webdir, "profiles", name)) # delete contents on kickstarts/$name directory in webdir - self.sync.rmtree(os.path.join(self.settings.webdir, "kickstarts", name)) + utils.rmtree(os.path.join(self.settings.webdir, "kickstarts", name)) def update_system_netboot_status(self,name): system = self.systems.find(name=name) @@ -133,13 +133,13 @@ class BootLiteSync: # rebuild system_list file in webdir self.sync.write_listings() # delete system YAML file in systems/$name in webdir - self.sync.rmfile(os.path.join(self.settings.webdir, "systems", name)) + utils.rmfile(os.path.join(self.settings.webdir, "systems", name)) # delete contents of kickstarts_sys/$name in webdir system_record = self.systems.find(name=name) # delete any kickstart files related to this system for (name,interface) in system_record.interfaces.iteritems(): filename = utils.get_config_filename(system_record,interface=name) - self.sync.rmtree(os.path.join(self.settings.webdir, "kickstarts_sys", filename)) + utils.rmtree(os.path.join(self.settings.webdir, "kickstarts_sys", filename)) # unneeded #if not system_record.is_pxe_supported(): @@ -154,7 +154,7 @@ class BootLiteSync: if distro is not None and distro in [ "ia64", "IA64"]: itanic = True if not itanic: - self.sync.rmfile(os.path.join(bootloc, "pxelinux.cfg", filename)) + utils.rmfile(os.path.join(bootloc, "pxelinux.cfg", filename)) else: - self.sync.rmfile(os.path.join(bootloc, filename)) + utils.rmfile(os.path.join(bootloc, filename)) diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 5c8aab9..1e65e42 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -1,10 +1,9 @@ """ -Builds out a TFTP/cobbler boot tree based on the object tree. +Builds out filesystem trees/data based on the object tree. This is the code behind 'cobbler sync'. -Copyright 2006,2007, Red Hat, Inc +Copyright 2006-2008, Red Hat, Inc Michael DeHaan -Tim Verhoeven This software may be freely redistributed under the terms of the GNU general public license. @@ -22,11 +21,13 @@ import yaml # Howell-Clark version import sub_process import sys import glob +import traceback +import errno import utils from cexceptions import * -import traceback -import errno +import templar + import item_distro import item_profile @@ -55,8 +56,7 @@ class BootSync: self.systems = config.systems() self.settings = config.settings() self.repos = config.repos() - self.blend_cache = {} - self.load_snippet_cache() + self.templar = templar.Templar(config) self.bootloc = utils.tftpboot_location() def run(self): @@ -106,13 +106,13 @@ class BootSync: path = self.settings.bootloaders[loader] newname = os.path.basename(path) destpath = os.path.join(self.bootloc, newname) - self.copyfile(path, destpath) - self.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.bootloc, "menu.c32")) + utils.copyfile(path, destpath) + utils.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.bootloc, "menu.c32")) # Copy memtest to tftpboot if package is installed on system for memtest in glob.glob('/boot/memtest*'): base = os.path.basename(memtest) - self.copyfile(memtest,os.path.join(self.bootloc,"images",base)) + utils.copyfile(memtest,os.path.join(self.bootloc,"images",base)) def write_dhcp_file(self): """ @@ -221,7 +221,7 @@ class BootSync: continue metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x] - self.apply_template(template_data, metadata, settings_file) + self.templar.render(template_data, metadata, settings_file, None) def regen_ethers(self): # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date @@ -276,16 +276,16 @@ class BootSync: path = os.path.join(self.settings.webdir,x) if os.path.isfile(path): if not x.endswith(".py"): - self.rmfile(path) + utils.rmfile(path) if os.path.isdir(path): if not x in ["web", "webui", "localmirror","repo_mirror","ks_mirror","kickstarts","kickstarts_sys","distros","images","systems","profiles","links","repo_profile","repo_system"] : # delete directories that shouldn't exist - self.rmtree(path) + utils.rmtree(path) if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles","repo_profile","repo_system"]: # clean out directory contents - self.rmtree_contents(path) - self.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg")) - self.rmtree_contents(os.path.join(self.bootloc, "images")) + utils.rmtree_contents(path) + utils.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg")) + utils.rmtree_contents(os.path.join(self.bootloc, "images")) def copy_distros(self): """ @@ -306,7 +306,7 @@ class BootSync: for dirtree in [self.bootloc, self.settings.webdir]: distros = os.path.join(dirtree, "images") distro_dir = os.path.join(distros,d.name) - self.mkdir(distro_dir) + utils.mkdir(distro_dir) kernel = utils.find_kernel(d.kernel) # full path initrd = utils.find_initrd(d.initrd) # full path if kernel is None or not os.path.isfile(kernel): @@ -316,13 +316,13 @@ class BootSync: b_kernel = os.path.basename(kernel) b_initrd = os.path.basename(initrd) if kernel.startswith(dirtree): - self.linkfile(kernel, os.path.join(distro_dir, b_kernel)) + utils.linkfile(kernel, os.path.join(distro_dir, b_kernel)) else: - self.copyfile(kernel, os.path.join(distro_dir, b_kernel)) + utils.copyfile(kernel, os.path.join(distro_dir, b_kernel)) if initrd.startswith(dirtree): - self.linkfile(initrd, os.path.join(distro_dir, b_initrd)) + utils.linkfile(initrd, os.path.join(distro_dir, b_initrd)) else: - self.copyfile(initrd, os.path.join(distro_dir, b_initrd)) + utils.copyfile(initrd, os.path.join(distro_dir, b_initrd)) def validate_kickstarts(self): """ @@ -356,7 +356,7 @@ class BootSync: def validate_kickstart_for_specific_profile(self,g): distro = g.get_conceptual_parent() - meta = utils.blender(self.api, False, g, self.blend_cache) + meta = utils.blender(self.api, False, g) if distro is None: raise CX(_("profile %(profile)s references missing distro %(distro)s") % { "profile" : g.name, "distro" : g.distro }) kickstart_path = utils.find_kickstart(meta["kickstart"]) @@ -367,10 +367,10 @@ class BootSync: "kickstarts", # profile kickstarts go here g.name ) - self.mkdir(copy_path) + utils.mkdir(copy_path) dest = os.path.join(copy_path, "ks.cfg") try: - meta = utils.blender(self.api, False, g, self.blend_cache) + meta = utils.blender(self.api, False, g) ksmeta = meta["ks_meta"] del meta["ks_meta"] meta.update(ksmeta) # make available at top level @@ -379,7 +379,7 @@ class BootSync: meta["kickstart_done"] = self.generate_kickstart_signal(g, None) meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"]) kfile = open(kickstart_path) - self.apply_template(kfile, meta, dest) + self.templar.render(kfile, meta, dest, g) kfile.close() except: traceback.print_exc() # leave this in, for now... @@ -404,7 +404,7 @@ class BootSync: if system: blend_this = system - blended = utils.blender(self.api, False, blend_this, self.blend_cache) + blended = utils.blender(self.api, False, blend_this) kickstart = blended.get("kickstart",None) buf = "" @@ -436,7 +436,7 @@ class BootSync: """ buf = "" - blended = utils.blender(self.api, False, obj, self.blend_cache) + blended = utils.blender(self.api, False, obj) configs = self.get_repo_filenames(obj,is_profile) repos = self.repos @@ -479,7 +479,7 @@ class BootSync: baseurls """ - blended = utils.blender(self.api, False, obj, self.blend_cache) + blended = utils.blender(self.api, False, obj) urlseg = self.get_repo_segname(is_profile) topdir = "%s/%s/%s/*.repo" % (self.settings.webdir, urlseg, blended["name"]) @@ -503,7 +503,7 @@ class BootSync: if not is_profile: distro = distro.get_conceptual_parent() - blended = utils.blender(self.api, False, obj, self.blend_cache) + blended = utils.blender(self.api, False, obj) configs = self.get_repo_filenames(obj, is_profile) buf = "" @@ -545,14 +545,14 @@ class BootSync: if profile is None: raise CX(_("system %(system)s references missing profile %(profile)s") % { "system" : s.name, "profile" : s.profile }) distro = profile.get_conceptual_parent() - meta = utils.blender(self.api, False, s, self.blend_cache) + meta = utils.blender(self.api, False, s) kickstart_path = utils.find_kickstart(meta["kickstart"]) if kickstart_path and os.path.exists(kickstart_path): copy_path = os.path.join(self.settings.webdir, "kickstarts_sys", # system kickstarts go here s.name ) - self.mkdir(copy_path) + utils.mkdir(copy_path) dest = os.path.join(copy_path, "ks.cfg") try: ksmeta = meta["ks_meta"] @@ -563,107 +563,12 @@ class BootSync: meta["kickstart_done"] = self.generate_kickstart_signal(profile, s) meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"]) kfile = open(kickstart_path) - self.apply_template(kfile, meta, dest) + self.templar.render(kfile, meta, dest, s) kfile.close() except: traceback.print_exc() raise CX(_("Error templating file %(src)s to %(dest)s") % { "src" : meta["kickstart"], "dest" : dest }) - def load_snippet_cache(self): - - # first load all of the files in /var/lib/cobbler/snippets and load them, for use - # in adding long bits to kickstart templates without having to have them hard coded - # inside the sync code. - - snippet_cache = {} - snippets = glob.glob("%s/*" % self.settings.snippetsdir) - for snip in snippets: - if os.path.isdir(snip): - continue - snip_file = open(snip) - data = snip_file.read() - snip_file.close() - snippet_cache[os.path.basename(snip)] = data - self.snippet_cache = snippet_cache - - - def apply_template(self, data_input, metadata, out_path): - """ - Take filesystem file kickstart_input, apply metadata using - Cheetah and save as out_path. - """ - - if type(data_input) != str: - data = data_input.read() - else: - data = data_input - - # backward support for Cobbler's legacy (and slightly more readable) - # template syntax. - data = data.replace("TEMPLATE::","$") - - # replace contents of the data stream with items from the snippet cache - # do not use Cheetah yet, Cheetah can't really be run twice on the same - # stream and be expected to do the right thing - newdata = "" - for line in data.split("\n"): - for x in self.snippet_cache: - if not line.startswith("#"): - line = line.replace("SNIPPET::%s" % x, self.snippet_cache[x]) - newdata = "\n".join((newdata, line)) - data = newdata - - # HACK: the ksmeta field may contain nfs://server:/mount in which - # case this is likely WRONG for kickstart, which needs the NFS - # directive instead. Do this to make the templates work. - newdata = "" - if metadata.has_key("tree") and metadata["tree"].startswith("nfs://"): - for line in data.split("\n"): - if line.find("--url") != -1 and line.find("url ") != -1: - rest = metadata["tree"][6:] # strip off "nfs://" part - try: - (server, dir) = rest.split(":",2) - except: - raise CX(_("Invalid syntax for NFS path given during import: %s" % metadata["tree"])) - line = "nfs --server %s --dir %s" % (server,dir) - # but put the URL part back in so koan can still see - # what the original value was - line = line + "\n" + "#url --url=%s" % metadata["tree"] - newdata = newdata + line + "\n" - data = newdata - - # tell Cheetah not to blow up if it can't find a symbol for something - data = "#errorCatcher Echo\n" + data - - # now do full templating scan, where we will also templatify the snippet insertions - t = Template(source=data, searchList=[metadata]) - try: - data_out = str(t) - except: - print _("There appears to be an formatting error in the template file.") - print _("For completeness, the traceback from Cheetah has been included below.") - raise - - # now apply some magic post-filtering that is used by cobbler import and some - # other places, but doesn't use Cheetah. Forcing folks to double escape - # things would be very unwelcome. - - for x in metadata: - if type(metadata[x]) == str: - data_out = data_out.replace("@@%s@@" % x, metadata[x]) - - # remove leading newlines which apparently breaks AutoYAST ? - if data_out.startswith("\n"): - data_out = data_out.strip() - - if out_path is not None: - self.mkdir(os.path.dirname(out_path)) - fd = open(out_path, "w+") - fd.write(data_out) - fd.close() - - return data_out - def build_trees(self): """ Now that kernels and initrds are copied and kickstarts are all valid, @@ -703,7 +608,7 @@ class BootSync: and also potentially in listed in the source_repos structure of the distro object, however these files have server URLs in them that must be templated out. This function does this. """ - blended = utils.blender(self.api, False, obj, self.blend_cache) + blended = utils.blender(self.api, False, obj) if is_profile: outseg = "repos_profile" @@ -735,7 +640,7 @@ class BootSync: dispname = infile.split("/")[-1].replace(".repo","") confdir = os.path.join(self.settings.webdir, outseg) outdir = os.path.join(confdir, blended["name"]) - self.mkdir(outdir) + utils.mkdir(outdir) try: infile_h = open(infile) except: @@ -744,7 +649,7 @@ class BootSync: infile_data = infile_h.read() infile_h.close() outfile = os.path.join(outdir, "%s.repo" % (dispname)) - self.apply_template(infile_data, blended, outfile) + self.templar.render(infile_data, blended, outfile, None) def write_all_system_files(self,system,just_edit_pxe=False): @@ -788,7 +693,7 @@ class BootSync: self.write_pxe_file(f2,system,profile,distro,True) else: # ensure the file doesn't exist - self.rmfile(f2) + utils.rmfile(f2) if not just_edit_pxe: # allows netboot-disable to be highly performant @@ -834,10 +739,10 @@ class BootSync: contents = self.write_memtest_pxe("/images/%s" % base) pxe_menu_items = pxe_menu_items + contents + "\n" - # save the template. + # save the template. metadata = { "pxe_menu_items" : pxe_menu_items } outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default") - self.apply_template(template_data, metadata, outfile) + self.templar.render(template_data, metadata, outfile, None) template_src.close() def write_memtest_pxe(self,filename): @@ -845,16 +750,13 @@ class BootSync: Write a configuration file for memtest """ - # --- # just some random variables template = None metadata = {} buffer = "" - # --- template = "/etc/cobbler/pxeprofile.template" - # --- # store variables for templating metadata["menu_label"] = "MENU LABEL %s" % os.path.basename(filename) metadata["profile_name"] = os.path.basename(filename) @@ -862,15 +764,13 @@ class BootSync: metadata["initrd_path"] = "" metadata["append_line"] = "" - # --- # get the template template_fh = open(template) template_data = template_fh.read() template_fh.close() - # --- # return results - buffer = self.apply_template(template_data, metadata, None) + buffer = self.templar.render(template_data, metadata, None) return buffer @@ -902,7 +802,7 @@ class BootSync: initrd_path = os.path.join("/images",distro.name,os.path.basename(distro.initrd)) # Find the kickstart if we inherit from another profile - kickstart_path = utils.blender(self.api, True, profile, self.blend_cache)["kickstart"] + kickstart_path = utils.blender(self.api, True, profile)["kickstart"] # --- # choose a template @@ -915,12 +815,11 @@ class BootSync: # now build the kernel command line if system is not None: - blended = utils.blender(self.api, True,system,self.blend_cache) + blended = utils.blender(self.api, True, system) else: - blended = utils.blender(self.api, True,profile,self.blend_cache) + blended = utils.blender(self.api, True,profile) kopts = blended["kernel_options"] - # --- # generate the append line append_line = "append %s" % utils.hash_to_string(kopts) if not is_ia64: @@ -928,7 +827,6 @@ class BootSync: if len(append_line) >= 255 + len("append "): print _("warning: kernel option length exceeds 255") - # --- # kickstart path rewriting (get URLs for local files) if kickstart_path is not None and kickstart_path != "": @@ -945,7 +843,6 @@ class BootSync: append_line = "%s auto=true url=%s" % (append_line, kickstart_path) append_line = append_line.replace("ksdevice","interface") - # --- # store variables for templating metadata["menu_label"] = "" if not is_ia64 and system is None: @@ -955,15 +852,13 @@ class BootSync: metadata["initrd_path"] = initrd_path metadata["append_line"] = append_line - # --- # get the template template_fh = open(template) template_data = template_fh.read() template_fh.close() - # --- # save file and/or return results, depending on how called. - buffer = self.apply_template(template_data, metadata, None) + buffer = self.templar.render(template_data, metadata, None) if filename is not None: fd = open(filename, "w") fd.write(buffer) @@ -991,7 +886,7 @@ class BootSync: """ Create distro information for koan install """ - blended = utils.blender(self.api, True, distro, self.blend_cache) + blended = utils.blender(self.api, True, distro) filename = os.path.join(self.settings.webdir,"distros",distro.name) fd = open(filename, "w+") fd.write(yaml.dump(blended)) @@ -1004,7 +899,7 @@ class BootSync: NOTE: relevant to http only """ - blended = utils.blender(self.api, True, profile, self.blend_cache) + blended = utils.blender(self.api, True, profile) filename = os.path.join(self.settings.webdir,"profiles",profile.name) fd = open(filename, "w+") if blended.has_key("kickstart") and blended["kickstart"].startswith("/"): @@ -1020,73 +915,11 @@ class BootSync: NOTE: relevant to http only """ - blended = utils.blender(self.api, True, system, self.blend_cache) + blended = utils.blender(self.api, True, system) filename = os.path.join(self.settings.webdir,"systems",system.name) fd = open(filename, "w+") fd.write(yaml.dump(blended)) fd.close() - def linkfile(self, src, dst): - """ - Attempt to create a link dst that points to src. Because file - systems suck we attempt several different methods or bail to - self.copyfile() - """ - try: - return os.link(src, dst) - except (IOError, OSError): - pass - - try: - return os.symlink(src, dst) - except (IOError, OSError): - pass - - return self.copyfile(src, dst) - - def copyfile(self,src,dst): - try: - return shutil.copyfile(src,dst) - except: - if not os.path.samefile(src,dst): - # accomodate for the possibility that we already copied - # the file as a symlink/hardlink - raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst}) - - def rmfile(self,path): - try: - os.unlink(path) - return True - except OSError, ioe: - if not ioe.errno == errno.ENOENT: # doesn't exist - traceback.print_exc() - raise CX(_("Error deleting %s") % path) - return True - - def rmtree_contents(self,path): - what_to_delete = glob.glob("%s/*" % path) - for x in what_to_delete: - self.rmtree(x) - - def rmtree(self,path): - try: - if os.path.isfile(path): - return self.rmfile(path) - else: - return shutil.rmtree(path,ignore_errors=True) - except OSError, ioe: - traceback.print_exc() - if not ioe.errno == errno.ENOENT: # doesn't exist - raise CX(_("Error deleting %s") % path) - return True - - def mkdir(self,path,mode=0777): - try: - return os.makedirs(path,mode) - except OSError, oe: - if not oe.errno == 17: # already exists (no constant for 17?) - traceback.print_exc() - print oe.errno - raise CX(_("Error creating") % path) diff --git a/cobbler/templar.py b/cobbler/templar.py new file mode 100644 index 0000000..fa401b8 --- /dev/null +++ b/cobbler/templar.py @@ -0,0 +1,189 @@ +""" +Cobbler uses Cheetah templates for lots of stuff, but there's +some additional magic around that to deal with snippets/etc. +(And it's not spelled wrong!) + +Copyright 2008, Red Hat, Inc +Michael DeHaan + +This software may be freely redistributed under the terms of the GNU +general public license. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +import os +import os.path +import glob +import utils +from cexceptions import * +from Cheetah.Template import Template + +class Templar: + + def __init__(self,config): + """ + Constructor + """ + self.config = config + self.api = config.api + self.settings = config.settings() + self.cache = {} + + def render(self, data_input, search_table, out_path, subject=None): + """ + Render data_input back into a file. + data_input is either a string or a filename + search_table is a hash of metadata keys and values + out_path if not-none writes the results to a file + (though results are always returned) + subject is a profile or system object, if available (for snippet eval) + """ + + if type(data_input) != str: + raw_data = data_input.read() + else: + raw_data = data_input + + # backward support for Cobbler's legacy (and slightly more readable) + # template syntax. + raw_data = raw_data.replace("TEMPLATE::","$") + + # replace snippets with the proper Cheetah include directives prior to processing. + # see Wiki for full details on how snippets operate. + snippet_results = "" + for line in raw_data.split("\n"): + line = self.replace_snippets(line,subject) + snippet_results = "\n".join((snippet_results, line)) + raw_data = snippet_results + + # HACK: the ksmeta field may contain nfs://server:/mount in which + # case this is likely WRONG for kickstart, which needs the NFS + # directive instead. Do this to make the templates work. + newdata = "" + if search_table.has_key("tree") and search_table["tree"].startswith("nfs://"): + for line in data.split("\n"): + if line.find("--url") != -1 and line.find("url ") != -1: + rest = search_table["tree"][6:] # strip off "nfs://" part + try: + (server, dir) = rest.split(":",2) + except: + raise CX(_("Invalid syntax for NFS path given during import: %s" % search_table["tree"])) + line = "nfs --server %s --dir %s" % (server,dir) + # but put the URL part back in so koan can still see + # what the original value was + line = line + "\n" + "#url --url=%s" % search_table["tree"] + newdata = newdata + line + "\n" + raw_data = newdata + + # tell Cheetah not to blow up if it can't find a symbol for something + raw_data = "#errorCatcher Echo\n" + raw_data + + # now do full templating scan, where we will also templatify the snippet insertions + t = Template(source=raw_data, searchList=[search_table]) + try: + data_out = str(t) + except: + print _("There appears to be an formatting error in the template file.") + print _("For completeness, the traceback from Cheetah has been included below.") + raise + + # now apply some magic post-filtering that is used by cobbler import and some + # other places, but doesn't use Cheetah. Forcing folks to double escape + # things would be very unwelcome. + + for x in search_table: + if type(search_table[x]) == str: + data_out = data_out.replace("@@%s@@" % x, search_table[x]) + + # remove leading newlines which apparently breaks AutoYAST ? + if data_out.startswith("\n"): + data_out = data_out.strip() + + if out_path is not None: + utils.mkdir(os.path.dirname(out_path)) + fd = open(out_path, "w+") + fd.write(data_out) + fd.close() + + return data_out + + def replace_snippets(self,line,subject): + """ + Replace all SNIPPET:: syntaxes on a line with the + results of evaluating the snippet, taking care not + to replace tabs with spaces or anything like that + """ + tokens = line.split(None) + for t in tokens: + if t.startswith("SNIPPET::"): + snippet_name = t.replace("SNIPPET::","") + line = line.replace(t,self.eval_snippet(snippet_name,subject)) + return line + + def eval_snippet(self,name,subject): + """ + Replace SNIPPET::foo with contents of files: + Use /var/lib/cobbler/snippets/per_system/$name/$sysname + /var/lib/cobbler/snippets/per_profile/$name/$proname + /var/lib/cobbler/snippets/$name + in order... (first one wins) + """ + + sd = self.settings.snippetsdir + default_path = "%s/%s" % (sd,name) + + if subject is None: + if os.path.exists(default_path): + return self.slurp(default_path) + else: + return self.slurp(None) + + + if subject.COLLECTION_TYPE == "system": + profile = self.api.find_profile(name=subject.profile) + sys_path = "%s/per_system/%s/%s" % (sd,name,subject.name) + pro_path = "%s/per_profile/%s/%s" % (sd,name,profile.name) + if os.path.exists(sys_path): + return self.slurp(sys_path) + elif os.path.exists(pro_path): + return self.slurp(pro_path) + elif os.path.exists(default_path): + return self.slurp(default_path) + else: + return self.slurp(None) + + if subject.COLLECTION_TYPE == "profile": + pro_path = "%s/per_profile/%s/%s" % (sd,name,subject.name) + if os.path.exists(pro_path): + return self.slurp(pro_path) + elif os.path.exists(default_path): + return self.slurp(default_path) + else: + return self.slurp(None) + + return self.slurp(None) + + def slurp(self,filename): + """ + Get the contents of a filename but if none is specified + just include some generic error text for the rendered + template. + """ + + if filename is None: + return "# error: no snippet data found" + + # potentially eliminate a ton of system calls if syncing + # thousands of systems that use the same template + if self.cache.has_key(filename): + return self.cache[filename] + + fd = open(filename,"r") + data = fd.read() + self.cache[filename] = data + fd.close() + + return data diff --git a/cobbler/utils.py b/cobbler/utils.py index d8cf6fc..2d601c7 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -21,6 +21,7 @@ import sub_process import shutil import string import traceback +import errno from cexceptions import * #placeholder for translation @@ -296,19 +297,14 @@ def grab_tree(api_handle, obj): results.append(settings) return results -def blender(api_handle,remove_hashes, root_obj, blend_cache=None): +def blender(api_handle,remove_hashes, root_obj): """ Combine all of the data in an object tree from the perspective of that point on the tree, and produce a merged hash containing consolidated data. """ - cache_enabled = False # FIXME: disabled for now as there a few bugs in this impl. - blend_key = "%s/%s/%s" % (root_obj.TYPE_NAME, root_obj.name, remove_hashes) - if cache_enabled and blend_cache is not None: - if blend_cache.has_key(blend_key): - return blend_cache[blend_key] settings = api_handle.settings() tree = grab_tree(api_handle, root_obj) @@ -353,9 +349,6 @@ def blender(api_handle,remove_hashes, root_obj, blend_cache=None): # sanitize output for koan and kernel option lines, etc if remove_hashes: results = flatten(results) - - if cache_enabled and blend_cache is not None: - blend_cache[blend_key] = results return results def flatten(data): @@ -548,6 +541,70 @@ def tftpboot_location(): return "/var/lib/tftpboot" return "/tftpboot" +def linkfile(src, dst): + """ + Attempt to create a link dst that points to src. Because file + systems suck we attempt several different methods or bail to + copyfile() + """ + + try: + return os.link(src, dst) + except (IOError, OSError): + pass + + try: + return os.symlink(src, dst) + except (IOError, OSError): + pass + + return utils.copyfile(src, dst) + +def copyfile(src,dst): + try: + return shutil.copyfile(src,dst) + except: + if not os.path.samefile(src,dst): + # accomodate for the possibility that we already copied + # the file as a symlink/hardlink + raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst}) + +def rmfile(path): + try: + os.unlink(path) + return True + except OSError, ioe: + if not ioe.errno == errno.ENOENT: # doesn't exist + traceback.print_exc() + raise CX(_("Error deleting %s") % path) + return True + +def rmtree_contents(path): + what_to_delete = glob.glob("%s/*" % path) + for x in what_to_delete: + rmtree(x) + +def rmtree(path): + try: + if os.path.isfile(path): + return rmfile(path) + else: + return shutil.rmtree(path,ignore_errors=True) + except OSError, ioe: + traceback.print_exc() + if not ioe.errno == errno.ENOENT: # doesn't exist + raise CX(_("Error deleting %s") % path) + return True + +def mkdir(path,mode=0777): + try: + return os.makedirs(path,mode) + except OSError, oe: + if not oe.errno == 17: # already exists (no constant for 17?) + traceback.print_exc() + print oe.errno + raise CX(_("Error creating") % path) + if __name__ == "__main__": # print redhat_release() print tftpboot_location() -- cgit