summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael DeHaan <mdehaan@mdehaan.rdu.redhat.com>2007-06-13 15:59:47 -0400
committerMichael DeHaan <mdehaan@mdehaan.rdu.redhat.com>2007-06-13 15:59:47 -0400
commit32908858c032f91726b02520d11a16d6cff2e93a (patch)
treed914d43b6231db3dc3755690fe1fca08223caee7
parent72e9e1de6c3bc096180c62734476f775cdebfbee (diff)
downloadcobbler-32908858c032f91726b02520d11a16d6cff2e93a.tar.gz
cobbler-32908858c032f91726b02520d11a16d6cff2e93a.tar.xz
cobbler-32908858c032f91726b02520d11a16d6cff2e93a.zip
Lots of work towards profile inheritance. This works in the UI now, with
some rough edges (like listing the tree). cobbler profile add --name=profile2 --inherit=profile1 --otherparameters=... cobbler profile edit --name=profile2 --stillmoreparamters=... Data is interleaved for hashes, combined for arrays, and overriden for scalar values. This was heavily inspired by Will-It-Blend, and in this implementation it all blends. Implementation notes -- Updating a parent profile doesn't apply changes to the child objects until a sync, so this seems like a good upgrade for a future commit. Also, the children mapping that makes this possible needs some tweaks because they may load out of order, in which case "cobbler list" can't render a full tree. There are various approaches to deal with this and it should be a (relatively) easy change.
-rw-r--r--TODO2
-rw-r--r--cobbler/api.py16
-rwxr-xr-xcobbler/cobbler.py54
-rw-r--r--cobbler/collection.py4
-rw-r--r--cobbler/config.py16
-rw-r--r--cobbler/item.py37
-rw-r--r--cobbler/item_distro.py34
-rw-r--r--cobbler/item_profile.py72
-rw-r--r--cobbler/item_repo.py25
-rw-r--r--cobbler/item_system.py32
-rw-r--r--cobbler/serializer.py1
-rw-r--r--cobbler/utils.py34
12 files changed, 231 insertions, 96 deletions
diff --git a/TODO b/TODO
index 751b7dd9..20a4a1e1 100644
--- a/TODO
+++ b/TODO
@@ -14,4 +14,4 @@ cobbler TODO list.
- build net-install CD images
- build non-net-install CD from cobbler profile
- have pre and post triggers, check return codes and validate
-
+- make is_valid throw exceptions that explains exactly what is wrong.
diff --git a/cobbler/api.py b/cobbler/api.py
index fe35751e..58f1046b 100644
--- a/cobbler/api.py
+++ b/cobbler/api.py
@@ -80,30 +80,30 @@ class BootAPI:
"""
return self._config.settings()
- def new_system(self):
+ def new_system(self,is_subobject=False):
"""
Return a blank, unconfigured system, unattached to a collection
"""
- return self._config.new_system()
+ return self._config.new_system(is_subobject=is_subobject)
- def new_distro(self):
+ def new_distro(self,is_subobject=False):
"""
Create a blank, unconfigured distro, unattached to a collection.
"""
- return self._config.new_distro()
+ return self._config.new_distro(is_subobject=is_subobject)
- def new_profile(self):
+ def new_profile(self,is_subobject=False):
"""
Create a blank, unconfigured profile, unattached to a collection
"""
- return self._config.new_profile()
+ return self._config.new_profile(is_subobject=is_subobject)
- def new_repo(self):
+ def new_repo(self,is_subobject=False):
"""
Create a blank, unconfigured repo, unattached to a collection
"""
- return self._config.new_repo()
+ return self._config.new_repo(is_subobject=is_subobject)
def check(self):
"""
diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py
index 7d88ccbd..fb34871c 100755
--- a/cobbler/cobbler.py
+++ b/cobbler/cobbler.py
@@ -112,7 +112,7 @@ class BootCLI:
Run the command line and return system exit code
"""
self.api.deserialize()
- self.curry_args(self.args[1:], self.commands['toplevel'])
+ self.relay_args(self.args[1:], self.commands['toplevel'])
def usage(self,args):
"""
@@ -173,8 +173,7 @@ class BootCLI:
# LISTING FUNCTIONS
def list(self,args):
- collection = self.api.distros()
- self.__tree(collection,0)
+ self.__tree(self.api.distros(),0)
self.__tree(self.api.repos(),0)
def __tree(self,collection,level):
@@ -262,8 +261,8 @@ class BootCLI:
######################################################################
# BASIC FRAMEWORK
- def __generic_add(self,args,new_fn,control_fn):
- obj = new_fn()
+ def __generic_add(self,args,new_fn,control_fn,does_inherit):
+ obj = new_fn(is_subobject=does_inherit)
control_fn(args,obj)
def __generic_edit(self,args,collection_fn,control_fn,exc_msg):
@@ -379,18 +378,42 @@ class BootCLI:
#####################################################################
# ADD FUNCTIONS
-
+
+ def __prescan_for_inheritance_args(self,args):
+ """
+ Normally we just feed all the arguments through to the functions
+ in question, but here, we need to send a special flag to the foo_add
+ functions if we are creating a subobject, because that needs to affect
+ what function calls we make. So, this checks to see if the user
+ is creating a subobject by looking for --inherit in the arguments list,
+ before we actually parse the --inherit arg. Complicated :)
+ """
+ for x in args:
+ try:
+ key, value = x.split("=",1)
+ value = value.replace('"','').replace("'",'')
+ if key == "--inherit":
+ return True
+ except:
+ traceback.print_exc() # FIXME: remove
+ pass
+ return False
+
def distro_add(self,args):
- self.__generic_add(args,self.api.new_distro,self.__distro_control)
+ does_inherit = self.__prescan_for_inheritance_args(args)
+ self.__generic_add(args,self.api.new_distro,self.__distro_control,does_inherit)
def profile_add(self,args):
- self.__generic_add(args,self.api.new_profile,self.__profile_control)
+ does_inherit = self.__prescan_for_inheritance_args(args)
+ self.__generic_add(args,self.api.new_profile,self.__profile_control,does_inherit)
def system_add(self,args):
- self.__generic_add(args,self.api.new_system,self.__system_control)
+ does_inherit = self.__prescan_for_inheritance_args(args)
+ self.__generic_add(args,self.api.new_system,self.__system_control,does_inherit)
def repo_add(self,args):
- self.__generic_add(args,self.api.new_repo,self.__repo_control)
+ does_inherit = self.__prescan_for_inheritance_args(args)
+ self.__generic_add(args,self.api.new_repo,self.__repo_control,does_inherit)
###############################################################
@@ -402,6 +425,7 @@ class BootCLI:
"""
commands = {
'--name' : lambda(a) : profile.set_name(a),
+ '--inherit' : lambda(a) : profile.set_parent(a),
'--newname' : lambda(a) : True,
'--profile' : lambda(a) : profile.set_name(a),
'--distro' : lambda(a) : profile.set_distro(a),
@@ -507,7 +531,7 @@ class BootCLI:
on_ok()
self.api.serialize()
- def curry_args(self, args, commands):
+ def relay_args(self, args, commands):
"""
Lookup command args[0] in the dispatch table and
feed it the remaining args[1:-1] as arguments.
@@ -652,25 +676,25 @@ class BootCLI:
"""
Handles any of the 'cobbler distro' subcommands
"""
- return self.curry_args(args, self.commands['distro'])
+ return self.relay_args(args, self.commands['distro'])
def profile(self,args):
"""
Handles any of the 'cobbler profile' subcommands
"""
- return self.curry_args(args, self.commands['profile'])
+ return self.relay_args(args, self.commands['profile'])
def system(self,args):
"""
Handles any of the 'cobbler system' subcommands
"""
- return self.curry_args(args, self.commands['system'])
+ return self.relay_args(args, self.commands['system'])
def repo(self,args):
"""
Handles any of the 'cobbler repo' subcommands
"""
- return self.curry_args(args, self.commands['repo'])
+ return self.relay_args(args, self.commands['repo'])
####################################################
diff --git a/cobbler/collection.py b/cobbler/collection.py
index a6a39915..43ad068e 100644
--- a/cobbler/collection.py
+++ b/cobbler/collection.py
@@ -97,7 +97,8 @@ class Collection(serializable.Serializable):
"""
if ref is None or not ref.is_valid():
- raise CX(_("invalid parameter"))
+ raise CX(_("insufficient or invalid arguments supplied"))
+ print "DEBUG: adding object %s" % ref.name
if not with_copy:
# don't need to run triggers, so add it already ...
self.listing[ref.name.lower()] = ref
@@ -126,7 +127,6 @@ class Collection(serializable.Serializable):
parent = ref.get_parent()
if parent != None:
parent.children[ref.name] = ref
-
return True
def _run_triggers(self,ref,globber):
diff --git a/cobbler/config.py b/cobbler/config.py
index 92bd9b5d..cd19d008 100644
--- a/cobbler/config.py
+++ b/cobbler/config.py
@@ -100,29 +100,29 @@ class Config:
"""
return self._repos
- def new_distro(self):
+ def new_distro(self,is_subobject=False):
"""
Create a new distro object with a backreference to this object
"""
- return distro.Distro(weakref.proxy(self))
+ return distro.Distro(weakref.proxy(self),is_subobject=is_subobject)
- def new_system(self):
+ def new_system(self,is_subobject=False):
"""
Create a new system with a backreference to this object
"""
- return system.System(weakref.proxy(self))
+ return system.System(weakref.proxy(self),is_subobject=is_subobject)
- def new_profile(self):
+ def new_profile(self,is_subobject=False):
"""
Create a new profile with a backreference to this object
"""
- return profile.Profile(weakref.proxy(self))
+ return profile.Profile(weakref.proxy(self),is_subobject=is_subobject)
- def new_repo(self):
+ def new_repo(self,is_subobject=False):
"""
Create a new mirror to keep track of...
"""
- return repo.Repo(weakref.proxy(self))
+ return repo.Repo(weakref.proxy(self),is_subobject=is_subobject)
def clear(self):
"""
diff --git a/cobbler/item.py b/cobbler/item.py
index af46defa..db2bef4e 100644
--- a/cobbler/item.py
+++ b/cobbler/item.py
@@ -22,15 +22,35 @@ class Item(serializable.Serializable):
TYPE_NAME = "generic"
- def __init__(self,config):
+ def __init__(self,config,is_subobject=False):
"""
Constructor. Requires a back reference to the Config management object.
+
+ NOTE: is_subobject is used for objects that allow inheritance in their trees. This
+ inheritance refers to conceptual inheritance, not Python inheritance. Objects created
+ with is_subobject need to call their set_parent() method immediately after creation
+ and pass in a value of an object of the same type. Currently this is only supported
+ for profiles. Subobjects blend their data with their parent objects and only require
+ a valid parent name and a name for themselves, so other required options can be
+ gathered from items further up the cobbler tree.
+
+ Old cobbler: New cobbler:
+ distro distro
+ profile profile
+ system profile <-- created with is_subobject=True
+ system <-- created as normal
+
+ For consistancy, there is some code supporting this in all object types, though it is only usable
+ (and only should be used) for profiles at this time. Objects that are children of
+ objects of the same type (i.e. subprofiles) need to pass this in as True. Otherwise, just
+ use False for is_subobject and the parent object will (therefore) have a different type.
+
"""
self.config = config
self.settings = self.config._settings
- self.clear()
- self.children = {} # caching for performance reasons, not serialized
- self.conceptual_parent = None # " "
+ self.clear(is_subobject) # reset behavior differs for inheritance cases
+ self.parent = None # all objects by default are not subobjects
+ self.children = {} # caching for performance reasons, not serialized
def clear(self):
raise exceptions.NotImplementedError
@@ -72,9 +92,6 @@ class Item(serializable.Serializable):
subprofile. Get the first parent of a different type.
"""
- if self.conceptual_parent is not None:
- return self.conceptual_parent
-
# FIXME: this is a workaround to get the type of an instance var
# what's a more clean way to do this that's python 2.3 friendly?
# this returns something like: cobbler.item_system.System
@@ -85,7 +102,7 @@ class Item(serializable.Serializable):
if mtype != ptype:
self.conceptual_parent = parent
return parent
-
+ parent = parent.get_parent()
return None
def set_name(self,name):
@@ -93,6 +110,8 @@ class Item(serializable.Serializable):
All objects have names, and with the exception of System
they aren't picky about it.
"""
+ if self.name not in ["",None] and self.parent not in ["",None] and self.name == self.parent:
+ raise CX(_("self parentage is weird"))
self.name = name
return True
@@ -125,7 +144,7 @@ class Item(serializable.Serializable):
"""
Used in subclass from_datastruct functions to load items from
a hash. Intented to ease backwards compatibility of config
- files during upgrades.
+ files during upgrades.
"""
if datastruct.has_key(key):
return datastruct[key]
diff --git a/cobbler/item_distro.py b/cobbler/item_distro.py
index 02f2eab4..b6a5bea8 100644
--- a/cobbler/item_distro.py
+++ b/cobbler/item_distro.py
@@ -26,18 +26,18 @@ class Distro(item.Item):
TYPE_NAME = _("distro")
- def clear(self):
+ def clear(self,is_subobject=False):
"""
Reset this object.
"""
- self.name = None
- self.kernel = None
- self.initrd = None
- self.kernel_options = {}
- self.ks_meta = {}
- self.arch = "x86"
- self.breed = "redhat"
- self.source_repos = []
+ self.name = None
+ self.kernel = (None, '<<inherit>>')[is_subobject]
+ self.initrd = (None, '<<inherit>>')[is_subobject]
+ self.kernel_options = ({}, '<<inherit>>')[is_subobject]
+ self.ks_meta = ({}, '<<inherit>>')[is_subobject]
+ self.arch = ('x86', '<<inherit>>')[is_subobject]
+ self.breed = ('redhat', '<<inherit>>')[is_subobject]
+ self.source_repos = ([], '<<inherit>>')[is_subobject]
def make_clone(self):
ds = self.to_datastruct()
@@ -48,13 +48,19 @@ class Distro(item.Item):
def get_parent(self):
"""
Return object next highest up the tree.
+ NOTE: conceptually there is no need for subdistros, but it's implemented
+ anyway for testing purposes
"""
- return None
+ if self.parent is None or self.parent == '':
+ return None
+ else:
+ return self.config.distros().find(self.parent)
def from_datastruct(self,seed_data):
"""
Modify this object to take on values in seed_data
"""
+ self.parent = self.load_item(seed_data,'parent')
self.name = self.load_item(seed_data,'name')
self.kernel = self.load_item(seed_data,'kernel')
self.initrd = self.load_item(seed_data,'initrd')
@@ -135,8 +141,11 @@ class Distro(item.Item):
A distro requires that the kernel and initrd be set. All
other variables are optional.
"""
+ # NOTE: this code does not support inheritable distros at this time.
+ # this is by design because inheritable distros do not make sense.
for x in (self.name,self.kernel,self.initrd):
- if x is None: return False
+ if x is None:
+ return False
return True
def to_datastruct(self):
@@ -151,7 +160,8 @@ class Distro(item.Item):
'ks_meta' : self.ks_meta,
'arch' : self.arch,
'breed' : self.breed,
- 'source_repos' : self.source_repos
+ 'source_repos' : self.source_repos,
+ 'parent' : self.parent
}
def printable(self):
diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py
index 8be30e95..79a7dcb3 100644
--- a/cobbler/item_profile.py
+++ b/cobbler/item_profile.py
@@ -28,24 +28,25 @@ class Profile(item.Item):
cloned.from_datastruct(ds)
return cloned
- def clear(self):
+ def clear(self,is_subobject=False):
"""
Reset this object.
"""
- self.name = None
- self.distro = None # a name, not a reference
- self.kickstart = self.settings.default_kickstart
- self.kernel_options = {}
- self.ks_meta = {}
- self.virt_file_size = 5 # GB. 5 = Decent _minimum_ default for FC5.
- self.virt_ram = 512 # MB. Install with 256 not likely to pass
- self.repos = "" # names of cobbler repo definitions
+ self.name = None
+ self.distro = (None, '<<inherit>>')[is_subobject]
+ self.kickstart = (self.settings.default_kickstart, '<<inherit>>')[is_subobject]
+ self.kernel_options = ({}, '<<inherit>>')[is_subobject]
+ self.ks_meta = ({}, '<<inherit>>')[is_subobject]
+ self.virt_file_size = (5, '<<inherit>>')[is_subobject]
+ self.virt_ram = (512, '<<inherit>>')[is_subobject]
+ self.repos = ("", '<<inherit>>')[is_subobject]
def from_datastruct(self,seed_data):
"""
Load this object's properties based on seed_data
"""
+ self.parent = self.load_item(seed_data,'parent')
self.name = self.load_item(seed_data,'name')
self.distro = self.load_item(seed_data,'distro')
self.kickstart = self.load_item(seed_data,'kickstart')
@@ -62,13 +63,33 @@ class Profile(item.Item):
self.virt_file_size = self.load_item(seed_data,'virt_file_size')
# backwards compatibility -- convert string entries to dicts for storage
- if type(self.kernel_options) != dict:
+ if self.kernel_options != "<<inherit>>" and type(self.kernel_options) != dict:
self.set_kernel_options(self.kernel_options)
- if type(self.ks_meta) != dict:
+ if self.ks_meta != "<<inherit>>" and type(self.ks_meta) != dict:
self.set_ksmeta(self.ks_meta)
return self
+ def set_parent(self,parent_name):
+ """
+ Instead of a --distro, set the parent of this object to another profile
+ and use the values from the parent instead of this one where the values
+ for this profile aren't filled in, and blend them together where they
+ are hashes. Basically this enables profile inheritance. To use this,
+ the object MUST have been constructed with is_subobject=True or the
+ default values for everything will be screwed up and this will likely NOT
+ work. So, API users -- make sure you pass is_subobject=True into the
+ constructor when using this.
+ """
+ if parent_name == self.name:
+ # check must be done in two places as set_parent could be called before/after
+ # set_name...
+ raise CX(_("self parentage is weird"))
+ found = self.config.profiles().find(parent_name)
+ if found is None:
+ raise CX(_("profile %s not found, inheritance not possible") % parent_name)
+ self.parent = parent_name
+
def set_distro(self,distro_name):
"""
Sets the distro. This must be the name of an existing
@@ -80,6 +101,10 @@ class Profile(item.Item):
raise CX(_("distribution not found"))
def set_repos(self,repos):
+ if repos == "<<inherit>>":
+ self.repos = "<<inherit>>"
+ return
+
if type(repos) != list:
# allow backwards compatibility support of string input
repolist = repos.split(None)
@@ -152,7 +177,11 @@ class Profile(item.Item):
"""
Return object next highest up the tree.
"""
- return self.config.distros().find(self.distro)
+ if self.parent is None or self.parent == '':
+ result = self.config.distros().find(self.distro)
+ else:
+ result = self.config.profiles().find(self.parent)
+ return result
def is_valid(self):
"""
@@ -160,9 +189,19 @@ class Profile(item.Item):
as well as Virt info, are optional. (Though I would say provisioning
without a kickstart is *usually* not a good idea).
"""
- for x in (self.name, self.distro):
- if x is None:
- return False
+ if self.parent is None or self.parent == '':
+ # all values must be filled in if not inheriting from another profile
+ if self.name is None:
+ raise CX(_("no name specified"))
+ if self.distro is None:
+ raise CX(_("no distro specified"))
+ else:
+ # if inheriting, specifying distro is not allowed, and
+ # name is required, but there are no other rules.
+ if self.name is None:
+ raise CX(_("no name specified"))
+ if self.distro != "<<inherit>>":
+ raise CX(_("cannot override distro when inheriting a profile"))
return True
def to_datastruct(self):
@@ -177,7 +216,8 @@ class Profile(item.Item):
'virt_file_size' : self.virt_file_size,
'virt_ram' : self.virt_ram,
'ks_meta' : self.ks_meta,
- 'repos' : self.repos
+ 'repos' : self.repos,
+ 'parent' : self.parent
}
def printable(self):
diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py
index b7832644..c9846b1d 100644
--- a/cobbler/item_repo.py
+++ b/cobbler/item_repo.py
@@ -27,15 +27,17 @@ class Repo(item.Item):
cloned.from_datastruct(ds)
return cloned
- def clear(self):
- self.name = None # is required
- self.mirror = None # is required
- self.keep_updated = 1 # has reasonable defaults
- self.local_filename = "" # off by default
- self.rpm_list = "" # just get selected RPMs + deps
- self.createrepo_flags = "-c cache" # none by default
-
+ def clear(self,is_subobject=False):
+ self.parent = None
+ self.name = None
+ self.mirror = (None, '<<inherit>>')[is_subobject]
+ self.keep_updated = (None, '<<inherit>>')[is_subobject]
+ self.local_filename = ("", '<<inherit>>')[is_subobject]
+ self.rpm_list = ("", '<<inherit>>')[is_subobject]
+ self.createrepo_flags = ("-c cache", '<<inherit>>')[is_subobject]
+
def from_datastruct(self,seed_data):
+ self.parent = self.load_item(seed_data, 'parent')
self.name = self.load_item(seed_data, 'name')
self.mirror = self.load_item(seed_data, 'mirror')
self.keep_updated = self.load_item(seed_data, 'keep_updated')
@@ -127,7 +129,8 @@ class Repo(item.Item):
'keep_updated' : self.keep_updated,
'local_filename' : self.local_filename,
'rpm_list' : self.rpm_list,
- 'createrepo_flags' : self.createrepo_flags
+ 'createrepo_flags' : self.createrepo_flags,
+ 'parent' : self.parent
}
def printable(self):
@@ -140,6 +143,10 @@ class Repo(item.Item):
return buf
def get_parent(self):
+ """
+ currently the Cobbler object space does not support subobjects of this object
+ as it is conceptually not useful.
+ """
return None
def is_rsync_mirror(self):
diff --git a/cobbler/item_system.py b/cobbler/item_system.py
index 9de575e4..c2e82438 100644
--- a/cobbler/item_system.py
+++ b/cobbler/item_system.py
@@ -28,17 +28,21 @@ class System(item.Item):
cloned.from_datastruct(ds)
return cloned
- def clear(self):
- self.name = None
- self.profile = None # a name, not a reference
- self.kernel_options = {}
- self.ks_meta = {}
- self.ip_address = "" # bad naming here, to the UI, this is usually 'ip-address'
- self.mac_address = ""
- self.netboot_enabled = 1
- self.hostname = ""
+ def clear(self,is_subobject=False):
+ # names of cobbler repo definitions
+
+ self.name = None
+ self.profile = (None, '<<inherit>>')[is_subobject]
+ self.kernel_options = ({}, '<<inherit>>')[is_subobject]
+ self.ks_meta = ({}, '<<inherit>>')[is_subobject]
+ self.ip_address = ("", '<<inherit>>')[is_subobject]
+ self.mac_address = ("", '<<inherit>>')[is_subobject]
+ self.netboot_enabled = (1, '<<inherit>>')[is_subobject]
+ self.hostname = ("", '<<inheirt>>')[is_subobject]
def from_datastruct(self,seed_data):
+
+ self.parent = self.load_item(seed_data, 'parent')
self.name = self.load_item(seed_data, 'name')
self.profile = self.load_item(seed_data, 'profile')
self.kernel_options = self.load_item(seed_data, 'kernel_options')
@@ -82,7 +86,10 @@ class System(item.Item):
"""
Return object next highest up the tree.
"""
- return self.config.profiles().find(self.profile)
+ if self.parent is None or self.parent == '':
+ return self.config.profiles().find(self.profile)
+ else:
+ return self.config.systems().find(self.parent)
def set_name(self,name):
"""
@@ -198,6 +205,8 @@ class System(item.Item):
"""
A system is valid when it contains a valid name and a profile.
"""
+ # NOTE: this validation code does not support inheritable distros at this time.
+ # this is by design as inheritable systems don't make sense.
if self.name is None:
return False
if self.profile is None:
@@ -213,7 +222,8 @@ class System(item.Item):
'ip_address' : self.ip_address,
'netboot_enabled' : self.netboot_enabled,
'hostname' : self.hostname,
- 'mac_address' : self.mac_address
+ 'mac_address' : self.mac_address,
+ 'parent' : self.parent
}
def printable(self):
diff --git a/cobbler/serializer.py b/cobbler/serializer.py
index a8a3e739..38a182d6 100644
--- a/cobbler/serializer.py
+++ b/cobbler/serializer.py
@@ -27,7 +27,6 @@ def serialize(obj):
Will create intermediate paths if it can. Returns True on Success,
False on permission errors.
"""
- # FIXME: DEBUG
filename = obj.filename()
try:
fd = open(filename,"w+")
diff --git a/cobbler/utils.py b/cobbler/utils.py
index e30c10ce..5a83047c 100644
--- a/cobbler/utils.py
+++ b/cobbler/utils.py
@@ -255,8 +255,8 @@ def blender(remove_hashes, root_obj):
consolidated data.
"""
settings = api.BootAPI().settings()
-
tree = grab_tree(root_obj)
+ tree.reverse() # start with top of tree, override going down
results = {}
for node in tree:
__consolidate(node,results)
@@ -281,15 +281,41 @@ def __consolidate(node,results):
specially.
"""
node_data = node.to_datastruct()
- for field in node_data:
- data_item = node_data[field]
+
+ # if the node has any data items labelled <<inherit>> we need to expunge them.
+ # so that they do not override the supernodes.
+ node_data_copy = {}
+ for key in node_data:
+ value = node_data[key]
+ if value != "<<inherit>>":
+ node_data_copy[key] = value
+
+ for field in node_data_copy:
+
+ data_item = node_data_copy[field]
if results.has_key(field):
+
+ # FIXME: remove, we're doing this higher up
+ # for subobjects (child objects), a value of <<inherit>>
+ # means defer up the stack, and by definition usage of the API
+ # must ensure the value is valid somewhere up the stack. So, remove
+ # any magic values of <<inherit>> prior to blending.
+ #if data_item == '<<inherit>>':
+ # # don't load into results hash, the parent will have the
+ # # data we need.
+ # continue
+
+ # now merge data types seperately depending on whether they are hash, list,
+ # or scalar.
if type(data_item) == dict:
+ # interweave hash results
results[field].update(data_item)
elif type(data_item) == list or type(data_item) == tuple:
+ # add to lists (cobbler doesn't have many lists)
+ # FIXME: should probably uniqueify list after doing this
results[field].extend(data_item)
else:
- # override
+ # just override scalars
results[field] = data_item
else:
results[field] = data_item