summaryrefslogtreecommitdiffstats
path: root/cobbler
diff options
context:
space:
mode:
authorMichael DeHaan <mdehaan@redhat.com>2008-04-11 15:50:09 -0400
committerMichael DeHaan <mdehaan@redhat.com>2008-04-11 15:50:09 -0400
commit650fcae97e686108592cff603ad6e95c5dc43c7d (patch)
tree2f9bb151cfd12d83774799d21ec4deb6f5fc8898 /cobbler
parent5da91f89c12b998a4e83db4d21ec6086aca37feb (diff)
downloadthird_party-cobbler-650fcae97e686108592cff603ad6e95c5dc43c7d.tar.gz
third_party-cobbler-650fcae97e686108592cff603ad6e95c5dc43c7d.tar.xz
third_party-cobbler-650fcae97e686108592cff603ad6e95c5dc43c7d.zip
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
Diffstat (limited to 'cobbler')
-rw-r--r--cobbler/action_litesync.py20
-rw-r--r--cobbler/action_sync.py259
-rw-r--r--cobbler/templar.py189
-rw-r--r--cobbler/utils.py75
4 files changed, 311 insertions, 232 deletions
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 <mdehaan@redhat.com>
-Tim Verhoeven <tim.verhoeven.be@gmail.com>
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 <mdehaan@redhat.com>
+
+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()