summaryrefslogtreecommitdiffstats
path: root/cobbler
diff options
context:
space:
mode:
Diffstat (limited to 'cobbler')
-rw-r--r--cobbler/acls.py2
-rw-r--r--cobbler/action_buildiso.py271
-rw-r--r--cobbler/action_check.py47
-rw-r--r--cobbler/action_import.py40
-rw-r--r--cobbler/action_litesync.py24
-rw-r--r--cobbler/action_power.py13
-rw-r--r--cobbler/action_reposync.py18
-rw-r--r--cobbler/action_sync.py95
-rw-r--r--cobbler/api.py135
-rwxr-xr-xcobbler/cobbler.py2
-rw-r--r--cobbler/cobblerd.py174
-rw-r--r--cobbler/collection.py26
-rw-r--r--cobbler/collection_distros.py12
-rw-r--r--cobbler/collection_images.py24
-rw-r--r--cobbler/collection_profiles.py15
-rw-r--r--cobbler/collection_repos.py10
-rw-r--r--cobbler/collection_systems.py12
-rw-r--r--cobbler/commands.py22
-rw-r--r--cobbler/config.py7
-rw-r--r--cobbler/demo_connect.py2
-rw-r--r--cobbler/item.py5
-rw-r--r--cobbler/item_distro.py156
-rw-r--r--cobbler/item_image.py46
-rw-r--r--cobbler/item_profile.py245
-rw-r--r--cobbler/item_repo.py15
-rw-r--r--cobbler/item_system.py302
-rw-r--r--cobbler/manage_ctrl.py1
-rw-r--r--cobbler/module_loader.py4
-rw-r--r--cobbler/modules/authn_configfile.py4
-rw-r--r--cobbler/modules/authn_ldap.py1
-rw-r--r--cobbler/modules/authn_spacewalk.py111
-rw-r--r--cobbler/modules/cli_distro.py5
-rw-r--r--cobbler/modules/cli_image.py4
-rw-r--r--cobbler/modules/cli_misc.py19
-rw-r--r--cobbler/modules/cli_profile.py10
-rw-r--r--cobbler/modules/cli_repo.py4
-rw-r--r--cobbler/modules/cli_report.py2
-rw-r--r--cobbler/modules/cli_system.py10
-rw-r--r--cobbler/modules/install_post_log.py29
-rwxr-xr-xcobbler/modules/install_post_report.py101
-rwxr-xr-xcobbler/modules/install_pre_clear_anamon_logs.py51
-rw-r--r--cobbler/modules/install_pre_log.py29
-rw-r--r--cobbler/modules/manage_bind.py31
-rw-r--r--cobbler/modules/manage_dnsmasq.py1
-rw-r--r--cobbler/modules/manage_isc.py6
-rw-r--r--cobbler/modules/serializer_catalog.py24
-rw-r--r--cobbler/modules/serializer_yaml.py6
-rw-r--r--cobbler/modules/sync_post_restart_services.py70
-rw-r--r--cobbler/pxegen.py299
-rw-r--r--cobbler/remote.py545
-rw-r--r--cobbler/services.py24
-rw-r--r--cobbler/settings.py17
-rw-r--r--cobbler/templar.py14
-rw-r--r--cobbler/test_basic.py43
-rw-r--r--cobbler/utils.py288
-rw-r--r--cobbler/webui/CobblerWeb.py400
-rw-r--r--cobbler/yaml/__init__.py23
-rw-r--r--cobbler/yaml/dump.py305
-rw-r--r--cobbler/yaml/implicit.py52
-rw-r--r--cobbler/yaml/inline.py44
-rw-r--r--cobbler/yaml/klass.py54
-rw-r--r--cobbler/yaml/load.py333
-rw-r--r--cobbler/yaml/ordered_dict.py38
-rw-r--r--cobbler/yaml/redump.py22
-rw-r--r--cobbler/yaml/stream.py199
-rw-r--r--cobbler/yaml/timestamp.py152
-rw-r--r--cobbler/yaml/ypath.py469
67 files changed, 2694 insertions, 2870 deletions
diff --git a/cobbler/acls.py b/cobbler/acls.py
index 8e7ecea9..e797f2f7 100644
--- a/cobbler/acls.py
+++ b/cobbler/acls.py
@@ -40,7 +40,7 @@ class AclEngine:
yfh = open("/etc/cobbler/acls.conf")
data = yfh.read()
yfh.close()
- self.data = yaml.load(data).next()
+ self.data = yaml.load(data)
self.verbose = verbose
def __match(self, needle, haystack):
diff --git a/cobbler/action_buildiso.py b/cobbler/action_buildiso.py
index de95afb9..2eebb668 100644
--- a/cobbler/action_buildiso.py
+++ b/cobbler/action_buildiso.py
@@ -29,6 +29,7 @@ import sys
import traceback
import shutil
import sub_process
+import re
import utils
from cexceptions import *
@@ -68,8 +69,10 @@ class BuildIso:
self.distros = config.distros()
self.profiles = config.profiles()
self.systems = config.systems()
+ self.distros = config.distros()
self.distmap = {}
self.distctr = 0
+ self.source = ""
def make_shorter(self,distname):
if self.distmap.has_key(distname):
@@ -79,44 +82,8 @@ class BuildIso:
self.distmap[distname] = str(self.distctr)
return str(self.distctr)
- def run(self,iso=None,tempdir=None,profiles=None,systems=None):
-
- # if iso is none, create it in . as "kickstart.iso"
- if iso is None:
- iso = "kickstart.iso"
-
- if tempdir is None:
- tempdir = os.path.join(os.getcwd(), "buildiso")
- print _("- using/creating tempdir: %s") % tempdir
- if not os.path.exists(tempdir):
- os.makedirs(tempdir)
- else:
- shutil.rmtree(tempdir)
- os.makedirs(tempdir)
-
- # if base of tempdir does not exist, fail
- # create all profiles unless filtered by "profiles"
-
- imagesdir = os.path.join(tempdir, "images")
- isolinuxdir = os.path.join(tempdir, "isolinux")
-
- print _("- building tree for isolinux")
- if not os.path.exists(imagesdir):
- os.makedirs(imagesdir)
- if not os.path.exists(isolinuxdir):
- os.makedirs(isolinuxdir)
-
- print _("- copying miscellaneous files")
- isolinuxbin = "/usr/lib/syslinux/isolinux.bin"
- menu = "/var/lib/cobbler/menu.c32"
- chain = "/usr/lib/syslinux/chain.c32"
- files = [ isolinuxbin, menu, chain ]
- for f in files:
- if not os.path.exists(f):
- raise CX(_("Required file not found: %s") % f)
- else:
- utils.copyfile(f, os.path.join(isolinuxdir, os.path.basename(f)))
-
+
+ def generate_netboot_iso(self,imagesdir,isolinuxdir,profiles=None,systems=None):
print _("- copying kernels and initrds - for profiles")
# copy all images in included profiles to images dir
for profile in self.api.profiles():
@@ -134,8 +101,14 @@ class BuildIso:
distname = self.make_shorter(dist.name)
# tempdir/isolinux/$distro/vmlinuz, initrd.img
# FIXME: this will likely crash on non-Linux breeds
- shutil.copyfile(dist.kernel, os.path.join(isolinuxdir, "%s.krn" % distname))
- shutil.copyfile(dist.initrd, os.path.join(isolinuxdir, "%s.img" % distname))
+ f1 = os.path.join(isolinuxdir, "%s.krn" % distname)
+ f2 = os.path.join(isolinuxdir, "%s.img" % distname)
+ if not os.path.exists(dist.kernel):
+ raise CX("path does not exist: %s" % dist.kernel)
+ if not os.path.exists(dist.initrd):
+ raise CX("path does not exist: %s" % dist.initrd)
+ shutil.copyfile(dist.kernel, f1)
+ shutil.copyfile(dist.initrd, f2)
if systems is not None:
print _("- copying kernels and initrds - for systems")
@@ -156,9 +129,15 @@ class BuildIso:
isolinuxcfg = os.path.join(isolinuxdir, "isolinux.cfg")
cfg = open(isolinuxcfg, "w+")
cfg.write(HEADER) # fixme, use template
-
+
print _("- generating profile list...")
- for profile in self.api.profiles():
+ #sort the profiles
+ profile_list = [profile for profile in self.profiles]
+ def sort_name(a,b):
+ return cmp(a.name,b.name)
+ profile_list.sort(sort_name)
+
+ for profile in profile_list:
use_this = True
if profiles is not None:
which_profiles = profiles.split(",")
@@ -189,16 +168,22 @@ class BuildIso:
length=len(append_line)
if length>254:
- print _("WARNING - append line length is greater than 254 chars: (%s chars)") % length
-
+ print _("WARNING - append line length is greater than 254 chars: (%s chars)") % length
+
cfg.write(append_line)
-
+
if systems is not None:
print _("- generating system list...")
cfg.write("\nMENU SEPARATOR\n")
- for system in self.api.systems():
+ #sort the systems
+ system_list = [system for system in self.systems]
+ def sort_name(a,b):
+ return cmp(a.name,b.name)
+ system_list.sort(sort_name)
+
+ for system in system_list:
use_this = False
if systems is not None:
which_systems = systems.split(",")
@@ -230,35 +215,195 @@ class BuildIso:
# add network info to avoid DHCP only if it is available
- if data.has_key("ip_address_eth0") and data["ip_address_eth0"] != "":
- append_line = append_line + " ip=%s" % data["ip_address_eth0"]
- if data.has_key("subnet_eth0") and data["subnet_eth0"] != "":
- append_line = append_line + " netmask=%s" % data["subnet_eth0"]
- if data.has_key("gateway_eth0") and data["gateway_eth0"] != "":
- append_line = append_line + " gateway=%s\n" % data["gateway_eth0"]
+ if data.has_key("bonding_master_eth0") and data["bonding_master_eth0"] != "":
+ primary_interface = data["bonding_master_eth0"]
+ else:
+ primary_interface = "eth0"
+
+ if data.has_key("ip_address_" + primary_interface) and data["ip_address_" + primary_interface] != "":
+ append_line = append_line + " ip=%s" % data["ip_address_" + primary_interface]
+
+ if data.has_key("subnet_" + primary_interface) and data["subnet_" + primary_interface] != "":
+ append_line = append_line + " netmask=%s" % data["subnet_" + primary_interface]
+
+ if data.has_key("gateway") and data["gateway"] != "":
+ append_line = append_line + " gateway=%s" % data["gateway"]
+
+ if data.has_key("name_servers") and data["name_servers"]:
+ append_line = append_line + " dns=%s\n" % ",".join(data["name_servers"])
length=len(append_line)
if length > 254:
- print _("WARNING - append line length is greater than 254 chars: (%s chars)") % length
-
+ print _("WARNING - append line length is greater than 254 chars: (%s chars)") % length
+
cfg.write(append_line)
- print _("- done writing config")
+ print _("- done writing config")
cfg.write("\n")
cfg.write("MENU END\n")
cfg.close()
-
- cmd = "mkisofs -o %s -r -b isolinux/isolinux.bin -c isolinux/boot.cat" % iso
- cmd = cmd + " -no-emul-boot -boot-load-size 4 "
- cmd = cmd + " -boot-info-table -V Cobbler\ Install -R -J -T %s" % tempdir
+
+
+ def generate_standalone_iso(self,imagesdir,isolinuxdir,distname,filesource):
+
+ # Get the distro object for the requested distro
+ # and then get all of its descendants (profiles/sub-profiles/systems)
+ distro = self.api.find_distro(distname)
+ if distro is None:
+ raise CX("distro %s was not found, aborting" % distname)
+ descendants = distro.get_descendants()
+
+ if filesource is None:
+ # Try to determine the source from the distro kernel path
+ print _("- trying to locate source for distro")
+ found_source = False
+ (source_head, source_tail) = os.path.split(distro.kernel)
+ while source_tail != '':
+ if source_head == os.path.join(self.api.settings().webdir, "ks_mirror"):
+ filesource = os.path.join(source_head, source_tail)
+ found_source = True
+ print _(" found source in %s" % filesource)
+ break
+ (source_head, source_tail) = os.path.split(source_head)
+ # Can't find the source, raise an error
+ if not found_source:
+ raise CX(_(" Error, no installation source found. When building a standalone ISO, you must specify a --source if the distro install tree is not hosted locally"))
+
+ print _("- copying kernels and initrds - for standalone distro")
+ # tempdir/isolinux/$distro/vmlinuz, initrd.img
+ # FIXME: this will likely crash on non-Linux breeds
+ f1 = os.path.join(isolinuxdir, "vmlinuz")
+ f2 = os.path.join(isolinuxdir, "initrd.img")
+ if not os.path.exists(distro.kernel):
+ raise CX("path does not exist: %s" % distro.kernel)
+ if not os.path.exists(distro.initrd):
+ raise CX("path does not exist: %s" % distro.initrd)
+ shutil.copyfile(distro.kernel, f1)
+ shutil.copyfile(distro.initrd, f2)
+
+ cmd = "rsync -rlptgu --exclude=boot.cat --exclude=TRANS.TBL --exclude=isolinux/ %s/ %s/../" % (filesource, isolinuxdir)
+ print _("- copying distro %s files (%s)" % (distname,cmd))
+ rc = sub_process.call(cmd, shell=True, close_fds=True)
+ if rc:
+ raise CX(_("rsync of files failed"))
+
+ print _("- generating a isolinux.cfg")
+ isolinuxcfg = os.path.join(isolinuxdir, "isolinux.cfg")
+ cfg = open(isolinuxcfg, "w+")
+ cfg.write(HEADER) # fixme, use template
+
+ for descendant in descendants:
+ data = utils.blender(self.api, True, descendant)
+
+ cfg.write("\n")
+ cfg.write("LABEL %s\n" % descendant.name)
+ cfg.write(" MENU LABEL %s\n" % descendant.name)
+ cfg.write(" kernel vmlinuz\n")
+
+ data["kickstart"] = "cdrom:/isolinux/ks-%s.cfg" % descendant.name
+
+ append_line = " append initrd=initrd.img"
+ append_line = append_line + " ks=%s " % data["kickstart"]
+ append_line = append_line + " %s\n" % data["kernel_options"]
+
+ cfg.write(append_line)
+
+ if descendant.COLLECTION_TYPE == 'profile':
+ kickstart_data = self.api.kickgen.generate_kickstart_for_profile(descendant.name)
+ elif descendant.COLLECTION_TYPE == 'system':
+ kickstart_data = self.api.kickgen.generate_kickstart_for_system(descendant.name)
+
+ cdregex = re.compile("url .*\n", re.IGNORECASE)
+ kickstart_data = cdregex.sub("cdrom\n", kickstart_data)
+
+ ks_name = os.path.join(isolinuxdir, "ks-%s.cfg" % descendant.name)
+ ks_file = open(ks_name, "w+")
+ ks_file.write(kickstart_data)
+ ks_file.close()
+
+ print _("- done writing config")
+ cfg.write("\n")
+ cfg.write("MENU END\n")
+ cfg.close()
+
+ return
+
+
+ def run(self,iso=None,tempdir=None,profiles=None,systems=None,distro=None,standalone=None,source=None):
+
+ # the distro option is for stand-alone builds only
+ if not standalone and distro is not None:
+ raise CX(_("The --distro option should only be used when creating a standalone ISO"))
+ # if building standalone, we only want --distro,
+ # profiles/systems are disallowed
+ if standalone:
+ if profiles is not None or systems is not None:
+ raise CX(_("When building a standalone ISO, use --distro only instead of --profiles/--systems"))
+ elif distro is None:
+ raise CX(_("When building a standalone ISO, you must specify a --distro"))
+ if source != None and not os.path.exists(source):
+ raise CX(_("The source specified (%s) does not exist" % source))
+
+ # if iso is none, create it in . as "kickstart.iso"
+ if iso is None:
+ iso = "kickstart.iso"
+
+ if tempdir is None:
+ tempdir = os.path.join(os.getcwd(), "buildiso")
+ else:
+ if not os.path.isdir(tempdir):
+ raise CX(_("The --tempdir specified is not a directory"))
+
+ (tempdir_head,tempdir_tail) = os.path.split(os.path.normpath(tempdir))
+ if tempdir_tail != "buildiso":
+ tempdir = os.path.join(tempdir, "buildiso")
+
+ print _("- using/creating tempdir: %s") % tempdir
+ if not os.path.exists(tempdir):
+ os.makedirs(tempdir)
+ else:
+ shutil.rmtree(tempdir)
+ os.makedirs(tempdir)
+
+ # if base of tempdir does not exist, fail
+ # create all profiles unless filtered by "profiles"
+
+ imagesdir = os.path.join(tempdir, "images")
+ isolinuxdir = os.path.join(tempdir, "isolinux")
+
+ print _("- building tree for isolinux")
+ if not os.path.exists(imagesdir):
+ os.makedirs(imagesdir)
+ if not os.path.exists(isolinuxdir):
+ os.makedirs(isolinuxdir)
+
+ print _("- copying miscellaneous files")
+ isolinuxbin = "/usr/lib/syslinux/isolinux.bin"
+ menu = "/var/lib/cobbler/menu.c32"
+ chain = "/usr/lib/syslinux/chain.c32"
+ files = [ isolinuxbin, menu, chain ]
+ for f in files:
+ if not os.path.exists(f):
+ raise CX(_("Required file not found: %s") % f)
+ else:
+ utils.copyfile(f, os.path.join(isolinuxdir, os.path.basename(f)), self.api)
+
+ if standalone:
+ self.generate_standalone_iso(imagesdir,isolinuxdir,distro,source)
+ else:
+ self.generate_netboot_iso(imagesdir,isolinuxdir,profiles,systems)
+
+ cmd = "mkisofs -quiet -o %s -r -b isolinux/isolinux.bin -c isolinux/boot.cat" % iso
+ cmd = cmd + " -no-emul-boot -boot-load-size 4"
+ cmd = cmd + " -boot-info-table -V Cobbler\ Install -R -J -T %s" % tempdir
print _("- running: %s") % cmd
rc = sub_process.call(cmd, shell=True, close_fds=True)
if rc:
raise CX(_("mkisofs failed"))
-
+
print _("ISO build complete")
print _("You may wish to delete: %s") % tempdir
- print _("The output file is: %s") % iso
-
-
+ print _("The output file is: %s") % iso
+
+
diff --git a/cobbler/action_check.py b/cobbler/action_check.py
index 99fffbb0..a2885a28 100644
--- a/cobbler/action_check.py
+++ b/cobbler/action_check.py
@@ -44,6 +44,7 @@ class BootCheck:
(The CLI usage is "cobbler check" before "cobbler sync")
"""
status = []
+ self.checked_dist = utils.check_dist()
self.check_name(status)
self.check_selinux(status)
if self.settings.manage_dhcp:
@@ -95,13 +96,13 @@ class BootCheck:
if notes != "":
notes = " (NOTE: %s)" % notes
rc = 0
- if utils.check_dist() == "redhat":
+ if self.checked_dist == "redhat" or self.checked_dist == "suse":
if os.path.exists("/etc/rc.d/init.d/%s" % which):
- rc = sub_process.call("/sbin/service %s status >/dev/null 2>/dev/null" % which, shell=True, close_fds=True)
+ rc = sub_process.call("/sbin/service %s status > /dev/null 2>/dev/null" % which, shell=True, close_fds=True)
if rc != 0:
status.append(_("service %s is not running%s") % (which,notes))
return False
- elif utils.check_dist() == "debian":
+ elif self.checked_dist == "debian":
if os.path.exists("/etc/init.d/%s" % which):
rc = sub_process.call("/etc/init.d/%s status /dev/null 2>/dev/null" % which, shell=True, close_fds=True)
if rc != 0:
@@ -116,7 +117,7 @@ class BootCheck:
if os.path.exists("/etc/rc.d/init.d/iptables"):
rc = sub_process.call("/sbin/service iptables status >/dev/null 2>/dev/null", shell=True, close_fds=True)
if rc == 0:
- status.append(_("since iptables may be running, ensure 69, 80, %(syslog)s, and %(xmlrpc)s are unblocked") % { "syslog" : self.settings.syslog_port, "xmlrpc" : self.settings.xmlrpc_port })
+ status.append(_("since iptables may be running, ensure 69, 80, and %(xmlrpc)s are unblocked") % { "xmlrpc" : self.settings.xmlrpc_port })
def check_yum(self,status):
if not os.path.exists("/usr/bin/createrepo"):
@@ -125,6 +126,11 @@ class BootCheck:
status.append(_("reposync is not installed, need for cobbler reposync, install/upgrade yum-utils?"))
if not os.path.exists("/usr/bin/yumdownloader"):
status.append(_("yumdownloader is not installed, needed for cobbler repo add with --rpm-list parameter, install/upgrade yum-utils?"))
+ if self.settings.yumreposync_flags.find("\-l"):
+ if self.checked_dist == "redhat" or self.checked_dist == "suse":
+ yum_utils_ver = sub_process.call("/usr/bin/rpmquery --queryformat=%{VERSION} yum-utils", shell=True, close_fds=True)
+ if yum_utils_ver < "1.1.17":
+ status.append(_("yum-utils need to be at least version 1.1.17 for reposync -l"))
def check_name(self,status):
"""
@@ -146,7 +152,30 @@ class BootCheck:
if line.find("httpd_can_network_connect ") != -1:
if line.find("off") != -1:
status.append(_("Must enable selinux boolean to enable Apache and web services components, run: setsebool -P httpd_can_network_connect true"))
-
+ data3 = sub_process.Popen("/usr/sbin/semanage fcontext -l | grep public_content_t",shell=True,stdout=sub_process.PIPE).communicate()[0]
+
+ rule1 = False
+ rule2 = False
+ rule3 = False
+ selinux_msg = "/usr/sbin/semanage fcontext -a -t public_content_t \"%s\""
+ for line in data3.split("\n"):
+ if line.startswith("/tftpboot/.*") and line.find("public_content_t") != -1:
+ rule1 = True
+ if line.startswith("/var/lib/tftpboot/.*") and line.find("public_content_t") != -1:
+ rule2 = True
+ if line.startswith("/var/www/cobbler/images/.*") and line.find("public_content_t") != -1:
+ rule3 = True
+
+ rules = []
+ if not os.path.exists("/tftpboot") and not rule1:
+ rules.append(selinux_msg % "/tftpboot/.*")
+ else:
+ if not rule2:
+ rules.append(selinux_msg % "/var/lib/tftpboot/.*")
+ if not rule3:
+ rules.append(selinux_msg % "/var/www/cobbler/images/.*")
+ if len(rules) > 0:
+ status.append("you need to set some SELinux content rules to ensure cobbler works correctly in your SELinux environment, run the following: %s" % " && ".join(rules))
def check_for_default_password(self,status):
default_pass = self.settings.default_password_crypted
@@ -185,8 +214,10 @@ class BootCheck:
"""
Check if Apache is installed.
"""
- self.check_service(status,"httpd")
-
+ if self.checked_dist == "suse":
+ self.check_service(status,"apache2")
+ else:
+ self.check_service(status,"httpd")
def check_dhcpd_bin(self,status):
"""
@@ -277,7 +308,7 @@ class BootCheck:
f = open(self.settings.tftpd_conf)
re_disable = re.compile(r'disable.*=.*yes')
for line in f.readlines():
- if re_disable.search(line):
+ if re_disable.search(line) and not line.strip().startswith("#"):
status.append(_("change 'disable' to 'no' in %(file)s") % { "file" : self.settings.tftpd_conf })
else:
status.append(_("file %(file)s does not exist") % { "file" : self.settings.tftpd_conf })
diff --git a/cobbler/action_import.py b/cobbler/action_import.py
index 5b94b89a..2008c53c 100644
--- a/cobbler/action_import.py
+++ b/cobbler/action_import.py
@@ -88,8 +88,8 @@ class Importer:
if self.arch == "x86":
# be consistent
self.arch = "i386"
- if self.arch not in [ "i386", "ia64", "ppc", "ppc64", "s390x", "x86_64", ]:
- raise CX(_("arch must be i386, ia64, ppc, ppc64, s390x or x86_64"))
+ if self.arch not in [ "i386", "ia64", "ppc", "ppc64", "s390", "s390x", "x86_64", ]:
+ raise CX(_("arch must be i386, ia64, ppc, ppc64, s390, s390x or x86_64"))
# if we're going to do any copying, set where to put things
# and then make sure nothing is already there.
@@ -113,7 +113,7 @@ class Importer:
if self.arch:
# append the arch path to the name if the arch is not already
# found in the name.
- for x in [ "i386", "ia64", "ppc", "ppc64", "s390x", "x86_64", "x86", ]:
+ for x in [ "i386", "ia64", "ppc", "ppc64", "s390", "s390x", "x86_64", "x86", ]:
if self.mirror_name.lower().find(x) != -1:
if self.arch != x :
raise CX(_("Architecture found on pathname (%s) does not fit the one given in command line (%s)")%(x,self.arch))
@@ -209,11 +209,8 @@ class Importer:
print _("---------------- (associating kickstarts)")
self.kickstart_finder(distros_added)
- # ensure everything is nicely written out to the filesystem
- # (which is not so neccessary in newer Cobbler but we're paranoid)
-
- print _("---------------- (syncing)")
- self.api.sync()
+ # ensure bootloaders are present
+ self.api.pxegen.copy_bootloaders()
return True
@@ -265,9 +262,9 @@ class Importer:
# print _("- skipping distro %s since it wasn't imported this time") % profile.distro
continue
+ kdir = os.path.dirname(distro.kernel)
+ importer = import_factory(kdir,self.path)
if self.kickstart_file == None:
- kdir = os.path.dirname(distro.kernel)
- importer = import_factory(kdir,self.path)
for rpm in importer.get_release_files():
# FIXME : This redhat specific check should go into the importer.find_release_files method
if rpm.find("notes") != -1:
@@ -519,9 +516,9 @@ class Importer:
print "- following symlink: %s" % fullname
os.path.walk(fullname, self.distro_adder, foo)
- if x.startswith("initrd") or x.startswith("ramdisk.image.gz"):
+ if ( x.startswith("initrd") or x.startswith("ramdisk.image.gz") ) and x != "initrd.size":
initrd = os.path.join(dirname,x)
- if ( x.startswith("vmlinuz") or x.startswith("kernel.img") ) and x.find("initrd") == -1:
+ if ( x.startswith("vmlinu") or x.startswith("kernel.img") ) and x.find("initrd") == -1:
kernel = os.path.join(dirname,x)
if x.lower().startswith("startrom.n1_"):
startrom = os.path.join(dirname,x)
@@ -774,7 +771,7 @@ class Importer:
name = name.replace("chrp","ppc64")
for separator in [ '-' , '_' , '.' ] :
- for arch in [ "i386" , "x86_64" , "ia64" , "ppc64", "ppc32", "ppc", "x86" , "s390x" , "386" , "amd" ]:
+ for arch in [ "i386" , "x86_64" , "ia64" , "ppc64", "ppc32", "ppc", "x86" , "s390x", "s390" , "386" , "amd" ]:
name = name.replace("%s%s" % ( separator , arch ),"")
return name
@@ -792,8 +789,10 @@ class Importer:
return "ia64"
if dirname.find("i386") != -1 or dirname.find("386") != -1 or dirname.find("x86") != -1:
return "i386"
- if dirname.find("s390") != -1:
+ if dirname.find("s390x") != -1:
return "s390x"
+ if dirname.find("s390") != -1:
+ return "s390"
if dirname.find("ppc64") != -1 or dirname.find("chrp") != -1:
return "ppc64"
if dirname.find("ppc32") != -1:
@@ -828,6 +827,7 @@ def guess_breed(kerneldir,path):
[ 'Packages' , "redhat" ],
[ 'Fedora' , "redhat" ],
[ 'Server' , "redhat" ],
+ [ 'Client' , "redhat" ],
[ 'setup.exe' , "windows" ],
]
guess = None
@@ -914,7 +914,7 @@ class BaseImporter:
for x in fnames:
if self.match_kernelarch_file(x):
# print _("- kernel header found: %s") % x
- for arch in [ "i386" , "x86_64" , "ia64" , "ppc64", "ppc", "s390x" ]:
+ for arch in [ "i386" , "x86_64" , "ia64" , "ppc64", "ppc", "s390", "s390x" ]:
if x.find(arch) != -1:
foo[arch] = 1
for arch in [ "i686" , "amd64" ]:
@@ -986,8 +986,11 @@ class RedHatImporter ( BaseImporter ) :
data = glob.glob(os.path.join(self.get_pkgdir(), "*release-*"))
data2 = []
for x in data:
- if x.find("generic") == -1:
- data2.append(x)
+ b = os.path.basename(x)
+ if b.find("fedora") != -1 or \
+ b.find("redhat") != -1 or \
+ b.find("centos") != -1:
+ data2.append(x)
return data2
# ================================================================
@@ -1099,13 +1102,14 @@ class RedHatImporter ( BaseImporter ) :
# OS_VERSION next
# OS_VERSION.MINOR next
# ARCH/default.ks next
- # default.ks finally.
+ # FLAVOR.ks next
kickstarts = [
"%s/%s/%s.%i.ks" % (kickbase,arch,os_version,int(minor)),
"%s/%s/%s.ks" % (kickbase,arch,os_version),
"%s/%s.%i.ks" % (kickbase,os_version,int(minor)),
"%s/%s.ks" % (kickbase,os_version),
"%s/%s/default.ks" % (kickbase,arch),
+ "%s/%s.ks" % (kickbase,flavor),
]
for kickstart in kickstarts:
if os.path.exists(kickstart):
diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py
index b040db0a..7773455e 100644
--- a/cobbler/action_litesync.py
+++ b/cobbler/action_litesync.py
@@ -46,11 +46,11 @@ class BootLiteSync:
Handles conversion of internal state to the tftpboot tree layout
"""
- def __init__(self,config):
+ def __init__(self,config,verbose=False):
"""
Constructor
"""
- self.verbose = True
+ self.verbose = verbose
self.config = config
self.distros = config.distros()
self.profiles = config.profiles()
@@ -58,7 +58,7 @@ class BootLiteSync:
self.images = config.images()
self.settings = config.settings()
self.repos = config.repos()
- self.sync = config.api.get_sync()
+ self.sync = config.api.get_sync(verbose)
def add_single_distro(self, name):
# get the distro record
@@ -101,7 +101,7 @@ class BootLiteSync:
# get the profile object:
profile = self.profiles.find(name=name)
if profile is None:
- raise CX(_("error in profile lookup"))
+ raise CX(_("error in profile lookup for %s" % name))
# rebuild the yum configuration files for any attached repos
# generate any templates listed in the distro
self.sync.pxegen.write_templates(profile)
@@ -160,10 +160,6 @@ class BootLiteSync:
system_record = self.systems.find(name=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)
- utils.rmtree(os.path.join(self.settings.webdir, "kickstarts_sys", filename))
if self.settings.manage_dhcp:
if self.settings.omapi_enabled:
@@ -179,8 +175,12 @@ class BootLiteSync:
distro = self.distros.find(name=profile.distro)
if distro is not None and distro in [ "ia64", "IA64"]:
itanic = True
- if not itanic:
- utils.rmfile(os.path.join(bootloc, "pxelinux.cfg", filename))
- else:
- utils.rmfile(os.path.join(bootloc, filename))
+
+ for (name,interface) in system_record.interfaces.iteritems():
+ filename = utils.get_config_filename(system_record,interface=name)
+
+ if not itanic:
+ utils.rmfile(os.path.join(bootloc, "pxelinux.cfg", filename))
+ else:
+ utils.rmfile(os.path.join(bootloc, filename))
diff --git a/cobbler/action_power.py b/cobbler/action_power.py
index 9db4535c..789750a1 100644
--- a/cobbler/action_power.py
+++ b/cobbler/action_power.py
@@ -31,6 +31,7 @@ import os.path
import sub_process
import sys
import traceback
+import time
import utils
from cexceptions import *
@@ -106,7 +107,15 @@ class PowerTool:
#if not os.path.exists(tool_needed):
# print "warning: %s does not seem to be installed" % tool_needed
- rc = sub_process.call(cmd, shell=False, close_fds=True)
+ # Try the power command 5 times before giving up.
+ # Some power switches are flakey
+ for x in range(0,5):
+ rc = sub_process.call(cmd, shell=False, close_fds=True)
+ if rc == 0:
+ break
+ else:
+ time.sleep(2)
+
if not rc == 0:
raise CX("command failed (rc=%s), please validate the physical setup and cobbler config" % rc)
@@ -137,6 +146,8 @@ class PowerTool:
"lpar" : os.path.join(powerdir,"power_lpar.template"),
"bladecenter": os.path.join(powerdir,"power_bladecenter.template"),
"virsh" : os.path.join(powerdir,"power_virsh.template"),
+ "integrity" : os.path.join(powerdir,"power_integrity.template"),
+ "wti" : os.path.join(powerdir,"power_wti.template"),
}
result = map.get(self.system.power_type, "")
diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py
index 4bb484c5..14ef1112 100644
--- a/cobbler/action_reposync.py
+++ b/cobbler/action_reposync.py
@@ -345,6 +345,24 @@ class RepoSync:
if rc !=0:
raise CX(_("cobbler reposync failed"))
+ repodata_path = os.path.join(dest_path, "repodata")
+
+ if not os.path.exists("/usr/bin/wget"):
+ raise CX(_("no /usr/bin/wget found, please install wget"))
+
+ cmd2 = "/usr/bin/wget -q %s/repodata/comps.xml -O /dev/null" % (repo_mirror)
+ rc = sub_process.call(cmd2, shell=True, close_fds=True)
+ if rc == 0:
+ if not os.path.isdir(repodata_path):
+ os.makedirs(repodata_path)
+
+ cmd2 = "/usr/bin/wget -q %s/repodata/comps.xml -O %s/comps.xml" % (repo_mirror, repodata_path)
+ print _("- %s") % cmd2
+
+ rc = sub_process.call(cmd2, shell=True, close_fds=True)
+ if rc !=0:
+ raise CX(_("wget failed"))
+
# now run createrepo to rebuild the index
if repo.mirror_locally:
diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py
index f6e5bcd8..78f3c3d5 100644
--- a/cobbler/action_sync.py
+++ b/cobbler/action_sync.py
@@ -56,19 +56,22 @@ class BootSync:
"""
Constructor
"""
- self.verbose = verbose
- self.config = config
- self.api = config.api
- self.distros = config.distros()
- self.profiles = config.profiles()
- self.systems = config.systems()
- self.settings = config.settings()
- self.repos = config.repos()
- self.templar = templar.Templar(config)
- self.pxegen = pxegen.PXEGen(config)
- self.dns = dns
- self.dhcp = dhcp
- self.bootloc = utils.tftpboot_location()
+ self.verbose = verbose
+ self.config = config
+ self.api = config.api
+ self.distros = config.distros()
+ self.profiles = config.profiles()
+ self.systems = config.systems()
+ self.settings = config.settings()
+ self.repos = config.repos()
+ self.templar = templar.Templar(config)
+ self.pxegen = pxegen.PXEGen(config)
+ self.dns = dns
+ self.dhcp = dhcp
+ self.bootloc = utils.tftpboot_location()
+ self.pxegen.verbose = verbose
+ self.dns.verbose = verbose
+ self.dhcp.verbose = verbose
def run(self):
"""
@@ -78,37 +81,67 @@ class BootSync:
if not os.path.exists(self.bootloc):
raise CX(_("cannot find directory: %s") % self.bootloc)
+ if self.verbose:
+ print "- running pre-sync triggers"
+
# run pre-triggers...
- utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/pre/*")
+ utils.run_triggers(self.api, None, "/var/lib/cobbler/triggers/sync/pre/*")
# (paranoid) in case the pre-trigger modified any objects...
+
+ if self.verbose:
+ print "- loading configuration"
self.api.deserialize()
+
self.distros = self.config.distros()
self.profiles = self.config.profiles()
self.systems = self.config.systems()
self.settings = self.config.settings()
self.repos = self.config.repos()
- self.pxegen = pxegen.PXEGen(self.config)
# execute the core of the sync operation
+
+ if self.verbose:
+ print "- cleaning trees"
self.clean_trees()
+
+ if self.verbose:
+ print "- copying bootloaders"
self.pxegen.copy_bootloaders()
+
+ if self.verbose:
+ print "- copying distros"
self.pxegen.copy_distros()
+
+ if self.verbose:
+ print "- copying images"
self.pxegen.copy_images()
self.pxegen.generate_windows_files()
for x in self.systems:
+ if self.verbose:
+ print "- copying files for system: %s" % x.name
self.pxegen.write_all_system_files(x)
+
if self.settings.manage_dhcp:
+ if self.verbose:
+ print "- rendering DHCP files"
self.dhcp.write_dhcp_file()
self.dhcp.regen_ethers()
if self.settings.manage_dns:
+ if self.verbose:
+ print "- rendering DNS files"
self.dns.regen_hosts()
self.dns.write_dns_files()
+
+ if self.verbose:
+ print "- generating PXE menu structure"
self.pxegen.make_pxe_menu()
self.pxegen.write_tftpd_rules(True)
# run post-triggers
- utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/post/*")
+ if self.verbose:
+ print "- running post-sync triggers"
+ utils.run_triggers(self.api, None, "/var/lib/cobbler/triggers/sync/post/*")
return True
def clean_trees(self):
@@ -128,14 +161,14 @@ class BootSync:
path = os.path.join(self.settings.webdir,x)
if os.path.isfile(path):
if not x.endswith(".py"):
- utils.rmfile(path)
+ utils.rmfile(path,verbose=self.verbose)
if os.path.isdir(path):
- if not x in ["web", "webui", "localmirror","repo_mirror","ks_mirror","images","links","repo_profile","repo_system","svc","rendered"] :
+ if not x in ["aux", "web", "webui", "localmirror","repo_mirror","ks_mirror","images","links","repo_profile","repo_system","svc","rendered"] :
# delete directories that shouldn't exist
- utils.rmtree(path)
+ utils.rmtree(path,verbose=self.verbose)
if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles","repo_profile","repo_system","rendered"]:
# clean out directory contents
- utils.rmtree_contents(path)
+ utils.rmtree_contents(path,verbose=self.verbose)
pxelinux_dir = os.path.join(self.bootloc, "pxelinux.cfg")
images_dir = os.path.join(self.bootloc, "images")
yaboot_bin_dir = os.path.join(self.bootloc, "ppc")
@@ -143,21 +176,21 @@ class BootSync:
s390_dir = os.path.join(self.bootloc, "s390x")
rendered_dir = os.path.join(self.settings.webdir, "rendered")
if not os.path.exists(pxelinux_dir):
- utils.mkdir(pxelinux_dir)
+ utils.mkdir(pxelinux_dir,verbose=self.verbose)
if not os.path.exists(images_dir):
- utils.mkdir(images_dir)
+ utils.mkdir(images_dir,verbose=self.verbose)
if not os.path.exists(rendered_dir):
- utils.mkdir(rendered_dir)
+ utils.mkdir(rendered_dir,verbose=self.verbose)
if not os.path.exists(yaboot_bin_dir):
- utils.mkdir(yaboot_bin_dir)
+ utils.mkdir(yaboot_bin_dir,verbose=self.verbose)
if not os.path.exists(yaboot_cfg_dir):
- utils.mkdir(yaboot_cfg_dir)
- utils.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg"))
- utils.rmtree_contents(os.path.join(self.bootloc, "images"))
- utils.rmtree_contents(os.path.join(self.bootloc, "s390x"))
- utils.rmtree_contents(os.path.join(self.bootloc, "ppc"))
- utils.rmtree_contents(os.path.join(self.bootloc, "etc"))
- utils.rmtree_contents(rendered_dir)
+ utils.mkdir(yaboot_cfg_dir,verbose=self.verbose)
+ utils.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg"),verbose=self.verbose)
+ utils.rmtree_contents(os.path.join(self.bootloc, "images"),verbose=self.verbose)
+ utils.rmtree_contents(os.path.join(self.bootloc, "s390x"),verbose=self.verbose)
+ utils.rmtree_contents(os.path.join(self.bootloc, "ppc"),verbose=self.verbose)
+ utils.rmtree_contents(os.path.join(self.bootloc, "etc"),verbose=self.verbose)
+ utils.rmtree_contents(rendered_dir,verbose=self.verbose)
diff --git a/cobbler/api.py b/cobbler/api.py
index 84c2c638..ffc62644 100644
--- a/cobbler/api.py
+++ b/cobbler/api.py
@@ -22,6 +22,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
"""
+import yaml
import config
import utils
import action_sync
@@ -48,7 +49,8 @@ import logging
import time
import random
import os
-import yaml
+import xmlrpclib
+import traceback
ERROR = 100
INFO = 10
@@ -65,7 +67,7 @@ class BootAPI:
__shared_state = {}
__has_loaded = False
- def __init__(self, log_settings={}):
+ def __init__(self, log_settings={}, is_cobblerd=False):
"""
Constructor
"""
@@ -82,6 +84,7 @@ class BootAPI:
# level (and remote.py web service level) instead.
random.seed()
+ self.is_cobblerd = is_cobblerd
try:
self.logger = self.__setup_logger("api")
@@ -90,14 +93,16 @@ class BootAPI:
# perms_ok is False
return
- self.logger_remote = self.__setup_logger("remote")
+ # FIMXE: conslidate into 1 server instance
+
self.selinux_enabled = utils.is_selinux_enabled()
self.dist = utils.check_dist()
self.os_version = utils.os_release()
self.acl_engine = acls.AclEngine()
-
+
BootAPI.__has_loaded = True
+
module_loader.load_modules()
self._config = config.Config(self)
@@ -118,9 +123,10 @@ class BootAPI:
self.pxegen = pxegen.PXEGen(self._config)
self.logger.debug("API handle initialized")
self.perms_ok = True
-
+
+
def __setup_logger(self,name):
- return utils.setup_logger(name, **self.log_settings)
+ return utils.setup_logger(name, is_cobblerd=self.is_cobblerd, **self.log_settings)
def is_selinux_enabled(self):
"""
@@ -142,6 +148,28 @@ class BootAPI:
return False
return True
+ def _internal_cache_update(self, collection_type, name, remove=False):
+ """
+ Update cobblerd so it won't have to ever reload the config, once started.
+ """
+ # FIXME: take value from settings, use raw port
+ if self.is_cobblerd:
+ # don't signal yourself, that's asking for trouble.
+ return True
+ self.server = xmlrpclib.Server("http://127.0.0.1:%s" % self.settings().xmlrpc_port)
+ try:
+ if not remove:
+ self.server.internal_cache_update(collection_type, name)
+ else:
+ self.server.internal_cache_remove(collection_type, name)
+ except Exception, e:
+ if len(e.args) == 2 and e[0] == 111:
+ # if cobblerd is not running, no harm done, nothing to signal
+ pass
+ else:
+ raise CX("error contacting cobblerd")
+ return False
+
def last_modified_time(self):
"""
Returns the time of the last modification to cobbler, made by any
@@ -181,14 +209,16 @@ class BootAPI:
version_tuple -- something like [ 1, 3, 2 ]
"""
fd = open("/var/lib/cobbler/version")
- data = yaml.load(fd.read()).next()
+ ydata = fd.read()
fd.close()
+ data = yaml.load(ydata)
if not extended:
# for backwards compatibility and use with koan's comparisons
elems = data["version_tuple"]
+ print elems
return int(elems[0]) + 0.1*int(elems[1]) + 0.001*int(elems[2])
else:
- return data
+ return data
def clear(self):
"""
@@ -237,14 +267,10 @@ class BootAPI:
def update(self):
"""
- This can be called when you expect a cobbler object
- to have changed outside of your API call. It does not
- have to be called before read operations but should be
- called before write operations depending on the last
- modification time. For the local API it is not needed.
+ This can be called is no longer used by cobbler.
+ And is here to just avoid breaking older scripts.
"""
- self.clear()
- self.deserialize()
+ return True
def copy_distro(self, ref, newname):
self.log("copy_distro",[ref.name, newname])
@@ -266,46 +292,45 @@ class BootAPI:
self.log("copy_image",[ref.name, newname])
return self._config.images().copy(ref,newname)
- def remove_distro(self, ref, recursive=False):
+ def remove_distro(self, ref, recursive=False, delete=True, with_triggers=True, ):
if type(ref) != str:
self.log("remove_distro",[ref.name])
- return self._config.distros().remove(ref.name, recursive=recursive)
+ return self._config.distros().remove(ref.name, recursive=recursive, with_delete=delete, with_triggers=with_triggers)
else:
self.log("remove_distro",ref)
- return self._config.distros().remove(ref, recursive=recursive)
-
+ return self._config.distros().remove(ref, recursive=recursive, with_delete=delete, with_triggers=with_triggers)
- def remove_profile(self,ref, recursive=False):
+ def remove_profile(self,ref, recursive=False, delete=True, with_triggers=True):
if type(ref) != str:
self.log("remove_profile",[ref.name])
- return self._config.profiles().remove(ref.name, recursive=recursive)
+ return self._config.profiles().remove(ref.name, recursive=recursive, with_delete=delete, with_triggers=with_triggers)
else:
self.log("remove_profile",ref)
- return self._config.profiles().remove(ref, recursive=recursive)
+ return self._config.profiles().remove(ref, recursive=recursive, with_delete=delete, with_triggers=with_triggers)
- def remove_system(self, ref, recursive=False):
+ def remove_system(self, ref, recursive=False, delete=True, with_triggers=True):
if type(ref) != str:
self.log("remove_system",[ref.name])
- return self._config.systems().remove(ref.name)
+ return self._config.systems().remove(ref.name, with_delete=delete, with_triggers=with_triggers)
else:
self.log("remove_system",ref)
- return self._config.systems().remove(ref)
+ return self._config.systems().remove(ref, with_delete=delete, with_triggers=with_triggers)
- def remove_repo(self, ref, recursive=False):
+ def remove_repo(self, ref, recursive=False, delete=True, with_triggers=True):
if type(ref) != str:
self.log("remove_repo",[ref.name])
- return self._config.repos().remove(ref.name)
+ return self._config.repos().remove(ref.name, with_delete=delete, with_triggers=with_triggers)
else:
self.log("remove_repo",ref)
- return self._config.repos().remove(ref)
+ return self._config.repos().remove(ref, with_delete=delete, with_triggers=with_triggers)
- def remove_image(self, ref, recursive=False):
+ def remove_image(self, ref, recursive=False, delete=True, with_triggers=True):
if type(ref) != str:
self.log("remove_image",[ref.name])
- return self._config.images().remove(ref.name, recursive=recursive)
+ return self._config.images().remove(ref.name, recursive=recursive, with_delete=delete, with_triggers=with_triggers)
else:
self.log("remove_image",ref)
- return self._config.images().remove(ref, recursive=recursive)
+ return self._config.images().remove(ref, recursive=recursive, with_delete=delete, with_triggers=with_triggers)
def rename_distro(self, ref, newname):
self.log("rename_distro",[ref.name,newname])
@@ -347,29 +372,34 @@ class BootAPI:
self.log("new_image",[is_subobject])
return self._config.new_image(is_subobject=is_subobject)
- def add_distro(self, ref, check_for_duplicate_names=False):
+ def add_distro(self, ref, check_for_duplicate_names=False, save=True):
self.log("add_distro",[ref.name])
- return self._config.distros().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names)
+ rc = self._config.distros().add(ref,check_for_duplicate_names=check_for_duplicate_names,save=save)
+ return rc
- def add_profile(self, ref, check_for_duplicate_names=False):
+ def add_profile(self, ref, check_for_duplicate_names=False,save=True):
self.log("add_profile",[ref.name])
- return self._config.profiles().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names)
+ rc = self._config.profiles().add(ref,check_for_duplicate_names=check_for_duplicate_names,save=save)
+ return rc
- def add_system(self, ref, check_for_duplicate_names=False, check_for_duplicate_netinfo=False):
+ def add_system(self, ref, check_for_duplicate_names=False, check_for_duplicate_netinfo=False, save=True):
self.log("add_system",[ref.name])
- return self._config.systems().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names,check_for_duplicate_netinfo=check_for_duplicate_netinfo)
+ rc = self._config.systems().add(ref,check_for_duplicate_names=check_for_duplicate_names,check_for_duplicate_netinfo=check_for_duplicate_netinfo,save=save)
+ return rc
- def add_repo(self, ref, check_for_duplicate_names=False):
+ def add_repo(self, ref, check_for_duplicate_names=False,save=True):
self.log("add_repo",[ref.name])
- return self._config.repos().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names)
-
- def add_image(self, ref, check_for_duplicate_names=False):
+ rc = self._config.repos().add(ref,check_for_duplicate_names=check_for_duplicate_names,save=save)
+ return rc
+
+ def add_image(self, ref, check_for_duplicate_names=False,save=True):
self.log("add_image",[ref.name])
- return self._config.images().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names)
+ rc = self._config.images().add(ref,check_for_duplicate_names=check_for_duplicate_names,save=save)
+ return rc
def find_distro(self, name=None, return_list=False, no_errors=False, **kargs):
return self._config.distros().find(name=name, return_list=return_list, no_errors=no_errors, **kargs)
-
+
def find_profile(self, name=None, return_list=False, no_errors=False, **kargs):
return self._config.profiles().find(name=name, return_list=return_list, no_errors=no_errors, **kargs)
@@ -389,12 +419,11 @@ class BootAPI:
results1 = collector()
results2 = []
for x in results1:
- print "INPUT: %s ACTUAL: %s" % (mtime, x.mtime)
if x.mtime == 0 or x.mtime >= mtime:
if not collapse:
- results2.append(results1)
+ results2.append(x)
else:
- results2.append(results1.to_datastruct())
+ results2.append(x.to_datastruct())
return results2
def get_distros_since(self,mtime,collapse=False):
@@ -511,7 +540,7 @@ class BootAPI:
validator = action_validate.Validate(self._config)
return validator.run()
- def sync(self):
+ def sync(self,verbose=False):
"""
Take the values currently written to the configuration files in
/etc, and /var, and build out the information tree found in
@@ -519,10 +548,10 @@ class BootAPI:
saved with serialize() will NOT be synchronized with this command.
"""
self.log("sync")
- sync = self.get_sync()
+ sync = self.get_sync(verbose=verbose)
return sync.run()
- def get_sync(self):
+ def get_sync(self,verbose=False):
self.dhcp = self.get_module_from_file(
"dhcp",
"module",
@@ -533,7 +562,7 @@ class BootAPI:
"module",
"manage_bind"
).get_manager(self._config)
- return action_sync.BootSync(self._config,dhcp=self.dhcp,dns=self.dns)
+ return action_sync.BootSync(self._config,dhcp=self.dhcp,dns=self.dns,verbose=verbose)
def reposync(self, name=None, tries=1, nofail=False):
"""
@@ -646,10 +675,10 @@ class BootAPI:
self.log("authorize",[user,resource,arg1,arg2,rc],debug=True)
return rc
- def build_iso(self,iso=None,profiles=None,systems=None,tempdir=None):
+ def build_iso(self,iso=None,profiles=None,systems=None,tempdir=None,distro=None,standalone=None,source=None):
builder = action_buildiso.BuildIso(self._config)
return builder.run(
- iso=iso, profiles=profiles, systems=systems, tempdir=tempdir
+ iso=iso, profiles=profiles, systems=systems, tempdir=tempdir, distro=distro, standalone=standalone, source=source
)
def replicate(self, cobbler_master = None, sync_all=False, sync_kickstarts=False, sync_trees=False, sync_repos=False, sync_triggers=False, systems=False):
@@ -697,7 +726,7 @@ class BootAPI:
Cycles power on a system that has power management configured.
"""
self.power_off(system, user, password)
- time.sleep(1)
+ time.sleep(5)
return self.power_on(system, user, password)
def get_os_details(self):
diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py
index c896d266..5bbbcef5 100755
--- a/cobbler/cobbler.py
+++ b/cobbler/cobbler.py
@@ -42,7 +42,7 @@ I18N_DOMAIN = "cobbler"
class BootCLI:
def __init__(self):
- self.api = api.BootAPI()
+ self.api = api.BootAPI(is_cobblerd=False)
self.loader = commands.FunctionLoader(self.api)
climods = self.api.get_modules_in_category("cli")
for mod in climods:
diff --git a/cobbler/cobblerd.py b/cobbler/cobblerd.py
index 36645750..8abe8329 100644
--- a/cobbler/cobblerd.py
+++ b/cobbler/cobblerd.py
@@ -41,25 +41,14 @@ import remote
def main():
core(logger=None)
-def core(logger=None):
+def core(api):
- bootapi = cobbler_api.BootAPI()
+ bootapi = api
settings = bootapi.settings()
- syslog_port = settings.syslog_port
xmlrpc_port = settings.xmlrpc_port
- xmlrpc_port2 = settings.xmlrpc_rw_port
-
- pid = os.fork()
regen_ss_file()
-
- if pid == 0:
- # part one: XMLRPC -- which may be just read-only or both read-only and read-write
- do_xmlrpc_tasks(bootapi, settings, xmlrpc_port, xmlrpc_port2, logger)
- else:
- # part two: syslog, or syslog+avahi if avahi is installed
- do_other_tasks(bootapi, settings, syslog_port, logger)
- os.waitpid(pid, 0)
+ do_xmlrpc_tasks(bootapi, settings, xmlrpc_port)
def regen_ss_file():
# this is only used for Kerberos auth at the moment.
@@ -76,44 +65,23 @@ def regen_ss_file():
os.system("chown apache /var/lib/cobbler/web.ss")
return 1
-def do_xmlrpc_tasks(bootapi, settings, xmlrpc_port, xmlrpc_port2, logger):
- if str(settings.xmlrpc_rw_enabled) != "0":
- pid2 = os.fork()
- if pid2 == 0:
- do_mandatory_xmlrpc_tasks(bootapi, settings, xmlrpc_port, logger)
- else:
- do_xmlrpc_rw(bootapi, settings, xmlrpc_port2, logger)
- os.waitpid(pid2, 0)
- else:
- logger.debug("xmlrpc_rw is disabled in the settings file")
- do_mandatory_xmlrpc_tasks(bootapi, settings, xmlrpc_port, logger)
-
-def do_mandatory_xmlrpc_tasks(bootapi,settings,xmlrpc_port,logger):
- #pid3 = os.fork()
- #if pid3 == 0:
- # do_xmlrpc(bootapi, settings, xmlrpc_port, logger)
- #else:
- # # NOTE: this shouldn't be enabled unless we decide
- # # to use it for something.
- # # do_xmlrpc_unix(bootapi, settings, logger)
- # pass
- do_xmlrpc(bootapi, settings, xmlrpc_port, logger)
-
-
-def do_other_tasks(bootapi, settings, syslog_port, logger):
-
- # FUTURE: this should also start the Web UI, if the dependencies
- # are available.
-
- if os.path.exists("/usr/bin/avahi-publish-service"):
- pid2 = os.fork()
- if pid2 == 0:
- do_syslog(bootapi, settings, syslog_port, logger)
- else:
- do_avahi(bootapi, settings, logger)
- os.waitpid(pid2, 0)
- else:
- do_syslog(bootapi, settings, syslog_port, logger)
+def do_xmlrpc_tasks(bootapi, settings, xmlrpc_port):
+ do_xmlrpc_rw(bootapi, settings, xmlrpc_port)
+
+#def do_other_tasks(bootapi, settings, syslog_port, logger):
+#
+# # FUTURE: this should also start the Web UI, if the dependencies
+# # are available.
+#
+# if os.path.exists("/usr/bin/avahi-publish-service"):
+# pid2 = os.fork()
+# if pid2 == 0:
+# do_syslog(bootapi, settings, syslog_port, logger)
+# else:
+# do_avahi(bootapi, settings, logger)
+# os.waitpid(pid2, 0)
+# else:
+# do_syslog(bootapi, settings, syslog_port, logger)
def log(logger,msg):
@@ -122,106 +90,34 @@ def log(logger,msg):
else:
print >>sys.stderr, msg
-def do_avahi(bootapi, settings, logger):
- # publish via zeroconf. This command will not terminate
- log(logger, "publishing avahi service")
- cmd = [ "/usr/bin/avahi-publish-service",
- "cobblerd",
- "_http._tcp",
- "%s" % settings.xmlrpc_port ]
- proc = sub_process.Popen(cmd, shell=False, stderr=sub_process.PIPE, stdout=sub_process.PIPE, close_fds=True)
- proc.communicate()[0]
- log(logger, "avahi service terminated")
-
-
-def do_xmlrpc(bootapi, settings, port, logger):
-
- # This is the simple XMLRPC API we provide to koan and other
- # apps that do not need to manage Cobbler's config
-
- xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerXMLRPCInterface,False)
-
- server = remote.CobblerXMLRPCServer(('', port))
- server.logRequests = 0 # don't print stuff
- log(logger, "XMLRPC running on %s" % port)
- server.register_instance(xinterface)
-
- while True:
- try:
- server.serve_forever()
- except IOError:
- # interrupted? try to serve again
- time.sleep(0.5)
+#def do_avahi(bootapi, settings, logger):
+# # publish via zeroconf. This command will not terminate
+# log(logger, "publishing avahi service")
+# cmd = [ "/usr/bin/avahi-publish-service",
+# "cobblerd",
+# "_http._tcp",
+# "%s" % settings.xmlrpc_port ]
+# proc = sub_process.Popen(cmd, shell=False, stderr=sub_process.PIPE, stdout=sub_process.PIPE, close_fds=True)
+# proc.communicate()[0]
+# log(logger, "avahi service terminated")
-def do_xmlrpc_rw(bootapi,settings,port,logger):
- xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,True)
- server = remote.CobblerReadWriteXMLRPCServer(('127.0.0.1', port))
- server.logRequests = 0 # don't print stuff
- logger.debug("XMLRPC (read-write variant) running on %s" % port)
- server.register_instance(xinterface)
+def do_xmlrpc_rw(bootapi,settings,port):
- while True:
- try:
- server.serve_forever()
- except IOError:
- # interrupted? try to serve again
- time.sleep(0.5)
-
-def do_xmlrpc_unix(bootapi,settings,logger):
-
- xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,True)
- SOCKT = "/var/lib/cobbler/sock"
- server = xmlrpclib2.UnixXMLRPCServer(SOCKT)
+ xinterface = remote.ProxiedXMLRPCInterface(bootapi,remote.CobblerXMLRPCInterface,True)
+ server = remote.CobblerXMLRPCServer(('127.0.0.1', port))
server.logRequests = 0 # don't print stuff
- logger.debug("XMLRPC (socket variant) available on %s" % SOCKT)
+ xinterface.logger.debug("XMLRPC running on %s" % port)
server.register_instance(xinterface)
while True:
try:
+ print "SERVING!"
server.serve_forever()
except IOError:
# interrupted? try to serve again
time.sleep(0.5)
-def do_syslog(bootapi, settings, port, logger):
-
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- s.bind(("0.0.0.0", port))
- log(logger, "syslog running on %s" % port)
-
- buf = 1024
-
- while 1:
- data, addr = s.recvfrom(buf)
- (ip, port) = addr
- system = bootapi.systems().find(ip_address = ip)
- if not system:
- usename = ip
- else:
- usename = system.name
-
- if not data:
- break
- else:
- logfile = open("/var/log/cobbler/syslog/%s" % usename, "a+")
- t = time.localtime()
- # write numeric time
- seconds = str(time.mktime(t))
- logfile.write(seconds)
- logfile.write("\t")
- # write string time
- timestr = str(time.asctime(t))
- logfile.write(timestr)
- logfile.write("\t")
- # write the IP address of the client
- logfile.write(ip)
- logfile.write("\t")
- # write the data
- logfile.write(data)
- logfile.write("\n")
- logfile.close()
-
if __name__ == "__main__":
#main()
diff --git a/cobbler/collection.py b/cobbler/collection.py
index 564a9dec..3bc15cc7 100644
--- a/cobbler/collection.py
+++ b/cobbler/collection.py
@@ -148,6 +148,13 @@ class Collection(serializable.Serializable):
def copy(self,ref,newname):
ref.name = newname
ref.uid = self.config.generate_uid()
+ if ref.COLLECTION_TYPE == "system":
+ # this should only happen for systems
+ for iname in ref.interfaces.keys():
+ # clear all these out to avoid DHCP/DNS conflicts
+ ref.set_dns_name("",iname)
+ ref.set_mac_address("",iname)
+ ref.set_ip_address("",iname)
return self.add(ref,save=True,with_copy=True,with_triggers=True,with_sync=True,check_for_duplicate_names=True,check_for_duplicate_netinfo=False)
def rename(self,ref,newname,with_sync=True,with_triggers=True):
@@ -251,7 +258,7 @@ class Collection(serializable.Serializable):
self.log_func("saving %s %s" % (self.collection_type(), ref.name))
# failure of a pre trigger will prevent the object from being added
if with_triggers:
- self._run_triggers(ref,"/var/lib/cobbler/triggers/add/%s/pre/*" % self.collection_type())
+ self._run_triggers(self.api, ref,"/var/lib/cobbler/triggers/add/%s/pre/*" % self.collection_type())
self.listing[ref.name.lower()] = ref
# save just this item if possible, if not, save
@@ -277,17 +284,22 @@ class Collection(serializable.Serializable):
# save the tree, so if neccessary, scripts can examine it.
if with_triggers:
- self._run_triggers(ref,"/var/lib/cobbler/triggers/add/%s/post/*" % self.collection_type())
-
+ self._run_triggers(self.api, ref,"/var/lib/cobbler/triggers/add/%s/post/*" % self.collection_type())
+
+
# update children cache in parent object
parent = ref.get_parent()
if parent != None:
parent.children[ref.name] = ref
+ # signal remote cobblerd to update it's cache of this item.
+ if save and not self.api.is_cobblerd:
+ self.api._internal_cache_update(ref.COLLECTION_TYPE,ref.name)
+
return True
- def _run_triggers(self,ref,globber):
- return utils.run_triggers(ref,globber)
+ def _run_triggers(self,api_handle,ref,globber):
+ return utils.run_triggers(api_handle,ref,globber)
def __duplication_checks(self,ref,check_for_duplicate_names,check_for_duplicate_netinfo):
"""
@@ -307,6 +319,10 @@ class Collection(serializable.Serializable):
match = self.api.find_distro(ref.name)
elif isinstance(ref, item_repo.Repo):
match = self.api.find_repo(ref.name)
+ elif isinstance(ref, item_image.Image):
+ match = self.api.find_image(ref.name)
+ else:
+ raise CX("internal error, unknown object type")
if match:
raise CX(_("An object already exists with that name. Try 'edit'?"))
diff --git a/cobbler/collection_distros.py b/cobbler/collection_distros.py
index 1ca3ca92..ae75f407 100644
--- a/cobbler/collection_distros.py
+++ b/cobbler/collection_distros.py
@@ -60,11 +60,11 @@ class Distros(collection.Collection):
if recursive:
kids = obj.get_children()
for k in kids:
- self.config.api.remove_profile(k, recursive=True)
+ self.config.api.remove_profile(k, recursive=recursive, delete=with_delete, with_triggers=with_triggers)
if with_delete:
if with_triggers:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/distro/pre/*")
+ self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/distro/pre/*")
if with_sync:
lite_sync = action_litesync.BootLiteSync(self.config)
lite_sync.remove_single_profile(name)
@@ -75,7 +75,7 @@ class Distros(collection.Collection):
if with_delete:
self.log_func("deleted distro %s" % name)
if with_triggers:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/distro/post/*")
+ self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/distro/post/*")
# look through all mirrored directories and find if any directory is holding
# this particular distribution's kernel and initrd
@@ -99,7 +99,11 @@ class Distros(collection.Collection):
if not found:
utils.rmtree(path)
+ if with_delete and not self.api.is_cobblerd:
+ self.api._internal_cache_update("distro", name, remove=True)
+
return True
- raise CX(_("cannot delete object that does not exist: %s") % name)
+ #if not recursive:
+ # raise CX(_("cannot delete object that does not exist: %s") % name)
diff --git a/cobbler/collection_images.py b/cobbler/collection_images.py
index 12cd5c60..2dfeeac1 100644
--- a/cobbler/collection_images.py
+++ b/cobbler/collection_images.py
@@ -39,11 +39,25 @@ class Images(collection.Collection):
# but is left in for consistancy in the API. Unused.
name = name.lower()
+
+ # first see if any Groups use this distro
+ if not recursive:
+ for v in self.config.systems():
+ if v.image is not None and v.image.lower() == name:
+ raise CX(_("removal would orphan system: %s") % v.name)
+
obj = self.find(name=name)
+
if obj is not None:
+
+ if recursive:
+ kids = obj.get_children()
+ for k in kids:
+ self.config.api.remove_system(k, recursive=True)
+
if with_delete:
if with_triggers:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/image/pre/*")
+ self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/image/pre/*")
if with_sync:
lite_sync = action_litesync.BootLiteSync(self.config)
lite_sync.remove_single_image(name)
@@ -54,7 +68,11 @@ class Images(collection.Collection):
if with_delete:
self.log_func("deleted repo %s" % name)
if with_triggers:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/image/post/*")
+ self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/image/post/*")
return True
- raise CX(_("cannot delete an object that does not exist: %s") % name)
+ if with_delete and not self.api.is_cobblerd:
+ self.api._internal_cache_update("image", name, remove=True)
+
+ #else:
+ # raise CX(_("cannot delete an object that does not exist: %s") % name)
diff --git a/cobbler/collection_profiles.py b/cobbler/collection_profiles.py
index 5554eeab..bb27dea4 100644
--- a/cobbler/collection_profiles.py
+++ b/cobbler/collection_profiles.py
@@ -58,13 +58,13 @@ class Profiles(collection.Collection):
kids = obj.get_children()
for k in kids:
if k.COLLECTION_TYPE == "profile":
- self.config.api.remove_profile(k, recursive=True)
+ self.config.api.remove_profile(k, recursive=recursive, delete=with_delete, with_triggers=with_triggers)
else:
- self.config.api.remove_system(k)
+ self.config.api.remove_system(k, recursive=recursive, delete=with_delete, with_triggers=with_triggers)
if with_delete:
if with_triggers:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/profile/pre/*")
+ self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/profile/pre/*")
if with_sync:
lite_sync = action_litesync.BootLiteSync(self.config)
lite_sync.remove_single_profile(name)
@@ -73,7 +73,12 @@ class Profiles(collection.Collection):
if with_delete:
self.log_func("deleted profile %s" % name)
if with_triggers:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/profile/post/*")
+ self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/profile/post/*")
+
+ if with_delete and not self.api.is_cobblerd:
+ self.api._internal_cache_update("profile", name, remove=True)
+
return True
- raise CX(_("cannot delete an object that does not exist: %s") % name)
+ #if not recursive:
+ # raise CX(_("cannot delete an object that does not exist: %s") % name)
diff --git a/cobbler/collection_repos.py b/cobbler/collection_repos.py
index 8ce150d4..3b2c1c20 100644
--- a/cobbler/collection_repos.py
+++ b/cobbler/collection_repos.py
@@ -56,7 +56,7 @@ class Repos(collection.Collection):
if obj is not None:
if with_delete:
if with_triggers:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/repo/pre/*")
+ self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/repo/pre/*")
del self.listing[name]
self.config.serialize_delete(self, obj)
@@ -64,12 +64,16 @@ class Repos(collection.Collection):
if with_delete:
self.log_func("deleted repo %s" % name)
if with_triggers:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/repo/post/*")
+ self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/repo/post/*")
path = "/var/www/cobbler/repo_mirror/%s" % obj.name
if os.path.exists(path):
utils.rmtree(path)
+ if with_delete and not self.api.is_cobblerd:
+ self.api._internal_cache_update("repo", name, remove=True)
+
return True
- raise CX(_("cannot delete an object that does not exist: %s") % name)
+ #if not recursive:
+ # raise CX(_("cannot delete an object that does not exist: %s") % name)
diff --git a/cobbler/collection_systems.py b/cobbler/collection_systems.py
index 211c5da9..5a628248 100644
--- a/cobbler/collection_systems.py
+++ b/cobbler/collection_systems.py
@@ -52,7 +52,7 @@ class Systems(collection.Collection):
if with_delete:
if with_triggers:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/system/pre/*")
+ self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/system/pre/*")
if with_sync:
lite_sync = action_litesync.BootLiteSync(self.config)
lite_sync.remove_single_system(name)
@@ -61,9 +61,13 @@ class Systems(collection.Collection):
if with_delete:
self.log_func("deleted system %s" % name)
if with_triggers:
- self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/system/post/*")
+ self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/system/post/*")
+
+ if with_delete and not self.api.is_cobblerd:
+ self.api._internal_cache_update("system", name, remove=True)
return True
-
- raise CX(_("cannot delete an object that does not exist: %s") % name)
+
+ #if not recursive:
+ # raise CX(_("cannot delete an object that does not exist: %s") % name)
diff --git a/cobbler/commands.py b/cobbler/commands.py
index 98a3a3a7..4bb6ec33 100644
--- a/cobbler/commands.py
+++ b/cobbler/commands.py
@@ -307,7 +307,7 @@ class CobblerFunction:
if not self.options.name:
raise CX(_("name is required"))
if not recursive:
- collect_fn().remove(self.options.name,with_delete=True)
+ collect_fn().remove(self.options.name,with_delete=True,recursive=False)
else:
collect_fn().remove(self.options.name,with_delete=True,recursive=True)
return None # signal that we want no further processing on the object
@@ -332,6 +332,12 @@ class CobblerFunction:
raise CX(_("object not found"))
return obj
+ try:
+ # catch some invalid executions of the CLI
+ getattr(self, "options")
+ except:
+ sys.exit(1)
+
if not self.options.name:
raise CX(_("name is required"))
@@ -377,8 +383,18 @@ class CobblerFunction:
if "copy" in self.args:
if self.options.newname:
- obj = obj.make_clone()
- obj.set_name(self.options.newname)
+ # FIXME: this should just use the copy function!
+ if obj.COLLECTION_TYPE == "distro":
+ return self.api.copy_distro(obj, self.options.newname)
+ if obj.COLLECTION_TYPE == "profile":
+ return self.api.copy_profile(obj, self.options.newname)
+ if obj.COLLECTION_TYPE == "system":
+ return self.api.copy_system(obj, self.options.newname)
+ if obj.COLLECTION_TYPE == "repo":
+ return self.api.copy_repo(obj, self.options.newname)
+ if obj.COLLECTION_TYPE == "image":
+ return self.api.copy_image(obj, self.options.newname)
+ raise CX(_("internal error, don't know how to copy"))
else:
raise CX(_("--newname is required"))
diff --git a/cobbler/config.py b/cobbler/config.py
index 78a4d7ca..2608b84d 100644
--- a/cobbler/config.py
+++ b/cobbler/config.py
@@ -44,7 +44,7 @@ import settings
import serializer
from utils import _
-
+from cexceptions import *
class Config:
@@ -204,7 +204,10 @@ class Config:
"""
Load the object hierachy from disk, using the filenames referenced in each object.
"""
- serializer.deserialize(self._settings)
+ try:
+ serializer.deserialize(self._settings)
+ except:
+ raise CX("/etc/cobbler/settings is not a valid YAML file")
serializer.deserialize(self._distros)
serializer.deserialize(self._repos)
serializer.deserialize(self._profiles)
diff --git a/cobbler/demo_connect.py b/cobbler/demo_connect.py
index b9d5192e..12627b1e 100644
--- a/cobbler/demo_connect.py
+++ b/cobbler/demo_connect.py
@@ -31,7 +31,7 @@ if __name__ == "__main__":
# NOTE: if you've changed your xmlrpc_rw port or
# disabled xmlrpc_rw this test probably won't work
- sp = ServerProxy("http://127.0.0.1:25152")
+ sp = ServerProxy("http://127.0.0.1:25151")
(options, args) = p.parse_args()
print "- trying to login with user=%s" % options.user
token = sp.login(options.user,options.password)
diff --git a/cobbler/item.py b/cobbler/item.py
index a4ecea5b..31697a2f 100644
--- a/cobbler/item.py
+++ b/cobbler/item.py
@@ -138,7 +138,7 @@ class Item(serializable.Serializable):
like authz_ownership, which ships with Cobbler but is off by default. Consult the Wiki
docs for more info on CustomizableAuthorization.
"""
- owners = utils.input_string_or_list(data)
+ owners = utils.input_string_or_list(data, delim=" ")
self.owners = owners
return True
@@ -196,7 +196,8 @@ class Item(serializable.Serializable):
Assigns a list of configuration management classes that can be assigned
to any object, such as those used by Puppet's external_nodes feature.
"""
- self.mgmt_classes = utils.input_string_or_list(mgmt_classes)
+ mgmt_classes_split = utils.input_string_or_list(mgmt_classes, delim=" ")
+ self.mgmt_classes = utils.input_string_or_list(mgmt_classes_split)
return True
def set_template_files(self,template_files,inplace=False):
diff --git a/cobbler/item_distro.py b/cobbler/item_distro.py
index 9b34d2e7..9be4f7d3 100644
--- a/cobbler/item_distro.py
+++ b/cobbler/item_distro.py
@@ -41,24 +41,25 @@ class Distro(item.Item):
"""
Reset this object.
"""
- self.name = None
- self.uid = ""
- self.owners = self.settings.default_ownership
- self.kernel = None
- self.initrd = None
- self.kernel_options = {}
- self.kernel_options_post = {}
- self.ks_meta = {}
- self.arch = 'i386'
- self.breed = 'redhat'
- self.os_version = ''
- self.source_repos = []
- self.mgmt_classes = []
- self.depth = 0
- self.template_files = {}
- self.comment = ""
- self.tree_build_time = 0
- self.redhat_management_key = "<<inherit>>"
+ self.name = None
+ self.uid = ""
+ self.owners = self.settings.default_ownership
+ self.kernel = None
+ self.initrd = None
+ self.kernel_options = {}
+ self.kernel_options_post = {}
+ self.ks_meta = {}
+ self.arch = 'i386'
+ self.breed = 'redhat'
+ self.os_version = ''
+ self.source_repos = []
+ self.mgmt_classes = []
+ self.depth = 0
+ self.template_files = {}
+ self.comment = ""
+ self.tree_build_time = 0
+ self.redhat_management_key = "<<inherit>>"
+ self.redhat_management_server = "<<inherit>>"
def make_clone(self):
ds = self.to_datastruct()
@@ -77,23 +78,24 @@ class Distro(item.Item):
"""
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.owners = self.load_item(seed_data,'owners',self.settings.default_ownership)
- self.kernel = self.load_item(seed_data,'kernel')
- self.initrd = self.load_item(seed_data,'initrd')
- self.kernel_options = self.load_item(seed_data,'kernel_options')
- self.kernel_options_post = self.load_item(seed_data,'kernel_options_post')
- self.ks_meta = self.load_item(seed_data,'ks_meta')
- self.arch = self.load_item(seed_data,'arch','i386')
- self.breed = self.load_item(seed_data,'breed','redhat')
- self.os_version = self.load_item(seed_data,'os_version','')
- self.source_repos = self.load_item(seed_data,'source_repos',[])
- self.depth = self.load_item(seed_data,'depth',0)
- self.mgmt_classes = self.load_item(seed_data,'mgmt_classes',[])
- self.template_files = self.load_item(seed_data,'template_files',{})
- self.comment = self.load_item(seed_data,'comment')
- self.redhat_management_key = self.load_item(seed_data,'redhat_management_key',"<<inherit>>")
+ self.parent = self.load_item(seed_data,'parent')
+ self.name = self.load_item(seed_data,'name')
+ self.owners = self.load_item(seed_data,'owners',self.settings.default_ownership)
+ self.kernel = self.load_item(seed_data,'kernel')
+ self.initrd = self.load_item(seed_data,'initrd')
+ self.kernel_options = self.load_item(seed_data,'kernel_options')
+ self.kernel_options_post = self.load_item(seed_data,'kernel_options_post')
+ self.ks_meta = self.load_item(seed_data,'ks_meta')
+ self.arch = self.load_item(seed_data,'arch','i386')
+ self.breed = self.load_item(seed_data,'breed','redhat')
+ self.os_version = self.load_item(seed_data,'os_version','')
+ self.source_repos = self.load_item(seed_data,'source_repos',[])
+ self.depth = self.load_item(seed_data,'depth',0)
+ self.mgmt_classes = self.load_item(seed_data,'mgmt_classes',[])
+ self.template_files = self.load_item(seed_data,'template_files',{})
+ self.comment = self.load_item(seed_data,'comment')
+ self.redhat_management_key = self.load_item(seed_data,'redhat_management_key',"<<inherit>>")
+ self.redhat_management_server = self.load_item(seed_data,'redhat_management_server',"<<inherit>>")
# backwards compatibility enforcement
self.set_arch(self.arch)
@@ -159,6 +161,9 @@ class Distro(item.Item):
def set_redhat_management_key(self,key):
return utils.set_redhat_management_key(self,key)
+
+ def set_redhat_management_server(self,server):
+ return utils.set_redhat_management_server(self,server)
def set_source_repos(self, repos):
"""
@@ -209,27 +214,28 @@ class Distro(item.Item):
Return a serializable datastructure representation of this object.
"""
return {
- 'name' : self.name,
- 'kernel' : self.kernel,
- 'initrd' : self.initrd,
- 'kernel_options' : self.kernel_options,
- 'kernel_options_post' : self.kernel_options_post,
- 'ks_meta' : self.ks_meta,
- 'mgmt_classes' : self.mgmt_classes,
- 'template_files' : self.template_files,
- 'arch' : self.arch,
- 'breed' : self.breed,
- 'os_version' : self.os_version,
- 'source_repos' : self.source_repos,
- 'parent' : self.parent,
- 'depth' : self.depth,
- 'owners' : self.owners,
- 'comment' : self.comment,
- 'tree_build_time' : self.tree_build_time,
- 'ctime' : self.ctime,
- 'mtime' : self.mtime,
- 'uid' : self.uid,
- 'redhat_management_key' : self.redhat_management_key
+ 'name' : self.name,
+ 'kernel' : self.kernel,
+ 'initrd' : self.initrd,
+ 'kernel_options' : self.kernel_options,
+ 'kernel_options_post' : self.kernel_options_post,
+ 'ks_meta' : self.ks_meta,
+ 'mgmt_classes' : self.mgmt_classes,
+ 'template_files' : self.template_files,
+ 'arch' : self.arch,
+ 'breed' : self.breed,
+ 'os_version' : self.os_version,
+ 'source_repos' : self.source_repos,
+ 'parent' : self.parent,
+ 'depth' : self.depth,
+ 'owners' : self.owners,
+ 'comment' : self.comment,
+ 'tree_build_time' : self.tree_build_time,
+ 'ctime' : self.ctime,
+ 'mtime' : self.mtime,
+ 'uid' : self.uid,
+ 'redhat_management_key' : self.redhat_management_key,
+ 'redhat_management_server' : self.redhat_management_server
}
def printable(self):
@@ -257,28 +263,30 @@ class Distro(item.Item):
buf = buf + _("owners : %s\n") % self.owners
buf = buf + _("post kernel options : %s\n") % self.kernel_options_post
buf = buf + _("redhat mgmt key : %s\n") % self.redhat_management_key
+ buf = buf + _("redhat mgmt server : %s\n") % self.redhat_management_server
buf = buf + _("template files : %s\n") % self.template_files
return buf
def remote_methods(self):
return {
- 'name' : self.set_name,
- 'kernel' : self.set_kernel,
- 'initrd' : self.set_initrd,
- 'kopts' : self.set_kernel_options,
- 'kopts-post' : self.set_kernel_options_post,
- 'kopts_post' : self.set_kernel_options_post,
- 'arch' : self.set_arch,
- 'ksmeta' : self.set_ksmeta,
- 'breed' : self.set_breed,
- 'os-version' : self.set_os_version,
- 'os_version' : self.set_os_version,
- 'owners' : self.set_owners,
- 'mgmt-classes' : self.set_mgmt_classes,
- 'mgmt_classes' : self.set_mgmt_classes,
- 'template-files': self.set_template_files,
- 'template_files': self.set_template_files,
- 'comment' : self.set_comment,
- 'redhat_management_key' : self.set_redhat_management_key
+ 'name' : self.set_name,
+ 'kernel' : self.set_kernel,
+ 'initrd' : self.set_initrd,
+ 'kopts' : self.set_kernel_options,
+ 'kopts-post' : self.set_kernel_options_post,
+ 'kopts_post' : self.set_kernel_options_post,
+ 'arch' : self.set_arch,
+ 'ksmeta' : self.set_ksmeta,
+ 'breed' : self.set_breed,
+ 'os-version' : self.set_os_version,
+ 'os_version' : self.set_os_version,
+ 'owners' : self.set_owners,
+ 'mgmt-classes' : self.set_mgmt_classes,
+ 'mgmt_classes' : self.set_mgmt_classes,
+ 'template-files' : self.set_template_files,
+ 'template_files' : self.set_template_files,
+ 'comment' : self.set_comment,
+ 'redhat_management_key' : self.set_redhat_management_key,
+ 'redhat_management_server' : self.set_redhat_management_server
}
diff --git a/cobbler/item_image.py b/cobbler/item_image.py
index 98e02558..5a4123b2 100644
--- a/cobbler/item_image.py
+++ b/cobbler/item_image.py
@@ -113,11 +113,49 @@ class Image(item.Item):
def set_file(self,filename):
"""
Stores the image location. This should be accessible on all nodes
- that need to access it. Format: either /mnt/commonpath/foo.iso or
- nfs://host/path/foo.iso
+ that need to access it. Format: can be one of the following:
+ * username:password@hostname:/path/to/the/filename.ext
+ * username@hostname:/path/to/the/filename.ext
+ * hostname:/path/to/the/filename.ext
+ * /path/to/the/filename.ext
"""
- # FIXME: this should accept NFS paths or filesystem paths
- self.file = filename
+ uri = ""
+ scheme = auth = hostname = path = ""
+ # we'll discard the protocol if it's supplied, for legacy support
+ if filename.find("://") != -1:
+ scheme, uri = filename.split("://")
+ filename = uri
+ else:
+ uri = filename
+
+ if filename.find("@") != -1:
+ auth, filename = filename.split("@")
+ # extract the hostname
+ # 1. if we have a colon, then everything before it is a hostname
+ # 2. if we don't have a colon, then check if we had a scheme; if
+ # we did, then grab all before the first forward slash as the
+ # hostname; otherwise, we've got a bad file
+ if filename.find(":") != -1:
+ hostname, filename = filename.split(":")
+ elif filename[0] != '/':
+ if len(scheme) > 0:
+ index = filename.find("/")
+ hostname = filename[:index]
+ filename = filename[index:]
+ else:
+ raise CX(_("invalid file: %s" % filename))
+ # raise an exception if we don't have a valid path
+ if len(filename) > 0 and filename[0] != '/':
+ raise CX(_("file contains an invalid path: %s" % filename))
+ if filename.find("/") != -1:
+ path, filename = filename.rsplit("/", 1)
+
+ if len(filename) == 0:
+ raise CX(_("missing filename"))
+ if len(auth) > 0 and len(hostname) == 0:
+ raise CX(_("a hostname must be specified with authentication details"))
+
+ self.file = uri
return True
def set_os_version(self,os_version):
diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py
index d30eb52a..3f9e9460 100644
--- a/cobbler/item_profile.py
+++ b/cobbler/item_profile.py
@@ -42,60 +42,64 @@ class Profile(item.Item):
"""
Reset this object.
"""
- self.name = None
- self.uid = ""
- self.random_id = ""
- self.owners = self.settings.default_ownership
- self.distro = (None, '<<inherit>>')[is_subobject]
- self.enable_menu = (self.settings.enable_menu, '<<inherit>>')[is_subobject]
- self.kickstart = (self.settings.default_kickstart , '<<inherit>>')[is_subobject]
- self.kernel_options = ({}, '<<inherit>>')[is_subobject]
- self.kernel_options_post = ({}, '<<inherit>>')[is_subobject]
- self.ks_meta = ({}, '<<inherit>>')[is_subobject]
- self.template_files = ({}, '<<inherit>>')[is_subobject]
- self.virt_cpus = (1, '<<inherit>>')[is_subobject]
- self.virt_file_size = (self.settings.default_virt_file_size, '<<inherit>>')[is_subobject]
- self.virt_ram = (self.settings.default_virt_ram, '<<inherit>>')[is_subobject]
- self.repos = ([], '<<inherit>>')[is_subobject]
- self.depth = 1
- self.virt_type = (self.settings.default_virt_type, '<<inherit>>')[is_subobject]
- self.virt_path = ("", '<<inherit>>')[is_subobject]
- self.virt_bridge = (self.settings.default_virt_bridge, '<<inherit>>')[is_subobject]
- self.dhcp_tag = ("default", '<<inherit>>')[is_subobject]
- self.mgmt_classes = ([], '<<inherit>>')[is_subobject]
- self.parent = ''
- self.server = "<<inherit>>"
- self.comment = ""
- self.ctime = 0
- self.mtime = 0
- self.name_servers = (self.settings.default_name_servers, '<<inherit>>')[is_subobject]
- self.redhat_management_key = "<<inherit>>"
+ self.name = None
+ self.uid = ""
+ self.random_id = ""
+ self.owners = self.settings.default_ownership
+ self.distro = (None, '<<inherit>>')[is_subobject]
+ self.enable_menu = (self.settings.enable_menu, '<<inherit>>')[is_subobject]
+ self.kickstart = (self.settings.default_kickstart , '<<inherit>>')[is_subobject]
+ self.kernel_options = ({}, '<<inherit>>')[is_subobject]
+ self.kernel_options_post = ({}, '<<inherit>>')[is_subobject]
+ self.ks_meta = ({}, '<<inherit>>')[is_subobject]
+ self.template_files = ({}, '<<inherit>>')[is_subobject]
+ self.virt_cpus = (1, '<<inherit>>')[is_subobject]
+ self.virt_file_size = (self.settings.default_virt_file_size, '<<inherit>>')[is_subobject]
+ self.virt_ram = (self.settings.default_virt_ram, '<<inherit>>')[is_subobject]
+ self.repos = ([], '<<inherit>>')[is_subobject]
+ self.depth = 1
+ self.virt_type = (self.settings.default_virt_type, '<<inherit>>')[is_subobject]
+ self.virt_path = ("", '<<inherit>>')[is_subobject]
+ self.virt_bridge = (self.settings.default_virt_bridge, '<<inherit>>')[is_subobject]
+ self.dhcp_tag = ("default", '<<inherit>>')[is_subobject]
+ self.mgmt_classes = ([], '<<inherit>>')[is_subobject]
+ self.parent = ''
+ self.server = "<<inherit>>"
+ self.comment = ""
+ self.ctime = 0
+ self.mtime = 0
+ self.name_servers = (self.settings.default_name_servers,[])[is_subobject]
+ self.name_servers_search = (self.settings.default_name_servers_search,[])[is_subobject]
+ self.redhat_management_key = "<<inherit>>"
+ self.redhat_management_server = "<<inherit>>"
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.owners = self.load_item(seed_data,'owners',self.settings.default_ownership)
- self.distro = self.load_item(seed_data,'distro')
- self.enable_menu = self.load_item(seed_data,'enable_menu', self.settings.enable_menu)
- self.kickstart = self.load_item(seed_data,'kickstart')
- self.kernel_options = self.load_item(seed_data,'kernel_options')
- self.kernel_options_post = self.load_item(seed_data,'kernel_options_post')
- self.ks_meta = self.load_item(seed_data,'ks_meta')
- self.template_files = self.load_item(seed_data,'template_files', {})
- self.repos = self.load_item(seed_data,'repos', [])
- self.depth = self.load_item(seed_data,'depth', 1)
- self.dhcp_tag = self.load_item(seed_data,'dhcp_tag', 'default')
- self.server = self.load_item(seed_data,'server', '<<inherit>>')
- self.mgmt_classes = self.load_item(seed_data,'mgmt_classes', [])
- self.comment = self.load_item(seed_data,'comment','')
- self.ctime = self.load_item(seed_data,'ctime',0)
- self.mtime = self.load_item(seed_data,'mtime',0)
- self.name_servers = self.load_item(seed_data,'name_servers',[])
- self.redhat_management_key = self.load_item(seed_data,'redhat_management_key', '<<inherit>>')
+ self.parent = self.load_item(seed_data,'parent','')
+ self.name = self.load_item(seed_data,'name')
+ self.owners = self.load_item(seed_data,'owners',self.settings.default_ownership)
+ self.distro = self.load_item(seed_data,'distro')
+ self.enable_menu = self.load_item(seed_data,'enable_menu', self.settings.enable_menu)
+ self.kickstart = self.load_item(seed_data,'kickstart')
+ self.kernel_options = self.load_item(seed_data,'kernel_options')
+ self.kernel_options_post = self.load_item(seed_data,'kernel_options_post')
+ self.ks_meta = self.load_item(seed_data,'ks_meta')
+ self.template_files = self.load_item(seed_data,'template_files', {})
+ self.repos = self.load_item(seed_data,'repos', [])
+ self.depth = self.load_item(seed_data,'depth', 1)
+ self.dhcp_tag = self.load_item(seed_data,'dhcp_tag', 'default')
+ self.server = self.load_item(seed_data,'server', '<<inherit>>')
+ self.mgmt_classes = self.load_item(seed_data,'mgmt_classes', [])
+ self.comment = self.load_item(seed_data,'comment','')
+ self.ctime = self.load_item(seed_data,'ctime',0)
+ self.mtime = self.load_item(seed_data,'mtime',0)
+ self.name_servers = self.load_item(seed_data,'name_servers',[])
+ self.name_servers_search = self.load_item(seed_data,'name_servers_search',[])
+ self.redhat_management_key = self.load_item(seed_data,'redhat_management_key', '<<inherit>>')
+ self.redhat_management_server = self.load_item(seed_data,'redhat_management_server', '<<inherit>>')
# backwards compatibility
if type(self.repos) != list:
@@ -179,11 +183,20 @@ class Profile(item.Item):
def set_redhat_management_key(self,key):
return utils.set_redhat_management_key(self,key)
+ def set_redhat_management_server(self,server):
+ return utils.set_redhat_management_server(self,server)
+
def set_name_servers(self,data):
- data = utils.input_string_or_list(data)
+ # FIXME: move to utils since shared with system
+ data = utils.input_string_or_list(data, delim=" ")
self.name_servers = data
return True
+ def set_name_servers_search(self,data):
+ data = utils.input_string_or_list(data, delim=" ")
+ self.name_servers_search = data
+ return True
+
def set_enable_menu(self,enable_menu):
"""
Sets whether or not the profile will be listed in the default
@@ -200,7 +213,7 @@ class Profile(item.Item):
def set_server(self,server):
if server is None or server == "":
- server = "<inherit>"
+ server = "<<inherit>>"
self.server = server
return True
@@ -279,34 +292,36 @@ class Profile(item.Item):
Return hash representation for the serializer
"""
return {
- 'name' : self.name,
- 'owners' : self.owners,
- 'distro' : self.distro,
- 'enable_menu' : self.enable_menu,
- 'kickstart' : self.kickstart,
- 'kernel_options' : self.kernel_options,
- 'kernel_options_post' : self.kernel_options_post,
- 'virt_file_size' : self.virt_file_size,
- 'virt_ram' : self.virt_ram,
- 'virt_bridge' : self.virt_bridge,
- 'virt_cpus' : self.virt_cpus,
- 'ks_meta' : self.ks_meta,
- 'template_files' : self.template_files,
- 'repos' : self.repos,
- 'parent' : self.parent,
- 'depth' : self.depth,
- 'virt_type' : self.virt_type,
- 'virt_path' : self.virt_path,
- 'dhcp_tag' : self.dhcp_tag,
- 'server' : self.server,
- 'mgmt_classes' : self.mgmt_classes,
- 'comment' : self.comment,
- 'ctime' : self.ctime,
- 'mtime' : self.mtime,
- 'name_servers' : self.name_servers,
- 'uid' : self.uid,
- 'random_id' : self.random_id,
- 'redhat_management_key' : self.redhat_management_key
+ 'name' : self.name,
+ 'owners' : self.owners,
+ 'distro' : self.distro,
+ 'enable_menu' : self.enable_menu,
+ 'kickstart' : self.kickstart,
+ 'kernel_options' : self.kernel_options,
+ 'kernel_options_post' : self.kernel_options_post,
+ 'virt_file_size' : self.virt_file_size,
+ 'virt_ram' : self.virt_ram,
+ 'virt_bridge' : self.virt_bridge,
+ 'virt_cpus' : self.virt_cpus,
+ 'ks_meta' : self.ks_meta,
+ 'template_files' : self.template_files,
+ 'repos' : self.repos,
+ 'parent' : self.parent,
+ 'depth' : self.depth,
+ 'virt_type' : self.virt_type,
+ 'virt_path' : self.virt_path,
+ 'dhcp_tag' : self.dhcp_tag,
+ 'server' : self.server,
+ 'mgmt_classes' : self.mgmt_classes,
+ 'comment' : self.comment,
+ 'ctime' : self.ctime,
+ 'mtime' : self.mtime,
+ 'name_servers' : self.name_servers,
+ 'name_servers_search' : self.name_servers_search,
+ 'uid' : self.uid,
+ 'random_id' : self.random_id,
+ 'redhat_management_key' : self.redhat_management_key,
+ 'redhat_management_server' : self.redhat_management_server
}
def printable(self):
@@ -328,9 +343,11 @@ class Profile(item.Item):
buf = buf + _("mgmt classes : %s\n") % self.mgmt_classes
buf = buf + _("modified : %s\n") % time.ctime(self.mtime)
buf = buf + _("name servers : %s\n") % self.name_servers
+ buf = buf + _("name servers search : %s\n") % self.name_servers_search
buf = buf + _("owners : %s\n") % self.owners
buf = buf + _("post kernel options : %s\n") % self.kernel_options_post
buf = buf + _("redhat mgmt key : %s\n") % self.redhat_management_key
+ buf = buf + _("redhat mgmt server : %s\n") % self.redhat_management_server
buf = buf + _("repos : %s\n") % self.repos
buf = buf + _("server : %s\n") % self.server
buf = buf + _("template_files : %s\n") % self.template_files
@@ -345,40 +362,42 @@ class Profile(item.Item):
def remote_methods(self):
return {
- 'name' : self.set_name,
- 'parent' : self.set_parent,
- 'profile' : self.set_name,
- 'distro' : self.set_distro,
- 'enable-menu' : self.set_enable_menu,
- 'enable_menu' : self.set_enable_menu,
- 'kickstart' : self.set_kickstart,
- 'kopts' : self.set_kernel_options,
- 'kopts-post' : self.set_kernel_options_post,
- 'kopts_post' : self.set_kernel_options_post,
- 'virt-file-size' : self.set_virt_file_size,
- 'virt_file_size' : self.set_virt_file_size,
- 'virt-ram' : self.set_virt_ram,
- 'virt_ram' : self.set_virt_ram,
- 'ksmeta' : self.set_ksmeta,
- 'template-files' : self.set_template_files,
- 'template_files' : self.set_template_files,
- 'repos' : self.set_repos,
- 'virt-path' : self.set_virt_path,
- 'virt_path' : self.set_virt_path,
- 'virt-type' : self.set_virt_type,
- 'virt_type' : self.set_virt_type,
- 'virt-bridge' : self.set_virt_bridge,
- 'virt_bridge' : self.set_virt_bridge,
- 'virt-cpus' : self.set_virt_cpus,
- 'virt_cpus' : self.set_virt_cpus,
- 'dhcp-tag' : self.set_dhcp_tag,
- 'dhcp_tag' : self.set_dhcp_tag,
- 'server' : self.set_server,
- 'owners' : self.set_owners,
- 'mgmt-classes' : self.set_mgmt_classes,
- 'mgmt_classes' : self.set_mgmt_classes,
- 'comment' : self.set_comment,
- 'name_servers' : self.set_name_servers,
- 'redhat_management_key' : self.set_redhat_management_key
+ 'name' : self.set_name,
+ 'parent' : self.set_parent,
+ 'profile' : self.set_name,
+ 'distro' : self.set_distro,
+ 'enable-menu' : self.set_enable_menu,
+ 'enable_menu' : self.set_enable_menu,
+ 'kickstart' : self.set_kickstart,
+ 'kopts' : self.set_kernel_options,
+ 'kopts-post' : self.set_kernel_options_post,
+ 'kopts_post' : self.set_kernel_options_post,
+ 'virt-file-size' : self.set_virt_file_size,
+ 'virt_file_size' : self.set_virt_file_size,
+ 'virt-ram' : self.set_virt_ram,
+ 'virt_ram' : self.set_virt_ram,
+ 'ksmeta' : self.set_ksmeta,
+ 'template-files' : self.set_template_files,
+ 'template_files' : self.set_template_files,
+ 'repos' : self.set_repos,
+ 'virt-path' : self.set_virt_path,
+ 'virt_path' : self.set_virt_path,
+ 'virt-type' : self.set_virt_type,
+ 'virt_type' : self.set_virt_type,
+ 'virt-bridge' : self.set_virt_bridge,
+ 'virt_bridge' : self.set_virt_bridge,
+ 'virt-cpus' : self.set_virt_cpus,
+ 'virt_cpus' : self.set_virt_cpus,
+ 'dhcp-tag' : self.set_dhcp_tag,
+ 'dhcp_tag' : self.set_dhcp_tag,
+ 'server' : self.set_server,
+ 'owners' : self.set_owners,
+ 'mgmt-classes' : self.set_mgmt_classes,
+ 'mgmt_classes' : self.set_mgmt_classes,
+ 'comment' : self.set_comment,
+ 'name_servers' : self.set_name_servers,
+ 'name_servers_search' : self.set_name_servers_search,
+ 'redhat_management_key' : self.set_redhat_management_key,
+ 'redhat_management_server' : self.set_redhat_management_server
}
diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py
index 3a62a21f..02622004 100644
--- a/cobbler/item_repo.py
+++ b/cobbler/item_repo.py
@@ -83,6 +83,7 @@ class Repo(item.Item):
self.set_mirror_locally(self.mirror_locally)
self.set_owners(self.owners)
self.set_environment(self.environment)
+ self.set_arch(self.arch)
self._guess_breed()
self.uid = self.load_item(seed_data,'uid','')
@@ -177,17 +178,7 @@ class Repo(item.Item):
contains games, and we probably don't want those), make it possible to list the packages
one wants out of those repos, so only those packages + deps can be mirrored.
"""
- if rpms is None:
- rpms = ""
- if type(rpms) != list:
- rpmlist = rpms.split(None)
- else:
- rpmlist = rpms
- try:
- rpmlist.remove('')
- except:
- pass
- self.rpm_list = rpmlist
+ self.rpm_list = utils.input_string_or_list(rpms,delim=" ")
return True
def set_createrepo_flags(self,createrepo_flags):
@@ -208,7 +199,7 @@ class Repo(item.Item):
"""
Override the arch used for reposync
"""
- return utils.set_arch(self,arch)
+ return utils.set_arch(self,arch,repo=True)
def is_valid(self):
"""
diff --git a/cobbler/item_system.py b/cobbler/item_system.py
index d1411092..b7e77448 100644
--- a/cobbler/item_system.py
+++ b/cobbler/item_system.py
@@ -38,65 +38,65 @@ class System(item.Item):
return cloned
def clear(self,is_subobject=False):
- self.name = None
- self.uid = ""
- self.owners = self.settings.default_ownership
- self.profile = None
- self.image = None
- self.kernel_options = {}
- self.kernel_options_post = {}
- self.ks_meta = {}
- self.interfaces = {}
- self.netboot_enabled = True
- self.depth = 2
- self.mgmt_classes = []
- self.template_files = {}
- self.kickstart = "<<inherit>>" # use value in profile
- self.server = "<<inherit>>" # "" (or settings)
- self.virt_path = "<<inherit>>" # ""
- self.virt_type = "<<inherit>>" # ""
- self.virt_cpus = "<<inherit>>" # ""
- self.virt_file_size = "<<inherit>>" # ""
- self.virt_ram = "<<inherit>>" # ""
- self.virt_type = "<<inherit>>" # ""
- self.virt_path = "<<inherit>>" # ""
- self.virt_bridge = "<<inherit>>" # ""
- self.comment = ""
- self.ctime = 0
- self.mtime = 0
- self.uid = ""
- self.random_id = ""
- self.power_type = self.settings.power_management_default_type
- self.power_address = ""
- self.power_user = ""
- self.power_pass = ""
- self.power_id = ""
- self.hostname = ""
- self.gateway = ""
- self.name_servers = ""
- self.bonding = ""
- self.bonding_master = ""
- self.bonding_opts = ""
- self.redhat_management_key = "<<inherit>>"
+ self.name = None
+ self.uid = ""
+ self.owners = self.settings.default_ownership
+ self.profile = None
+ self.image = None
+ self.kernel_options = {}
+ self.kernel_options_post = {}
+ self.ks_meta = {}
+ self.interfaces = {}
+ self.netboot_enabled = True
+ self.depth = 2
+ self.mgmt_classes = []
+ self.template_files = {}
+ self.kickstart = "<<inherit>>" # use value in profile
+ self.server = "<<inherit>>" # "" (or settings)
+ self.virt_path = "<<inherit>>" # ""
+ self.virt_type = "<<inherit>>" # ""
+ self.virt_cpus = "<<inherit>>" # ""
+ self.virt_file_size = "<<inherit>>" # ""
+ self.virt_ram = "<<inherit>>" # ""
+ self.virt_type = "<<inherit>>" # ""
+ self.virt_path = "<<inherit>>" # ""
+ self.virt_bridge = "<<inherit>>" # ""
+ self.comment = ""
+ self.ctime = 0
+ self.mtime = 0
+ self.uid = ""
+ self.random_id = ""
+ self.power_type = self.settings.power_management_default_type
+ self.power_address = ""
+ self.power_user = ""
+ self.power_pass = ""
+ self.power_id = ""
+ self.hostname = ""
+ self.gateway = ""
+ self.name_servers = []
+ self.name_servers_search = []
+ self.bonding = ""
+ self.bonding_master = ""
+ self.bonding_opts = ""
+ self.redhat_management_key = "<<inherit>>"
+ self.redhat_management_server = "<<inherit>>"
def delete_interface(self,name):
"""
- Used to remove an interface. Not valid for the default
-interface.
+ Used to remove an interface.
"""
- if self.interfaces.has_key(name) and name != "eth0":
+ if self.interfaces.has_key(name) and len(self.interfaces) > 1:
del self.interfaces[name]
else:
- if name == "eth0":
- raise CX(_("Interface %s can never be deleted") % name)
- else:
+ if not self.interfaces.has_key(name):
raise CX(_("Cannot delete interface that is not present: %s") % name)
+ else:
+ raise CX(_("At least one interface needs to be defined."))
+
return True
def __get_interface(self,name):
- if name is None:
- return self.__get_default_interface()
if not self.interfaces.has_key(name):
self.interfaces[name] = {
@@ -115,8 +115,6 @@ interface.
return self.interfaces[name]
- def __get_default_interface(self):
- return self.__get_interface("eth0")
def from_datastruct(self,seed_data):
@@ -179,7 +177,9 @@ interface.
self.hostname = self.load_item(seed_data, 'hostname', __hostname)
self.name_servers = self.load_item(seed_data, 'name_servers', '<<inherit>>')
+ self.name_servers_search = self.load_item(seed_data, 'name_servers_search', '<<inherit>>')
self.redhat_management_key = self.load_item(seed_data, 'redhat_management_key', '<<inherit>>')
+ self.redhat_management_server = self.load_item(seed_data, 'redhat_management_server', '<<inherit>>')
# virt specific
@@ -290,6 +290,12 @@ interface.
self.set_image(self.image)
self.set_profile(self.profile)
+
+ # enforce that the system extends from a profile or system but not both
+ # profile wins as it's the more common usage
+ self.set_image(self.image)
+ self.set_profile(self.profile)
+
return self
def get_parent(self):
@@ -308,8 +314,6 @@ interface.
Set the name. If the name is a MAC or IP, and the first MAC and/or IP is not defined, go ahead
and fill that value in.
"""
- intf = self.__get_default_interface()
-
if self.name not in ["",None] and self.parent not in ["",None] and self.name == self.parent:
raise CX(_("self parentage is weird"))
@@ -319,10 +323,15 @@ interface.
if not x.isalnum() and not x in [ "_", "-", ".", ":", "+" ] :
raise CX(_("invalid characters in name: %s") % x)
+ # Stuff here defaults to eth0. Yes, it's ugly and hardcoded, but so was
+ # the default interface behaviour that's now removed. ;)
+ # --Jasper Capel
if utils.is_mac(name):
+ intf = self.__get_interface("eth0")
if intf["mac_address"] == "":
intf["mac_address"] = name
elif utils.is_ip(name):
+ intf = self.__get_interface("eth0")
if intf["ip_address"] == "":
intf["ip_address"] = name
self.name = name
@@ -332,6 +341,9 @@ interface.
def set_redhat_management_key(self,key):
return utils.set_redhat_management_key(self,key)
+ def set_redhat_management_server(self,server):
+ return utils.set_redhat_management_server(self,server)
+
def set_server(self,server):
"""
If a system can't reach the boot server at the value configured in settings
@@ -351,7 +363,7 @@ interface.
intf = self.__get_interface(interface)
if intf["mac_address"] != "":
- return intf["mac_address"]
+ return intf["mac_address"].strip()
else:
return None
@@ -364,7 +376,7 @@ interface.
intf = self.__get_interface(interface)
if intf["ip_address"] != "":
- return intf["ip_address"]
+ return intf["ip_address"].strip()
else:
return None
@@ -387,12 +399,6 @@ interface.
return True
return False
- def set_default_interface(self,interface):
- if self.interfaces.has_key(interface):
- self.default_interface = interface
- else:
- raise CX(_("invalid interface (%s)") % interface)
-
def set_dhcp_tag(self,dhcp_tag,interface):
intf = self.__get_interface(interface)
intf["dhcp_tag"] = dhcp_tag
@@ -427,7 +433,7 @@ interface.
"""
intf = self.__get_interface(interface)
if address == "" or utils.is_ip(address):
- intf["ip_address"] = address
+ intf["ip_address"] = address.strip()
return True
raise CX(_("invalid format for IP address (%s)") % address)
@@ -445,16 +451,23 @@ interface.
return True
def set_name_servers(self,data):
- data = utils.input_string_or_list(data)
+ data = utils.input_string_or_list(data, delim=" ")
self.name_servers = data
return True
+ def set_name_servers_search(self,data):
+ data = utils.input_string_or_list(data, delim=" ")
+ self.name_servers_search = data
+ return True
+
def set_subnet(self,subnet,interface):
intf = self.__get_interface(interface)
intf["subnet"] = subnet
return True
def set_virt_bridge(self,bridge,interface):
+ if bridge == "":
+ bridge = self.settings.default_virt_bridge
intf = self.__get_interface(interface)
intf["virt_bridge"] = bridge
return True
@@ -591,7 +604,7 @@ interface.
if power_type is None:
power_type = ""
power_type = power_type.lower()
- valid = "bullpap wti apc_snmp ether-wake ipmilan drac ipmitool ilo rsai lpar bladecenter virsh none"
+ valid = "bullpap wti apc_snmp ether-wake ipmilan drac ipmitool ilo rsa lpar bladecenter virsh integrity none"
choices = valid.split(" ")
choices.sort()
if power_type not in choices:
@@ -629,41 +642,43 @@ interface.
def to_datastruct(self):
return {
- 'name' : self.name,
- 'uid' : self.uid,
- 'random_id' : self.random_id,
- 'kernel_options' : self.kernel_options,
- 'kernel_options_post' : self.kernel_options_post,
- 'depth' : self.depth,
- 'interfaces' : self.interfaces,
- 'ks_meta' : self.ks_meta,
- 'kickstart' : self.kickstart,
- 'netboot_enabled' : self.netboot_enabled,
- 'owners' : self.owners,
- 'parent' : self.parent,
- 'profile' : self.profile,
- 'image' : self.image,
- 'server' : self.server,
- 'virt_cpus' : self.virt_cpus,
- 'virt_bridge' : self.virt_bridge,
- 'virt_file_size' : self.virt_file_size,
- 'virt_path' : self.virt_path,
- 'virt_ram' : self.virt_ram,
- 'virt_type' : self.virt_type,
- 'mgmt_classes' : self.mgmt_classes,
- 'template_files' : self.template_files,
- 'comment' : self.comment,
- 'ctime' : self.ctime,
- 'mtime' : self.mtime,
- 'power_type' : self.power_type,
- 'power_address' : self.power_address,
- 'power_user' : self.power_user,
- 'power_pass' : self.power_pass,
- 'power_id' : self.power_id,
- 'hostname' : self.hostname,
- 'gateway' : self.gateway,
- 'name_servers' : self.name_servers,
- 'redhat_management_key' : self.redhat_management_key
+ 'name' : self.name,
+ 'uid' : self.uid,
+ 'random_id' : self.random_id,
+ 'kernel_options' : self.kernel_options,
+ 'kernel_options_post' : self.kernel_options_post,
+ 'depth' : self.depth,
+ 'interfaces' : self.interfaces,
+ 'ks_meta' : self.ks_meta,
+ 'kickstart' : self.kickstart,
+ 'netboot_enabled' : self.netboot_enabled,
+ 'owners' : self.owners,
+ 'parent' : self.parent,
+ 'profile' : self.profile,
+ 'image' : self.image,
+ 'server' : self.server,
+ 'virt_cpus' : self.virt_cpus,
+ 'virt_bridge' : self.virt_bridge,
+ 'virt_file_size' : self.virt_file_size,
+ 'virt_path' : self.virt_path,
+ 'virt_ram' : self.virt_ram,
+ 'virt_type' : self.virt_type,
+ 'mgmt_classes' : self.mgmt_classes,
+ 'template_files' : self.template_files,
+ 'comment' : self.comment,
+ 'ctime' : self.ctime,
+ 'mtime' : self.mtime,
+ 'power_type' : self.power_type,
+ 'power_address' : self.power_address,
+ 'power_user' : self.power_user,
+ 'power_pass' : self.power_pass,
+ 'power_id' : self.power_id,
+ 'hostname' : self.hostname,
+ 'gateway' : self.gateway,
+ 'name_servers' : self.name_servers,
+ 'name_servers_search' : self.name_servers_search,
+ 'redhat_management_key' : self.redhat_management_key,
+ 'redhat_management_server' : self.redhat_management_server
}
def printable(self):
@@ -682,6 +697,7 @@ interface.
buf = buf + _("modified : %s\n") % time.ctime(self.mtime)
buf = buf + _("name servers : %s\n") % self.name_servers
+ buf = buf + _("name servers search : %s\n") % self.name_servers_search
buf = buf + _("netboot enabled? : %s\n") % self.netboot_enabled
buf = buf + _("owners : %s\n") % self.owners
buf = buf + _("server : %s\n") % self.server
@@ -745,50 +761,52 @@ interface.
# compatibility. At some point they may be removed.
return {
- 'name' : self.set_name,
- 'profile' : self.set_profile,
- 'image' : self.set_image,
- 'kopts' : self.set_kernel_options,
- 'kopts-post' : self.set_kernel_options_post,
- 'kopts_post' : self.set_kernel_options_post,
- 'ksmeta' : self.set_ksmeta,
- 'kickstart' : self.set_kickstart,
- 'netboot-enabled' : self.set_netboot_enabled,
- 'netboot_enabled' : self.set_netboot_enabled,
- 'virt-path' : self.set_virt_path,
- 'virt_path' : self.set_virt_path,
- 'virt-type' : self.set_virt_type,
- 'virt_type' : self.set_virt_type,
- 'modify-interface' : self.modify_interface,
- 'modify_interface' : self.modify_interface,
- 'delete-interface' : self.delete_interface,
- 'delete_interface' : self.delete_interface,
- 'virt-path' : self.set_virt_path,
- 'virt_path' : self.set_virt_path,
- 'virt-ram' : self.set_virt_ram,
- 'virt_ram' : self.set_virt_ram,
- 'virt-type' : self.set_virt_type,
- 'virt_type' : self.set_virt_type,
- 'virt-cpus' : self.set_virt_cpus,
- 'virt_cpus' : self.set_virt_cpus,
- 'virt-file-size' : self.set_virt_file_size,
- 'virt_file_size' : self.set_virt_file_size,
- 'server' : self.set_server,
- 'owners' : self.set_owners,
- 'mgmt-classes' : self.set_mgmt_classes,
- 'mgmt_classes' : self.set_mgmt_classes,
- 'template-files' : self.set_template_files,
- 'template_files' : self.set_template_files,
- 'comment' : self.set_comment,
- 'power_type' : self.set_power_type,
- 'power_address' : self.set_power_address,
- 'power_user' : self.set_power_user,
- 'power_pass' : self.set_power_pass,
- 'power_id' : self.set_power_id,
- 'hostname' : self.set_hostname,
- 'gateway' : self.set_gateway,
- 'name_servers' : self.set_name_servers,
- 'redhat_management_key' : self.set_redhat_management_key
+ 'name' : self.set_name,
+ 'profile' : self.set_profile,
+ 'image' : self.set_image,
+ 'kopts' : self.set_kernel_options,
+ 'kopts-post' : self.set_kernel_options_post,
+ 'kopts_post' : self.set_kernel_options_post,
+ 'ksmeta' : self.set_ksmeta,
+ 'kickstart' : self.set_kickstart,
+ 'netboot-enabled' : self.set_netboot_enabled,
+ 'netboot_enabled' : self.set_netboot_enabled,
+ 'virt-path' : self.set_virt_path,
+ 'virt_path' : self.set_virt_path,
+ 'virt-type' : self.set_virt_type,
+ 'virt_type' : self.set_virt_type,
+ 'modify-interface' : self.modify_interface,
+ 'modify_interface' : self.modify_interface,
+ 'delete-interface' : self.delete_interface,
+ 'delete_interface' : self.delete_interface,
+ 'virt-path' : self.set_virt_path,
+ 'virt_path' : self.set_virt_path,
+ 'virt-ram' : self.set_virt_ram,
+ 'virt_ram' : self.set_virt_ram,
+ 'virt-type' : self.set_virt_type,
+ 'virt_type' : self.set_virt_type,
+ 'virt-cpus' : self.set_virt_cpus,
+ 'virt_cpus' : self.set_virt_cpus,
+ 'virt-file-size' : self.set_virt_file_size,
+ 'virt_file_size' : self.set_virt_file_size,
+ 'server' : self.set_server,
+ 'owners' : self.set_owners,
+ 'mgmt-classes' : self.set_mgmt_classes,
+ 'mgmt_classes' : self.set_mgmt_classes,
+ 'template-files' : self.set_template_files,
+ 'template_files' : self.set_template_files,
+ 'comment' : self.set_comment,
+ 'power_type' : self.set_power_type,
+ 'power_address' : self.set_power_address,
+ 'power_user' : self.set_power_user,
+ 'power_pass' : self.set_power_pass,
+ 'power_id' : self.set_power_id,
+ 'hostname' : self.set_hostname,
+ 'gateway' : self.set_gateway,
+ 'name_servers' : self.set_name_servers,
+ 'name_servers_search' : self.set_name_servers_search,
+ 'redhat_management_key' : self.set_redhat_management_key,
+ 'redhat_management_server' : self.set_redhat_management_server
}
diff --git a/cobbler/manage_ctrl.py b/cobbler/manage_ctrl.py
index 10d44e8b..95ad5b8f 100644
--- a/cobbler/manage_ctrl.py
+++ b/cobbler/manage_ctrl.py
@@ -30,7 +30,6 @@ import sys
import glob
import traceback
import errno
-import popen2
from shlex import shlex
diff --git a/cobbler/module_loader.py b/cobbler/module_loader.py
index a7be1f68..f50e4bf4 100644
--- a/cobbler/module_loader.py
+++ b/cobbler/module_loader.py
@@ -82,7 +82,7 @@ def load_modules(module_path=mod_path, blacklist=None):
def get_module_by_name(name):
return MODULE_CACHE.get(name, None)
-def get_module_from_file(category,field,fallback_module_name=None):
+def get_module_from_file(category,field,fallback_module_name=None,just_name=False):
try:
value = cp.get(category,field)
@@ -91,6 +91,8 @@ def get_module_from_file(category,field,fallback_module_name=None):
value = fallback_module_name
else:
raise CX(_("Cannot find config file setting for: %s") % field)
+ if just_name:
+ return value
rc = MODULE_CACHE.get(value, None)
if rc is None:
raise CX(_("Failed to load module for %s/%s") % (category,field))
diff --git a/cobbler/modules/authn_configfile.py b/cobbler/modules/authn_configfile.py
index 1f4cdb26..b9d2996e 100644
--- a/cobbler/modules/authn_configfile.py
+++ b/cobbler/modules/authn_configfile.py
@@ -26,7 +26,7 @@ import ConfigParser
import sys
import os
from utils import _
-import md5
+from utils import md5
import traceback
plib = distutils.sysconfig.get_python_lib()
@@ -75,7 +75,7 @@ def authenticate(api_handle,username,password):
for (user,realm,actual_blob) in userlist:
if user == username and realm == "Cobbler":
input = ":".join([user,realm,password])
- input_blob = md5.md5(input).hexdigest()
+ input_blob = md5(input).hexdigest()
if input_blob.lower() == actual_blob.lower():
return True
diff --git a/cobbler/modules/authn_ldap.py b/cobbler/modules/authn_ldap.py
index e4313e07..a8718c5b 100644
--- a/cobbler/modules/authn_ldap.py
+++ b/cobbler/modules/authn_ldap.py
@@ -23,7 +23,6 @@ import distutils.sysconfig
import sys
import os
from utils import _
-import md5
import traceback
# we'll import this just a bit later
diff --git a/cobbler/modules/authn_spacewalk.py b/cobbler/modules/authn_spacewalk.py
index 45a26a01..eec3bd7c 100644
--- a/cobbler/modules/authn_spacewalk.py
+++ b/cobbler/modules/authn_spacewalk.py
@@ -29,35 +29,126 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-
def register():
"""
The mandatory cobbler module registration hook.
"""
return "authn"
+def __looks_like_a_token(password):
+
+ # what spacewalk sends us could be an internal token or it could be a password
+ # if it's long and lowercase hex, it's /likely/ a token, and we should try to treat
+ # it as a token first, if not, we should treat it as a password. All of this
+ # code is there to avoid extra XMLRPC calls, which are slow.
+
+ # we can't use binascii.unhexlify here as it's an "odd length string"
+
+ if password.lower() != password:
+ # tokens are always lowercase, this isn't a token
+ return False
+
+ #try:
+ # #data = binascii.unhexlify(password)
+ # return True # looks like a token, but we can't be sure
+ #except:
+ # return False # definitely not a token
+
+ return (len(password) > 45)
+
def authenticate(api_handle,username,password):
"""
Validate a username/password combo, returning True/False
This will pass the username and password back to Spacewalk
to see if this authentication request is valid.
+
+ See also: http://www.redhat.com/spacewalk/documentation/api/0.4/
+
"""
- #spacewalk_url = api_handle.settings().spacewalk_url
- server = api_handle.settings().redhat_management_server
+ if api_handle is not None:
+ server = api_handle.settings().redhat_management_server
+ user_enabled = api_handle.settings().redhat_management_permissive
+ else:
+ server = "columbia.devel.redhat.com"
+ user_enabled = True
+
if server == "xmlrpc.rhn.redhat.com":
- return False # don't bother RHN!
+ return False # emergency fail, don't bother RHN!
+
spacewalk_url = "https://%s/rpc/api" % server
client = xmlrpclib.Server(spacewalk_url, verbose=0)
- valid = client.auth.checkAuthToken(username,password)
-
- if valid is None:
- return False
+ if __looks_like_a_token(password) or username == 'taskomatic_user':
+
+ # The tokens
+ # are lowercase hex, but a password can also be lowercase hex,
+ # so we have to try it as both a token and then a password if
+ # we are unsure. We do it this way to be faster but also to avoid
+ # any login failed stuff in the logs that we don't need to send.
+
+ try:
+ valid = client.auth.checkAuthToken(username,password)
+ except:
+ # if the token is not a token this will raise an exception
+ # rather than return an integer.
+ valid = 0
+
+ # problem at this point, 0xdeadbeef is valid as a token but if that
+ # fails, it's also a valid password, so we must try auth system #2
+
+ if valid != 1:
+ # first API code returns 1 on success
+ # the second uses exceptions for login failed.
+ #
+ # so... token check failed, but maybe the username/password
+ # is just a simple username/pass!
+
+ if user_enabled == 0:
+ # this feature must be explicitly enabled.
+ return False
+
+
+ session = ""
+ try:
+ session = client.auth.login(username,password)
+ except:
+ # FIXME: should log exceptions that are not excepted
+ # as we could detect spacewalk java errors here that
+ # are not login related.
+ return False
+
+ # login success by username, role must also match
+ roles = client.user.listRoles(session, username)
+ if not ("config_admin" in roles or "org_admin" in roles):
+ return False
+
+ return True
- return (valid == 1)
-
+ else:
+
+ # it's an older version of spacewalk, so just try the username/pass
+ # OR: we know for sure it's not a token because it's not lowercase hex.
+
+ if user_enabled == 0:
+ # this feature must be explicitly enabled.
+ return False
+
+
+ session = ""
+ try:
+ session = client.auth.login(username,password)
+ except:
+ return False
+
+ # login success by username, role must also match
+ roles = client.user.listRoles(session, username)
+ if not ("config_admin" in roles or "org_admin" in roles):
+ return False
+ return True
+if __name__ == "__main__":
+ print authenticate(None,"admin","redhat")
diff --git a/cobbler/modules/cli_distro.py b/cobbler/modules/cli_distro.py
index 0c1425b1..7105b403 100644
--- a/cobbler/modules/cli_distro.py
+++ b/cobbler/modules/cli_distro.py
@@ -28,7 +28,7 @@ mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
from utils import _
-import commands
+import cobbler.commands as commands
import cexceptions
@@ -61,6 +61,7 @@ class DistroFunction(commands.CobblerFunction):
p.add_option("--ksmeta", dest="ksmeta", help="ex: 'blippy=7'")
p.add_option("--mgmt-classes", dest="mgmt_classes", help="list of config management classes (for Puppet, etc)")
p.add_option("--redhat-management-key", dest="redhat_management_key", help="authentication token for RHN/Spacewalk/Satellite")
+ p.add_option("--redhat-management-server", dest="redhat_management_server", help="RHN/Spacewalk/Satellite server")
p.add_option("--template-files", dest="template_files", help="specify files to be generated from templates during a sync")
p.add_option("--name", dest="name", help="ex: 'RHEL-5-i386' (REQUIRED)")
@@ -118,6 +119,8 @@ class DistroFunction(commands.CobblerFunction):
obj.set_template_files(self.options.template_files,self.options.inplace)
if self.options.redhat_management_key is not None:
obj.set_redhat_management_key(self.options.redhat_management_key)
+ if self.options.redhat_management_server is not None:
+ obj.set_redhat_management_server(self.options.redhat_management_server)
return self.object_manipulator_finish(obj, self.api.distros, self.options)
diff --git a/cobbler/modules/cli_image.py b/cobbler/modules/cli_image.py
index b6b96ff4..0265bd78 100644
--- a/cobbler/modules/cli_image.py
+++ b/cobbler/modules/cli_image.py
@@ -20,7 +20,7 @@ mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
from utils import _
-import commands
+import cobbler.commands as commands
import cexceptions
@@ -54,6 +54,8 @@ class ImageFunction(commands.CobblerFunction):
if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--os-version", dest="os_version", help="ex: rhel4, fedora 9")
+ if self.matches_args(args,["remove"]):
+ p.add_option("--recursive", action="store_true", dest="recursive", help="also delete child objects")
if self.matches_args(args,["copy","rename"]):
diff --git a/cobbler/modules/cli_misc.py b/cobbler/modules/cli_misc.py
index 94ccc0d1..f83b5c8c 100644
--- a/cobbler/modules/cli_misc.py
+++ b/cobbler/modules/cli_misc.py
@@ -21,7 +21,7 @@ mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
from utils import _
-import commands
+import cobbler.commands as commands
from cexceptions import *
HELP_FORMAT = commands.HELP_FORMAT
@@ -63,7 +63,7 @@ class CheckFunction(commands.CobblerFunction):
if len(status) == 0:
self.logprint(fd,"No setup problems found")
- self.logprint(fd,"Manual review and editing of /var/lib/cobbler/settings is recommended to tailor cobbler to your particular configuration.")
+ self.logprint(fd,"Manual review and editing of /etc/cobbler/settings is recommended to tailor cobbler to your particular configuration.")
self.logprint(fd,"Good luck.")
return True
else:
@@ -172,7 +172,10 @@ class StatusFunction(commands.CobblerFunction):
########################################################
class SyncFunction(commands.CobblerFunction):
-
+
+ def add_options(self, p, args):
+ p.add_option("--verbose", dest="verbose", action="store_true", help="run sync with more output")
+
def help_me(self):
return HELP_FORMAT % ("cobbler sync","")
@@ -180,7 +183,7 @@ class SyncFunction(commands.CobblerFunction):
return "sync"
def run(self):
- return self.api.sync()
+ return self.api.sync(verbose=self.options.verbose)
########################################################
@@ -222,6 +225,9 @@ class BuildIsoFunction(commands.CobblerFunction):
p.add_option("--profiles", dest="profiles", help="(OPTIONAL) use these profiles only")
p.add_option("--systems", dest="systems", help="(OPTIONAL) use these systems only")
p.add_option("--tempdir", dest="tempdir", help="(OPTIONAL) working directory")
+ p.add_option("--distro", dest="distro", help="(OPTIONAL) used with --standalone to create a distro-based ISO including all associated profiles/systems")
+ p.add_option("--standalone", dest="standalone", action="store_true", help="(OPTIONAL) creates a standalone ISO with all required distro files on it")
+ p.add_option("--source", dest="source", help="(OPTIONAL) used with --standalone to specify a source for the distribution files")
def help_me(self):
return HELP_FORMAT % ("cobbler buildiso","[ARGS]")
@@ -234,7 +240,10 @@ class BuildIsoFunction(commands.CobblerFunction):
iso=self.options.isoname,
profiles=self.options.profiles,
systems=self.options.systems,
- tempdir=self.options.tempdir
+ tempdir=self.options.tempdir,
+ distro=self.options.distro,
+ standalone=self.options.standalone,
+ source=self.options.source
)
########################################################
diff --git a/cobbler/modules/cli_profile.py b/cobbler/modules/cli_profile.py
index 1f8a5655..a3ca5d4c 100644
--- a/cobbler/modules/cli_profile.py
+++ b/cobbler/modules/cli_profile.py
@@ -28,7 +28,7 @@ mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
from utils import _
-import commands
+import cobbler.commands as commands
import cexceptions
@@ -72,6 +72,7 @@ class ProfileFunction(commands.CobblerFunction):
if not self.matches_args(args,["dumpvars","remove","report","getks","list"]):
p.add_option("--name-servers", dest="name_servers", help="name servers for static setups")
+ p.add_option("--name-servers-search", dest="name_servers_search", help="name servers search path for static setups")
if "copy" in args or "rename" in args:
p.add_option("--newname", dest="newname")
@@ -88,6 +89,7 @@ class ProfileFunction(commands.CobblerFunction):
if not self.matches_args(args,["dumpvars","remove","report","getks","list"]):
p.add_option("--redhat-management-key", dest="redhat_management_key", help="authentication token for RHN/Spacewalk/Satellite")
+ p.add_option("--redhat-management-server", dest="redhat_management_server", help="RHN/Spacewalk/Satellite server")
p.add_option("--repos", dest="repos", help="names of cobbler repos")
p.add_option("--server", dest="server_override", help="overrides value in settings file")
p.add_option("--template-files", dest="template_files", help="specify files to be generated from templates during a sync")
@@ -148,7 +150,7 @@ class ProfileFunction(commands.CobblerFunction):
if self.options.dhcp_tag is not None:
obj.set_dhcp_tag(self.options.dhcp_tag)
if self.options.server_override is not None:
- obj.set_server(self.options.server)
+ obj.set_server(self.options.server_overide)
if self.options.owners is not None:
obj.set_owners(self.options.owners)
@@ -158,8 +160,12 @@ class ProfileFunction(commands.CobblerFunction):
obj.set_template_files(self.options.template_files,self.options.inplace)
if self.options.name_servers is not None:
obj.set_name_servers(self.options.name_servers)
+ if self.options.name_servers_search is not None:
+ obj.set_name_servers_search(self.options.name_servers_search)
if self.options.redhat_management_key is not None:
obj.set_redhat_management_key(self.options.redhat_management_key)
+ if self.options.redhat_management_server is not None:
+ obj.set_redhat_management_server(self.options.redhat_management_server)
return self.object_manipulator_finish(obj, self.api.profiles, self.options)
diff --git a/cobbler/modules/cli_repo.py b/cobbler/modules/cli_repo.py
index a8483dcb..53c0232c 100644
--- a/cobbler/modules/cli_repo.py
+++ b/cobbler/modules/cli_repo.py
@@ -28,7 +28,7 @@ mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
from utils import _
-import commands
+import cobbler.commands as commands
import cexceptions
@@ -83,7 +83,7 @@ class RepoFunction(commands.CobblerFunction):
def run(self):
if self.args and "find" in self.args:
- items = self.api.find_system(return_list=True, no_errors=True, **self.options.__dict__)
+ items = self.api.find_repo(return_list=True, no_errors=True, **self.options.__dict__)
for x in items:
print x.name
return True
diff --git a/cobbler/modules/cli_report.py b/cobbler/modules/cli_report.py
index 82060dda..4bec4b5d 100644
--- a/cobbler/modules/cli_report.py
+++ b/cobbler/modules/cli_report.py
@@ -20,7 +20,7 @@ mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
from utils import _, get_random_mac
-import commands
+import cobbler.commands as commands
from cexceptions import *
HELP_FORMAT = commands.HELP_FORMAT
diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py
index 4805f9c9..7c0924ed 100644
--- a/cobbler/modules/cli_system.py
+++ b/cobbler/modules/cli_system.py
@@ -28,7 +28,7 @@ mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
from utils import _, get_random_mac
-import commands
+import cobbler.commands as commands
from cexceptions import *
@@ -73,7 +73,9 @@ class SystemFunction(commands.CobblerFunction):
p.add_option("--mac", dest="mac", help="ex: 'AA:BB:CC:DD:EE:FF', (RECOMMENDED)")
p.add_option("--mgmt-classes", dest="mgmt_classes", help="list of config management classes (for Puppet, etc)")
p.add_option("--name-servers", dest="name_servers", help="name servers for static setups")
+ p.add_option("--name-servers-search", dest="name_servers_search", help="name servers search path for static setups")
p.add_option("--redhat-management-key", dest="redhat_management_key", help="authentication token for RHN/Spacewalk/Satellite")
+ p.add_option("--redhat-management-server", dest="redhat_management_server", help="RHN/Spacewalk/Satellite server")
p.add_option("--static-routes", dest="static_routes", help="sets static routes (see manpage)")
p.add_option("--template-files", dest="template_files",help="specify files to be generated from templates during a sync")
@@ -101,7 +103,7 @@ class SystemFunction(commands.CobblerFunction):
if not self.matches_args(args,["dumpvars","remove","report","getks","list"]):
p.add_option("--power-pass", dest="power_pass", help="password for power management interface")
if not self.matches_args(args,["dumpvars","poweron","poweroff","reboot","remove","report","getks","list"]):
- p.add_option("--power-type", dest="power_type", help="one of: none, apc_snmp, bullpap, drac, ether-wake, ilo, ipmilan, ipmitool, wti, lpar, bladecenter, virsh")
+ p.add_option("--power-type", dest="power_type", help="one of: none, apc_snmp, bullpap, drac, ether-wake, ilo, ipmilan, ipmitool, wti, lpar, bladecenter, virsh, integrity")
if not self.matches_args(args,["dumpvars","remove","report","getks","list"]):
p.add_option("--power-user", dest="power_user", help="username for power management interface, if required")
@@ -245,8 +247,12 @@ class SystemFunction(commands.CobblerFunction):
obj.set_template_files(self.options.template_files,self.options.inplace)
if self.options.name_servers is not None:
obj.set_name_servers(self.options.name_servers)
+ if self.options.name_servers_search is not None:
+ obj.set_name_servers_search(self.options.name_servers_search)
if self.options.redhat_management_key is not None:
obj.set_redhat_management_key(self.options.redhat_management_key)
+ if self.options.redhat_management_server is not None:
+ obj.set_redhat_management_server(self.options.redhat_management_server)
rc = self.object_manipulator_finish(obj, self.api.systems, self.options)
diff --git a/cobbler/modules/install_post_log.py b/cobbler/modules/install_post_log.py
new file mode 100644
index 00000000..02c0b55e
--- /dev/null
+++ b/cobbler/modules/install_post_log.py
@@ -0,0 +1,29 @@
+import distutils.sysconfig
+import sys
+import os
+from utils import _
+import traceback
+import cexceptions
+import os
+import sys
+import time
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+def register():
+ # this pure python trigger acts as if it were a legacy shell-trigger, but is much faster.
+ # the return of this method indicates the trigger type
+ return "/var/lib/cobbler/triggers/install/post/*"
+
+def run(api, args):
+ objtype = args[0] # "system" or "profile"
+ name = args[1] # name of system or profile
+ ip = args[2] # ip or "?"
+
+ fd = open("/var/log/cobbler/install.log","a+")
+ fd.write("%s\t%s\t%s\tstop\t%s\n" % (objtype,name,ip,time.time()))
+ fd.close()
+
+ return 0
diff --git a/cobbler/modules/install_post_report.py b/cobbler/modules/install_post_report.py
new file mode 100755
index 00000000..b42753b4
--- /dev/null
+++ b/cobbler/modules/install_post_report.py
@@ -0,0 +1,101 @@
+# (c) 2008-2009
+# Jeff Schroeder <jeffschroeder@computer.org>
+# Michael DeHaan <mdehaan@redhat.com>
+#
+# License: GPLv2+
+
+# Post install trigger for cobbler to
+# send out a pretty email report that
+# contains target information.
+
+import distutils.sysconfig
+import sys
+import os
+import traceback
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+from utils import _
+import smtplib
+import sys
+import cobbler.templar as templar
+from cobbler.cexceptions import CX
+import utils
+
+def register():
+ # this pure python trigger acts as if it were a legacy shell-trigger, but is much faster.
+ # the return of this method indicates the trigger type
+ return "/var/lib/cobbler/triggers/install/post/*"
+
+def run(api, args):
+
+ settings = api.settings()
+
+ # go no further if this feature is turned off
+ if not str(settings.build_reporting_enabled).lower() in [ "1", "yes", "y", "true"]:
+ print "not enabled"
+ return 0
+
+ objtype = args[0] # "target" or "profile"
+ name = args[1] # name of target or profile
+ boot_ip = args[2] # ip or "?"
+
+ if objtype == "system":
+ target = api.find_system(name)
+ else:
+ target = api.find_profile(name)
+
+ # collapse the object down to a rendered datastructure
+ target = utils.blender(api, False, target)
+
+ if target == {}:
+ raise CX("failure looking up target")
+
+ to_addr = settings.build_reporting_email
+ if to_addr == "":
+ return 0
+
+ # add the ability to specify an MTA for servers that don't run their own
+ smtp_server = settings.build_reporting_smtp_server
+ if smtp_server == "":
+ smtp_server = "localhost"
+
+ # use a custom from address or fall back to a reasonable default
+ from_addr = settings.build_reporting_sender
+ if from_addr == "":
+ from_addr = "cobbler@%s" % settings.server
+
+ subject = settings.build_reporting_subject
+ if subject == "":
+ subject = '[Cobbler] install complete '
+
+ to_addr = ", ".join(to_addr)
+ metadata = {
+ "from_addr" : from_addr,
+ "to_addr" : to_addr,
+ "subject" : subject,
+ "boot_ip" : boot_ip
+ }
+ metadata.update(target)
+
+ input_template = open("/etc/cobbler/reporting/build_report_email.template")
+ input_data = input_template.read()
+ input_template.close()
+
+ message = templar.Templar().render(input_data, metadata, None)
+ # for debug, call
+ # print message
+
+ # Send the mail
+ # FIXME: on error, return non-zero
+ server_handle = smtplib.SMTP(smtp_server)
+ server_handle.sendmail(from_addr, to_addr, message)
+ server_handle.quit()
+
+ return 0
+
+
+
+
diff --git a/cobbler/modules/install_pre_clear_anamon_logs.py b/cobbler/modules/install_pre_clear_anamon_logs.py
new file mode 100755
index 00000000..381759aa
--- /dev/null
+++ b/cobbler/modules/install_pre_clear_anamon_logs.py
@@ -0,0 +1,51 @@
+import distutils.sysconfig
+import sys
+import os
+from utils import _
+import traceback
+import cexceptions
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+import os
+import glob
+import sys
+
+
+def register():
+ # this pure python trigger acts as if it were a legacy shell-trigger, but is much faster.
+ # the return of this method indicates the trigger type
+ return "/var/lib/cobbler/triggers/install/pre/*"
+
+def run(api, args):
+
+ if len(args) < 3:
+ raise CX("invalid invocation")
+
+ objtype = args[0] # "system" or "profile"
+ name = args[1] # name of system or profile
+ ip = args[2] # ip or "?"
+
+ settings = api.settings()
+ anamon_enabled = str(settings.anamon_enabled)
+
+ # Remove any files matched with the given glob pattern
+ def unlink_files(globex):
+ for f in glob.glob(globex):
+ if os.path.isfile(f):
+ try:
+ os.unlink(f)
+ except OSError, e:
+ pass
+
+ if str(anamon_enabled) in [ "true", "1", "y", "yes"]:
+ dirname = "/var/log/cobbler/anamon/%s" % name
+ if os.path.isdir(dirname):
+ unlink_files(os.path.join(dirname, "*"))
+
+ # TODO - log somewhere that we cleared a systems anamon logs
+ return 0
+
+
diff --git a/cobbler/modules/install_pre_log.py b/cobbler/modules/install_pre_log.py
new file mode 100644
index 00000000..4469a514
--- /dev/null
+++ b/cobbler/modules/install_pre_log.py
@@ -0,0 +1,29 @@
+import distutils.sysconfig
+import sys
+import os
+from utils import _
+import traceback
+import cexceptions
+import os
+import sys
+import time
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+def register():
+ # this pure python trigger acts as if it were a legacy shell-trigger, but is much faster.
+ # the return of this method indicates the trigger type
+ return "/var/lib/cobbler/triggers/install/pre/*"
+
+def run(api, args):
+ objtype = args[0] # "system" or "profile"
+ name = args[1] # name of system or profile
+ ip = args[2] # ip or "?"
+
+ fd = open("/var/log/cobbler/install.log","a+")
+ fd.write("%s\t%s\t%s\tstart\t%s\n" % (objtype,name,ip,time.time()))
+ fd.close()
+
+ return 0
diff --git a/cobbler/modules/manage_bind.py b/cobbler/modules/manage_bind.py
index e7cdc3a7..657a9a77 100644
--- a/cobbler/modules/manage_bind.py
+++ b/cobbler/modules/manage_bind.py
@@ -30,7 +30,6 @@ import sys
import glob
import traceback
import errno
-import popen2
import re
from shlex import shlex
@@ -82,7 +81,13 @@ class BindManager:
in them
"""
zones = {}
- for zone in self.settings.manage_forward_zones:
+ forward_zones = self.settings.manage_forward_zones
+ if type(forward_zones) != type([]):
+ # gracefully handle when user inputs only a single zone
+ # as a string instead of a list with only a single item
+ forward_zones = [forward_zones]
+
+ for zone in forward_zones:
zones[zone] = {}
for system in self.systems:
@@ -126,7 +131,13 @@ class BindManager:
in them
"""
zones = {}
- for zone in self.settings.manage_reverse_zones:
+ reverse_zones = self.settings.manage_reverse_zones
+ if type(reverse_zones) != type([]):
+ # gracefully handle when user inputs only a single zone
+ # as a string instead of a list with only a single item
+ reverse_zones = [reverse_zones]
+
+ for zone in reverse_zones:
zones[zone] = {}
for sys in self.systems:
@@ -223,14 +234,14 @@ zone "%(arpa)s." {
octets = map(lambda x: [str(i) for i in x], octets)
return ['.'.join(i) for i in octets]
- def __pretty_print_host_records(self, hosts, type='A', rclass='IN'):
+ def __pretty_print_host_records(self, hosts, rectype='A', rclass='IN'):
"""
Format host records by order and with consistent indentation
"""
names = [k for k,v in hosts.iteritems()]
if not names: return '' # zones with no hosts
- if type == 'PTR':
+ if rectype == 'PTR':
names = self.__ip_sort(names)
else:
names.sort()
@@ -239,10 +250,10 @@ zone "%(arpa)s." {
s = ""
for name in names:
- s += "%s %s %s %s\n" % (name + (" " * (max_name - len(name))),
- rclass,
- type,
- hosts[name])
+ spacing = " " * (max_name - len(name))
+ my_name = "%s%s" % (name, spacing)
+ my_host = hosts[name]
+ s += "%s %s %s %s\n" % (my_name, rclass, rectype, my_host)
return s
def __write_zone_files(self):
@@ -297,7 +308,7 @@ zone "%(arpa)s." {
except:
template_data = default_template_data
- metadata['host_record'] = self.__pretty_print_host_records(hosts, type='PTR')
+ metadata['host_record'] = self.__pretty_print_host_records(hosts, rectype='PTR')
self.templar.render(template_data, metadata, '/var/named/' + zone, None)
diff --git a/cobbler/modules/manage_dnsmasq.py b/cobbler/modules/manage_dnsmasq.py
index 344020f8..4a9b47d5 100644
--- a/cobbler/modules/manage_dnsmasq.py
+++ b/cobbler/modules/manage_dnsmasq.py
@@ -31,7 +31,6 @@ import sys
import glob
import traceback
import errno
-import popen2
from shlex import shlex
import utils
from cexceptions import *
diff --git a/cobbler/modules/manage_isc.py b/cobbler/modules/manage_isc.py
index fb22a380..9846314b 100644
--- a/cobbler/modules/manage_isc.py
+++ b/cobbler/modules/manage_isc.py
@@ -30,7 +30,7 @@ import sys
import glob
import traceback
import errno
-import popen2
+from utils import popen2
from shlex import shlex
@@ -83,7 +83,7 @@ class IscManager:
if ip.find("/") != -1:
return
try:
- fromchild, tochild = popen2.popen2(self.settings.omshell_bin)
+ fromchild, tochild = popen2([self.settings.omshell_bin])
tochild.write("port %s\n" % port)
tochild.flush()
tochild.write("connect\n")
@@ -111,7 +111,7 @@ class IscManager:
Use DHCP's API to delete a DHCP entry in
the /var/lib/dhcpd/dhcpd.leases file
"""
- fromchild, tochild = popen2.popen2(self.settings.omshell_bin)
+ fromchild, tochild = popen2([self.settings.omshell_bin])
try:
tochild.write("port %s\n" % port)
tochild.flush()
diff --git a/cobbler/modules/serializer_catalog.py b/cobbler/modules/serializer_catalog.py
index 35d1517a..72ab2cc3 100644
--- a/cobbler/modules/serializer_catalog.py
+++ b/cobbler/modules/serializer_catalog.py
@@ -25,6 +25,7 @@ import os
import sys
import glob
import traceback
+import yaml # PyYAML
plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
@@ -32,8 +33,7 @@ sys.path.insert(0, mod_path)
from utils import _
import utils
-import yaml # Howell-Clark version
-import cexceptions
+from cexceptions import *
import os
def register():
@@ -46,7 +46,10 @@ def serialize_item(obj, item):
filename = "/var/lib/cobbler/config/%ss.d/%s" % (obj.collection_type(),item.name)
datastruct = item.to_datastruct()
fd = open(filename,"w+")
- fd.write(yaml.dump(datastruct))
+ ydata = yaml.dump(datastruct)
+ if ydata is None or ydata == "":
+ raise CX("internal yaml error, tried to write empty file to %s, data was %s" % (filename, datastruct))
+ fd.write(ydata)
fd.close()
return True
@@ -63,7 +66,7 @@ def deserialize_item_raw(collection_type, item_name):
if not os.path.exists(filename):
return None
fd = open(filename)
- datastruct = yaml.load(fd.read()).next()
+ datastruct = yaml.load(fd.read())
fd.close()
return datastruct
@@ -84,14 +87,14 @@ def deserialize_raw(collection_type):
old_filename = "/var/lib/cobbler/%ss" % collection_type
if collection_type == "settings":
fd = open("/etc/cobbler/settings")
- datastruct = yaml.load(fd.read()).next()
+ datastruct = yaml.load(fd.read())
fd.close()
return datastruct
elif os.path.exists(old_filename):
# for use in migration
sys.stderr.write("reading from old config format: %s\n" % old_filename)
fd = open(old_filename)
- datastruct = yaml.load(fd.read()).next()
+ datastruct = yaml.load(fd.read())
fd.close()
return datastruct
else:
@@ -99,7 +102,14 @@ def deserialize_raw(collection_type):
files = glob.glob("/var/lib/cobbler/config/%ss.d/*" % collection_type)
for f in files:
fd = open(f)
- results.append(yaml.load(fd.read()).next())
+ ydata = fd.read()
+ if ydata is None or ydata == "":
+ raise CX("error, empty file %s" % f)
+ try:
+ datastruct = yaml.load(ydata)
+ except:
+ raise CX("error parsing yaml file: %s" % f)
+ results.append(datastruct)
fd.close()
return results
diff --git a/cobbler/modules/serializer_yaml.py b/cobbler/modules/serializer_yaml.py
index 75f89cf9..f583c7cc 100644
--- a/cobbler/modules/serializer_yaml.py
+++ b/cobbler/modules/serializer_yaml.py
@@ -25,6 +25,7 @@ import os
import sys
import glob
import traceback
+import yaml # PyYAML
plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
@@ -32,7 +33,6 @@ sys.path.insert(0, mod_path)
from utils import _
import utils
-import yaml # Howell-Clark version
import cexceptions
import os
@@ -87,7 +87,7 @@ def deserialize_raw(collection_type):
except IOError, ioe:
return [{}]
data = fd.read()
- datastruct = yaml.load(data).next() # first record
+ datastruct = yaml.load(data) # first record
fd.close()
return datastruct
@@ -120,7 +120,7 @@ def deserialize(obj,topological=False):
raise cexceptions.CX(_("Need permissions to read %s") % obj.filename())
data = fd.read()
try:
- datastruct = yaml.load(data).next() # first record
+ datastruct = yaml.load(data) # first record
except:
# load failure, make empty list
datastruct = []
diff --git a/cobbler/modules/sync_post_restart_services.py b/cobbler/modules/sync_post_restart_services.py
new file mode 100644
index 00000000..fa9699b9
--- /dev/null
+++ b/cobbler/modules/sync_post_restart_services.py
@@ -0,0 +1,70 @@
+import distutils.sysconfig
+import sys
+import os
+from utils import _
+import traceback
+import cexceptions
+import os
+import sys
+import xmlrpclib
+import cobbler.module_loader as module_loader
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+def register():
+ # this pure python trigger acts as if it were a legacy shell-trigger, but is much faster.
+ # the return of this method indicates the trigger type
+ return "/var/lib/cobbler/triggers/sync/post/*"
+
+def run(api,args):
+
+ settings = api.settings()
+
+ manage_dhcp = str(settings.manage_dhcp).lower()
+ manage_dns = str(settings.manage_dns).lower()
+ manage_xinetd = str(settings.manage_xinetd).lower()
+ restart_dhcp = str(settings.restart_dhcp).lower()
+ restart_dns = str(settings.restart_dns).lower()
+ restart_xinetd = str(settings.restart_xinetd).lower()
+ omapi_enabled = str(settings.omapi_enabled).lower()
+ omapi_port = str(settings.omapi_port).lower()
+
+ which_dhcp_module = module_loader.get_module_from_file("dhcp","module",just_name=True).strip()
+ which_dns_module = module_loader.get_module_from_file("dns","module",just_name=True).strip()
+
+ # special handling as we don't want to restart it twice
+ has_restarted_dnsmasq = False
+
+ rc = 0
+ if manage_dhcp != "0":
+ if which_dhcp_module == "manage_isc":
+ if not omapi_enabled in [ "1", "true", "yes", "y" ] and restart_dhcp:
+ rc = os.system("/usr/sbin/dhcpd -t")
+ if rc != 0:
+ print "/usr/sbin/dhcpd -t failed"
+ return 1
+ rc = os.system("/sbin/service dhcpd restart")
+ elif which_dhcp_module == "manage_dnsmasq":
+ if restart_dhcp:
+ rc = os.system("/sbin/service dnsmasq restart")
+ has_restarted_dnsmasq = True
+ else:
+ print "- error: unknown DHCP engine: %s" % which_dhcp_module
+ rc = 411
+
+ if manage_dns != "0" and restart_dns != "0":
+ if which_dns_module == "manage_bind":
+ rc = os.system("/sbin/service named restart")
+ elif which_dns_module == "manage_dnsmasq" and not has_restarted_dnsmasq:
+ rc = os.ssytem("/sbin/service dnsmasq restart")
+ else:
+ print "- error: unknown DNS engine: %s" % which_dns_module
+ rc = 412
+
+ if manage_xinetd != "0" and restart_xinetd != "0":
+ rc = os.system("/sbin/service xinetd restart")
+
+ return rc
+
diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py
index 3fcc4030..7ff07808 100644
--- a/cobbler/pxegen.py
+++ b/cobbler/pxegen.py
@@ -24,6 +24,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
import os
import os.path
import shutil
+import shlex
import time
import sys
import glob
@@ -64,6 +65,7 @@ class PXEGen:
self.images = config.images()
self.templar = templar.Templar(config)
self.bootloc = utils.tftpboot_location()
+ self.verbose = False
def copy_bootloaders(self):
"""
@@ -75,27 +77,27 @@ class PXEGen:
# copy syslinux from one of two locations
try:
- utils.copyfile_pattern('/usr/lib/syslinux/pxelinux.0', dst, api=self.api)
+ utils.copyfile_pattern('/usr/lib/syslinux/pxelinux.0', dst, api=self.api, verbose=self.verbose)
except:
- utils.copyfile_pattern('/usr/share/syslinux/pxelinux.0', dst, api=self.api)
+ utils.copyfile_pattern('/usr/share/syslinux/pxelinux.0', dst, api=self.api, verbose=self.verbose)
# copy memtest only if we find it
- utils.copyfile_pattern('/boot/memtest*', dst, require_match=False, api=self.api)
+ utils.copyfile_pattern('/boot/memtest*', dst, require_match=False, api=self.api, verbose=self.verbose)
# copy elilo which we include for IA64 targets
- utils.copyfile_pattern('/var/lib/cobbler/elilo-3.8-ia64.efi', dst, api=self.api)
+ utils.copyfile_pattern('/var/lib/cobbler/elilo-3.8-ia64.efi', dst, api=self.api, verbose=self.verbose)
# copy menu.c32 as the older one has some bugs on certain RHEL
- utils.copyfile_pattern('/var/lib/cobbler/menu.c32', dst, api=self.api)
+ utils.copyfile_pattern('/var/lib/cobbler/menu.c32', dst, api=self.api, verbose=self.verbose)
# copy yaboot which we include for PowerPC targets
- utils.copyfile_pattern('/var/lib/cobbler/yaboot-1.3.14', dst, api=self.api)
+ utils.copyfile_pattern('/var/lib/cobbler/yaboot-1.3.14', dst, api=self.api, verbose=self.verbose)
# copy memdisk as we need it to boot ISOs
try:
- utils.copyfile_pattern('/usr/lib/syslinux/memdisk', dst, api=self.api)
+ utils.copyfile_pattern('/usr/lib/syslinux/memdisk', dst, api=self.api, verbose=self.verbose)
except:
- utils.copyfile_pattern('/usr/share/syslinux/memdisk', dst, api=self.api)
+ utils.copyfile_pattern('/usr/share/syslinux/memdisk', dst, api=self.api, verbose=self.verbose)
def copy_distros(self):
@@ -114,14 +116,14 @@ class PXEGen:
if d.breed == "windows":
continue
try:
+ if self.verbose:
+ print "- copying files for distro: %s" % d.name
self.copy_single_distro_files(d)
except CX, e:
errors.append(e)
print e.value
# FIXME: using logging module so this ends up in cobbler.log?
- if len(errors) > 0:
- raise CX(_("Error(s) encountered while copying distro files"))
def copy_images(self):
"""
@@ -130,14 +132,14 @@ class PXEGen:
errors = list()
for i in self.images:
try:
+ if self.verbose:
+ print "- copying files for image: %s" % i.name
self.copy_single_image_files(i)
except CX, e:
errors.append(e)
print e.value
# FIXME: using logging module so this ends up in cobbler.log?
- if len(errors) > 0:
- raise CX(_("Error(s) encountered while copying image files"))
def copy_single_distro_files(self, d):
for dirtree in [self.bootloc, self.settings.webdir]:
@@ -157,8 +159,9 @@ class PXEGen:
allow_symlink=True
dst1 = os.path.join(distro_dir, b_kernel)
dst2 = os.path.join(distro_dir, b_initrd)
- utils.linkfile(kernel, dst1, symlink_ok=allow_symlink, api=self.api)
- utils.linkfile(initrd, dst2, symlink_ok=allow_symlink, api=self.api)
+ utils.linkfile(kernel, dst1, symlink_ok=allow_symlink, api=self.api, verbose=self.verbose)
+
+ utils.linkfile(initrd, dst2, symlink_ok=allow_symlink, api=self.api, verbose=self.verbose)
def copy_single_image_files(self, img):
images_dir = os.path.join(self.bootloc, "images2")
@@ -170,7 +173,7 @@ class PXEGen:
os.makedirs(images_dir)
basename = os.path.basename(img.file)
newfile = os.path.join(images_dir, img.name)
- utils.linkfile(filename, newfile, api=self.api)
+ utils.linkfile(filename, newfile, api=self.api, verbose=self.verbose)
return True
def generate_windows_files(self):
@@ -316,6 +319,150 @@ class PXEGen:
return True
+ def generate_windows_files(self):
+ # FIXME: hard coding of /tftpboot here is wrong
+ utils.mkdir("/tftpboot/profiles")
+ for p in self.profiles:
+ distro = p.get_conceptual_parent()
+ if distro and distro.breed != "windows":
+ continue
+ else:
+ self.generate_windows_profile_pxe(p)
+ utils.mkdir("/tftpboot/systems")
+ for s in self.systems:
+ profile = s.get_conceptual_parent()
+ if profile:
+ distro = profile.get_conceptual_parent()
+ if distro and distro.breed != "windows":
+ continue
+ else:
+ self.generate_windows_system_pxe(s)
+
+ def generate_windows_profile_pxe(self, profile):
+ distro = profile.get_conceptual_parent()
+
+ dest_dir = os.path.join("/tftpboot/profiles", profile.name)
+ utils.mkdir(dest_dir)
+
+ utils.cabextract(distro.kernel, dest_dir, self.api)
+
+ src_file = os.path.join(dest_dir, "startrom.n12")
+ dest_file = os.path.join(dest_dir, "winpxe.0")
+ cmd = [ "/bin/sed", "-i", "-e", "s/ntldr/L%s/gi" % profile.random_id, src_file ]
+ sub_process.call(cmd, shell=False, close_fds=False)
+ os.rename(src_file, dest_file)
+
+ utils.cabextract(distro.initrd, dest_dir, self.api)
+
+ src_file = os.path.join(dest_dir, "setupldr.exe")
+ dest_file = os.path.join(dest_dir, "NTLDR")
+ cmd = [ "/bin/sed", "-i", "-e", "s/winnt\\.sif/w%s.sif/gi" % profile.random_id, src_file ]
+ sub_process.call(cmd, shell=False, close_fds=False)
+ cmd = [ "/bin/sed", "-i", "-e", "s/ntdetect\\.com/ntd_%s.com/gi" % profile.random_id, src_file ]
+ sub_process.call(cmd, shell=False, close_fds=False)
+ os.rename(src_file, dest_file)
+
+ src_dir = os.path.dirname(distro.kernel)
+ src_file = os.path.join(src_dir, "ntdetect.com")
+ file_name = os.path.join(dest_dir, "ntdetect.com")
+ utils.copyfile(src_file, file_name, self.api)
+
+ template = profile.kickstart
+ fd = open(template, "r")
+ template_data = fd.read()
+ fd.close()
+
+ blended = utils.blender(self.api, False, profile)
+ blended['next_server'] = self.settings.next_server
+
+ ksmeta = blended.get("ks_meta",{})
+ del blended["ks_meta"]
+ blended.update(ksmeta) # make available at top level
+
+ # this is a workaround for a dumb bug in cheetah...
+ # a backslash before a variable is always treated as
+ # escaping the $, so things are not rendered correctly.
+ # The only option is to use another variable for
+ # backslashes when leading another variable. i.e.:
+ # \\$variable
+ blended['bsp'] = '\\'
+
+ data = self.templar.render(template_data, blended, None)
+
+ # write .sif to /tftpboot/profiles/$name/winnt.sif
+ file_name = os.path.join(dest_dir, "winnt.sif")
+ fd = open(file_name, "w")
+ fd.write(data)
+ fd.close()
+
+ return True
+
+ def generate_windows_system_pxe(self, system):
+ profile = system.get_conceptual_parent()
+ if not profile:
+ return False
+
+ distro = profile.get_conceptual_parent()
+
+ dest_dir = os.path.join("/tftpboot/systems", system.name)
+ utils.mkdir(dest_dir)
+
+ utils.cabextract(distro.kernel, dest_dir, self.api)
+
+ src_file = os.path.join(dest_dir, "startrom.n12")
+ dest_file = os.path.join(dest_dir, "winpxe.0")
+ cmd = [ "/bin/sed", "-i", "-e", "s/ntldr/L%s/gi" % system.random_id, src_file ]
+ sub_process.call(cmd, shell=False, close_fds=False)
+ os.rename(src_file, dest_file)
+
+ utils.cabextract(distro.initrd, dest_dir, self.api)
+
+ src_file = os.path.join(dest_dir, "setupldr.exe")
+ dest_file = os.path.join(dest_dir, "NTLDR")
+ cmd = [ "/bin/sed", "-i", "-e", "s/winnt\\.sif/w%s.sif/gi" % system.random_id, src_file ]
+ sub_process.call(cmd, shell=False, close_fds=False)
+ cmd = [ "/bin/sed", "-i", "-e", "s/ntdetect\\.com/ntd_%s.com/gi" % system.random_id, src_file ]
+ sub_process.call(cmd, shell=False, close_fds=False)
+ os.rename(src_file, dest_file)
+
+ src_dir = os.path.dirname(distro.kernel)
+ src_file = os.path.join(src_dir, "ntdetect.com")
+ file_name = os.path.join(dest_dir, "ntdetect.com")
+ utils.copyfile(src_file, file_name, self.api)
+
+ template = system.kickstart
+ if template == "<<inherit>>":
+ template = profile.kickstart
+
+ fd = open(template, "r")
+ template_data = fd.read()
+ fd.close()
+
+ blended = utils.blender(self.api, False, system)
+ blended['next_server'] = self.settings.next_server
+
+ ksmeta = blended.get("ks_meta",{})
+ del blended["ks_meta"]
+ blended.update(ksmeta) # make available at top level
+
+ # this is a workaround for a dumb bug in cheetah...
+ # a backslash before a variable is always treated as
+ # escaping the $, so things are not rendered correctly.
+ # The only option is to use another variable for
+ # backslashes when leading another variable. i.e.:
+ # \\$variable
+ blended['bsp'] = '\\'
+
+ data = self.templar.render(template_data, blended, None)
+
+ # write .sif to /tftpboot/systems/$name/winnt.sif
+ file_name = os.path.join(dest_dir, "winnt.sif")
+ fd = open(file_name, "w")
+ fd.write(data)
+ fd.close()
+
+ return True
+
def write_all_system_files(self,system):
profile = system.get_conceptual_parent()
@@ -331,8 +478,35 @@ class PXEGen:
image_based = True
image = profile
- # this used to just generate a single PXE config file, but now must
- # generate one record for each described NIC ...
+ # hack: s390 generates files per system not per interface
+ if not image_based and distro.arch.startswith("s390"):
+ # Always write a system specific _conf and _parm file
+ f2 = os.path.join(self.bootloc, "s390x", "s_%s" % system.name)
+ cf = "%s_conf" % f2
+ pf = "%s_parm" % f2
+ template_cf = open("/etc/cobbler/pxe/s390x_conf.template")
+ template_pf = open("/etc/cobbler/pxe/s390x_parm.template")
+ blended = utils.blender(self.api, True, system)
+ self.templar.render(template_cf, blended, cf)
+ # FIXME: profiles also need this data!
+ # FIXME: the _conf and _parm files are limited to 80 characters in length
+ kickstart_path = "http://%s/cblr/svc/op/ks/system/%s" % (blended["http_server"], system.name)
+ # gather default kernel_options and default kernel_options_s390x
+ kopts = blended.get("kernel_options","")
+ hkopts = shlex.split(utils.hash_to_string(kopts))
+ blended["kickstart_expanded"] = "ks=%s" % kickstart_path
+ blended["kernel_options"] = hkopts
+ self.templar.render(template_pf, blended, pf)
+
+ # Write system specific zPXE file
+ if system.is_management_supported():
+ self.write_pxe_file(f2,system,profile,distro,distro.arch)
+ else:
+ # ensure the file doesn't exist
+ utils.rmfile(f2)
+ return
+
+ # generate one record for each described NIC ..
for (name,interface) in system.interfaces.iteritems():
@@ -345,8 +519,8 @@ class PXEGen:
else:
working_arch = distro.arch
- if working_arch is None or working_arch == "":
- working_arch = "x86"
+ if working_arch is None:
+ raise "internal error, invalid arch supplied"
# for tftp only ...
if working_arch in [ "i386", "x86", "x86_64", "standard"]:
@@ -370,10 +544,6 @@ class PXEGen:
if os.path.lexists(f3):
utils.rmfile(f3)
os.symlink("../yaboot-1.3.14", f3)
-
- elif working_arch == "s390x":
- filename = "%s" % utils.get_config_filename(system,interface=name)
- f2 = os.path.join(self.bootloc, "s390x", filename)
else:
continue
@@ -405,18 +575,27 @@ class PXEGen:
distro = profile.get_conceptual_parent()
if distro is None:
raise CX(_("profile is missing distribution: %s, %s") % (profile.name, profile.distro))
- if distro.arch == "s390x":
+ if distro.arch.startswith("s390"):
listfile.write("%s\n" % profile.name)
- f2 = os.path.join(self.bootloc, "s390x", profile.name)
- self.write_pxe_file(f2,None,profile,distro,distro.arch)
- listfile2 = open(os.path.join(s390path, "image_list"),"w+")
- for image in image_list:
- if os.path.exists(image.file):
- listfile2.write("%s\n" % image.name)
- f2 = os.path.join(self.bootloc, "s390x", image.name)
- self.write_pxe_file(f2,None,None,None,image.arch,image=image)
+ f2 = os.path.join(self.bootloc, "s390x", "p_%s" % profile.name)
+ self.write_pxe_file(f2,None,profile,distro,distro.arch)
+ cf = "%s_conf" % f2
+ pf = "%s_parm" % f2
+ template_cf = open("/etc/cobbler/pxe/s390x_conf.template")
+ template_pf = open("/etc/cobbler/pxe/s390x_parm.template")
+ blended = utils.blender(self.api, True, profile)
+ self.templar.render(template_cf, blended, cf)
+ # FIXME: profiles also need this data!
+ # FIXME: the _conf and _parm files are limited to 80 characters in length
+ kickstart_path = "http://%s/cblr/svc/op/ks/profile/%s" % (blended["http_server"], profile.name)
+ # gather default kernel_options and default kernel_options_s390x
+ kopts = blended.get("kernel_options","")
+ hkopts = shlex.split(utils.hash_to_string(kopts))
+ blended["kickstart_expanded"] = "ks=%s" % kickstart_path
+ blended["kernel_options"] = hkopts
+ self.templar.render(template_pf, blended, pf)
+
listfile.close()
- listfile2.close()
def make_actual_pxe_menu(self):
# only do this if there is NOT a system named default.
@@ -448,7 +627,7 @@ class PXEGen:
continue
distro = profile.get_conceptual_parent()
# xen distros can be ruled out as they won't boot
- if distro.name.find("-xen") != -1:
+ if distro.name.find("-xen") != -1 or distro.arch not in ["i386", "x86_64"]:
# can't PXE Xen
continue
contents = self.write_pxe_file(None,None,profile,distro,distro.arch,include_header=False)
@@ -524,6 +703,9 @@ class PXEGen:
more details
"""
+ if arch is None:
+ raise "missing arch"
+
if image and not os.path.exists(image.file):
return None # nfs:// URLs or something, can't use for TFTP
@@ -539,9 +721,10 @@ class PXEGen:
initrd_path = None
if image is None:
- # profile or system+profile based, not image based, or system+image based
+ # not image based, it's something normalish
if distro is not None and distro.breed == "windows":
+ # this is to support linux-ris
if system:
kernel_path = "systems/%s/winpxe.0" % system.name
elif profile:
@@ -551,8 +734,14 @@ class PXEGen:
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)["kickstart"]
+ if system:
+ blended = utils.blender(self.api, True, system)
+ else:
+ blended = utils.blender(self.api, True, profile)
+ kickstart_path = blended["kickstart"]
+
else:
+ # this is an image we are making available, not kernel+initrd
if image.image_type == "direct":
kernel_path = os.path.join("/images2",image.name)
elif image.image_type == "memdisk":
@@ -571,19 +760,18 @@ class PXEGen:
else:
template = os.path.join(self.settings.pxe_template_dir,"pxesystem.template")
- if arch == "s390x":
- template = os.path.join(self.settings.pxe_template_dir,"pxesystem_s390x.template")
- elif arch == "ia64":
- template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ia64.template")
- elif arch.startswith("ppc"):
- template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ppc.template")
+ if arch.startswith("s390"):
+ template = os.path.join(self.settings.pxe_template_dir,"pxesystem_s390x.template")
+ elif arch == "ia64":
+ template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ia64.template")
+ elif arch.startswith("ppc"):
+ template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ppc.template")
else:
# local booting on ppc requires removing the system-specific dhcpd.conf filename
if arch is not None and arch.startswith("ppc"):
# Disable yaboot network booting for all interfaces on the system
for (name,interface) in system.interfaces.iteritems():
- # Determine filename for system-specific yaboot.conf
filename = "%s" % utils.get_config_filename(system, interface=name).lower()
# Remove symlink to the yaboot binary
@@ -599,15 +787,20 @@ class PXEGen:
# Yaboot/OF doesn't support booting locally once you've
# booted off the network, so nothing left to do
return None
+ elif arch is not None and arch.startswith("s390"):
+ template = os.path.join(self.settings.pxe_template_dir,"pxelocal_s390x.template")
else:
template = os.path.join(self.settings.pxe_template_dir,"pxelocal.template")
else:
+ # not a system record, so this is a profile record
+
if distro is not None and distro.breed == "windows":
template = os.path.join(self.settings.pxe_template_dir,"pxeprofile_win.template")
+ elif arch.startswith("s390"):
+ template = os.path.join(self.settings.pxe_template_dir,"pxeprofile_s390x.template")
else:
template = os.path.join(self.settings.pxe_template_dir,"pxeprofile.template")
-
# now build the kernel command line
if system is not None:
blended = utils.blender(self.api, True, system)
@@ -624,15 +817,18 @@ class PXEGen:
else:
append_line = "append %s" % hkopts
+ # FIXME - the append_line length limit is architecture specific
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 != "":
+ # FIXME: need to make shorter rewrite rules for these URLs
+
if system is not None and kickstart_path.startswith("/"):
kickstart_path = "http://%s/cblr/svc/op/ks/system/%s" % (blended["http_server"], system.name)
- elif kickstart_path.startswith("/") or kickstart_path.find("/cobbler/kickstarts/") != -1:
+ elif kickstart_path.startswith("/"):
kickstart_path = "http://%s/cblr/svc/op/ks/profile/%s" % (blended["http_server"], profile.name)
if distro.breed is None or distro.breed == "redhat":
@@ -644,14 +840,14 @@ class PXEGen:
# interface=bootif causes a failure
# append_line = append_line.replace("ksdevice","interface")
- if arch in ["s390x", "ppc", "ppc64"]:
+ if arch.startswith("ppc") or arch.startswith("s390"):
# remove the prefix "append"
append_line = append_line[7:]
# store variables for templating
metadata["menu_label"] = ""
if profile:
- if not arch in [ "ia64", "ppc", "ppc64", "s390x" ]:
+ if not arch in [ "ia64", "ppc", "ppc64", "s390", "s390x" ]:
metadata["menu_label"] = "MENU LABEL %s" % profile.name
metadata["profile_name"] = profile.name
elif image:
@@ -706,10 +902,15 @@ class PXEGen:
return results
blended = utils.blender(self.api, False, obj)
+
ksmeta = blended.get("ks_meta",{})
del blended["ks_meta"]
blended.update(ksmeta) # make available at top level
+ templates = blended.get("template_files",{})
+ del blended["template_files"]
+ blended.update(templates) # make available at top level
+
(success, templates) = utils.input_string_or_hash(templates)
if not success:
@@ -718,7 +919,9 @@ class PXEGen:
for template in templates.keys():
dest = templates[template]
-
+ if dest is None:
+ continue
+
# Run the source and destination files through
# templar first to allow for variables in the path
template = self.templar.render(template, blended, None).strip()
diff --git a/cobbler/remote.py b/cobbler/remote.py
index 8154dc58..10e9cc20 100644
--- a/cobbler/remote.py
+++ b/cobbler/remote.py
@@ -27,10 +27,13 @@ import sys
import socket
import time
import os
+import base64
import SimpleXMLRPCServer
import xmlrpclib
import random
+import stat
import base64
+import fcntl
import string
import traceback
import glob
@@ -45,7 +48,7 @@ import item_system
import item_repo
import item_image
from utils import *
-from utils import _ # * does not import _
+from utils import _
# FIXME: make configurable?
TOKEN_TIMEOUT = 60*60 # 60 minutes
@@ -53,25 +56,86 @@ OBJECT_TIMEOUT = 60*60 # 60 minutes
TOKEN_CACHE = {}
OBJECT_CACHE = {}
+class DataCache:
+
+ def __init__(self, api):
+ """
+ Constructor
+ """
+ self.api = api
+
+ def update(self,collection_type, name):
+ data = self.api.deserialize_item_raw(collection_type, name)
+
+ if data is None:
+ return False
+
+ if collection_type == "distro":
+ obj = item_distro.Distro(self.api._config)
+ obj.from_datastruct(data)
+ self.api.add_distro(obj, False, False)
+
+ if collection_type == "profile":
+ subprofile = False
+ if data.has_key("parent") and data["parent"] != "":
+ subprofile = True
+ obj = item_profile.Profile(self.api._config, is_subobject = subprofile)
+ obj.from_datastruct(data)
+ self.api.add_profile(obj, False, False)
+
+ if collection_type == "system":
+ obj = item_system.System(self.api._config)
+ obj.from_datastruct(data)
+ self.api.add_system(obj, False, False, False)
+
+ if collection_type == "repo":
+ obj = item_repo.Repo(self.api._config)
+ obj.from_datastruct(data)
+ self.api.add_repo(obj, False, False)
+
+ if collection_type == "image":
+ obj = item_image.Image(self.api._config)
+ obj.from_datastruct(data)
+ self.api.add_image(obj, False, False)
+
+
+ def remove(self,collection_type, name):
+ # for security reasons, only remove if actually gone
+ data = self.api.deserialize_item_raw(collection_type, name)
+ if data is None:
+ if collection_type == "distro":
+ self.api.remove_distro(name, delete=False, recursive=True, with_triggers=False)
+ if collection_type == "profile":
+ self.api.remove_profile(name, delete=False, recursive=True, with_triggers=False)
+ if collection_type == "system":
+ self.api.remove_system(name, delete=False, recursive=True, with_triggers=False)
+ if collection_type == "repo":
+ self.api.remove_repo(name, delete=False, recursive=True, with_triggers=False)
+ if collection_type == "image":
+ self.api.remove_image(name, delete=False, recursive=True, with_triggers=False)
+
# *********************************************************************
# *********************************************************************
class CobblerXMLRPCInterface:
"""
- This is the interface used for all public XMLRPC methods, for instance,
- as used by koan. The read-write interface which inherits from this adds
- more methods, though that interface can be disabled.
+ This is the interface used for all XMLRPC methods, for instance,
+ as used by koan or CobblerWeb
note: public methods take an optional parameter token that is just
- here for consistancy with the ReadWrite API. The tokens for the read only
- interface are intentionally /not/ validated. It's a public API.
+ here for consistancy with the ReadWrite API. Read write operations do
+ require the token.
"""
- def __init__(self,api,logger,enable_auth_if_relevant):
+ def __init__(self,api,enable_auth_if_relevant):
self.api = api
- self.logger = logger
self.auth_enabled = enable_auth_if_relevant
+ self.cache = DataCache(self.api)
+ self.logger = self.api.logger
+ self.token_cache = TOKEN_CACHE
+ self.object_cache = OBJECT_CACHE
self.timestamp = self.api.last_modified_time()
+ random.seed(time.time())
def __sorter(self,a,b):
return cmp(a["name"],b["name"])
@@ -85,10 +149,15 @@ class CobblerXMLRPCInterface:
return self.api.last_modified_time()
def update(self, token=None):
- now = self.api.last_modified_time()
- if (now > self.timestamp):
- self.timestamp = now
- self.api.update()
+ # no longer neccessary
+ return True
+
+ def internal_cache_update(self, collection_type, data):
+ self.cache.update(collection_type, data)
+ return True
+
+ def internal_cache_remove(self, collection_type, data):
+ self.cache.remove(collection_type, data)
return True
def ping(self):
@@ -163,11 +232,26 @@ class CobblerXMLRPCInterface:
# FIXME: a global lock or module around data access loading
# would be useful for non-db backed storage
- data = self.api.deserialize_raw(collection_name)
- total_items = len(data)
-
if collection_name == "settings":
- return self._fix_none(data)
+ data = self.api.deserialize_raw("settings")
+ return self.xmlrpc_hacks(data)
+ else:
+ contents = []
+ if collection_name.startswith("distro"):
+ contents = self.api.distros()
+ elif collection_name.startswith("profile"):
+ contents = self.api.profiles()
+ elif collection_name.startswith("system"):
+ contents = self.api.systems()
+ elif collection_name.startswith("repo"):
+ contents = self.api.repos()
+ elif collection_name.startswith("image"):
+ contents = self.api.images()
+ else:
+ raise CX("internal error, collection name is %s" % collection_name)
+ # FIXME: speed this up
+ data = contents.to_datastruct()
+ total_items = len(data)
data.sort(self.__sorter)
@@ -184,9 +268,9 @@ class CobblerXMLRPCInterface:
start_point = total_items - 1 # correct ???
if end_point > total_items:
end_point = total_items
- data = self._fix_none(data[start_point:end_point])
+ data = self.xmlrpc_hacks(data[start_point:end_point])
- return self._fix_none(data)
+ return self.xmlrpc_hacks(data)
def get_kickstart_templates(self,token=None,**rest):
"""
@@ -208,10 +292,6 @@ class CobblerXMLRPCInterface:
def generate_kickstart(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None,**rest):
self._log("generate_kickstart")
-
- if profile and not system:
- regrc = self.register_mac(REMOTE_MAC,profile)
-
return self.api.generate_kickstart(profile,system)
def get_settings(self,token=None,**rest):
@@ -219,7 +299,9 @@ class CobblerXMLRPCInterface:
Return the contents of /etc/cobbler/settings, which is a hash.
"""
self._log("get_settings",token=token)
- return self.__get_all("settings")
+ results = self.api.settings().to_datastruct()
+ self._log("my settings are: %s" % results)
+ return self.xmlrpc_hacks(results)
def get_repo_config_for_profile(self,profile_name,**rest):
"""
@@ -259,43 +341,78 @@ class CobblerXMLRPCInterface:
return "# object not found: %s" % system_name
return self.api.get_template_file_for_system(obj,path)
- def register_mac(self,mac,profile,token=None,**rest):
+ def register_new_system(self,info,token=None,**rest):
"""
If register_new_installs is enabled in settings, this allows
- kickstarts to add new system records for per-profile-provisioned
- systems automatically via a wget in %post. This has security
- implications.
- READ: https://fedorahosted.org/cobbler/wiki/AutoRegistration
+ /usr/bin/cobbler-register (part of the koan package) to add
+ new system records remotely if they don't already exist.
+ There is a cobbler_register snippet that helps with doing
+ this automatically for new installs but it can also be used
+ for existing installs. See "AutoRegistration" on the Wiki.
"""
-
- if mac is None:
- # don't go further if not being called by anaconda
- return 1
-
- if not self.api.settings().register_new_installs:
- # must be enabled in settings
- return 2
-
- system = self.api.find_system(mac_address=mac)
- if system is not None:
- # do not allow overwrites
- return 3
-
- # the MAC probably looks like "eth0 AA:BB:CC:DD:EE:FF" now, fix it
- if mac.find(" ") != -1:
- mac = mac.split()[-1]
-
- dup = self.api.find_system(mac_address=mac)
- if dup is not None:
- return 4
-
- self._log("register mac for profile %s" % profile,token=token,name=mac)
+
+ enabled = self.api.settings().register_new_installs
+ if not str(enabled) in [ "1", "y", "yes", "true" ]:
+ raise CX("registration is disabled in cobbler settings")
+
+ # validate input
+ name = info.get("name","")
+ profile = info.get("profile","")
+ hostname = info.get("hostname","")
+ interfaces = info.get("interfaces",{})
+ ilen = len(interfaces.keys())
+
+ if name == "":
+ raise CX("no system name submitted")
+ if profile == "":
+ raise CX("profile not submitted")
+ if ilen == 0:
+ raise CX("no interfaces submitted")
+ if ilen >= 64:
+ raise CX("too many interfaces submitted")
+
+ # validate things first
+ name = info.get("name","")
+ inames = interfaces.keys()
+ if self.api.find_system(name=name):
+ raise CX("system name conflicts")
+ if hostname != "" and self.api.find_system(hostname=hostname):
+ raise CX("hostname conflicts")
+
+ for iname in inames:
+ mac = info["interfaces"][iname].get("mac_address","")
+ ip = info["interfaces"][iname].get("ip_address","")
+ if ip.find("/") != -1:
+ raise CX("no CIDR ips are allowed")
+ if mac == "":
+ raise CX("missing MAC address for interface %s" % iname)
+ if mac != "":
+ system = self.api.find_system(mac_address=mac)
+ if system is not None:
+ raise CX("mac conflict: %s" % mac)
+ if ip != "":
+ system = self.api.find_system(ip_address=ip)
+ if system is not None:
+ raise CX("ip conflict: %s"% ip)
+
+ # looks like we can go ahead and create a system now
obj = self.api.new_system()
obj.set_profile(profile)
- name = mac.replace(":","_")
obj.set_name(name)
- obj.set_mac_address(mac, "eth0")
+ if hostname != "":
+ obj.set_hostname(hostname)
obj.set_netboot_enabled(False)
+ for iname in inames:
+ mac = info["interfaces"][iname].get("mac_address","")
+ ip = info["interfaces"][iname].get("ip_address","")
+ netmask = info["interfaces"][iname].get("netmask","")
+ obj.set_mac_address(mac, iname)
+ if hostname != "":
+ obj.set_dns_name(hostname, iname)
+ if ip != "":
+ obj.set_ip_address(ip, iname)
+ if netmask != "":
+ obj.set_subnet(netmask, iname)
self.api.add_system(obj)
return 0
@@ -320,6 +437,109 @@ class CobblerXMLRPCInterface:
systems.add(obj,save=True,with_triggers=False,with_sync=False,quick_pxe_update=True)
return True
+ def upload_log_data(self, sys_name, file, size, offset, data, token=None,**rest):
+
+ """
+ This is a logger function used by the "anamon" logging system to
+ upload all sorts of auxilliary data from Anaconda.
+ As it's a bit of a potential log-flooder, it's off by default
+ and needs to be enabled in /etc/cobbler/settings.
+ """
+
+ self._log("upload_log_data (file: '%s', size: %s, offset: %s)" % (file, size, offset), token=token, name=sys_name)
+
+ # Check if enabled in self.api.settings()
+ if not self.api.settings().anamon_enabled:
+ # feature disabled!
+ return False
+
+ # Find matching system record
+ systems = self.api.systems()
+ obj = systems.find(name=sys_name)
+ if obj == None:
+ # system not found!
+ self._log("upload_log_data - system '%s' not found" % sys_name, token=token, name=sys_name)
+ return False
+
+ return self.__upload_file(sys_name, file, size, offset, data)
+
+ def __upload_file(self, sys_name, file, size, offset, data):
+ '''
+ system: the name of the system
+ name: the name of the file
+ size: size of contents (bytes)
+ data: base64 encoded file contents
+ offset: the offset of the chunk
+ files can be uploaded in chunks, if so the size describes
+ the chunk rather than the whole file. the offset indicates where
+ the chunk belongs
+ the special offset -1 is used to indicate the final chunk'''
+ contents = base64.decodestring(data)
+ del data
+ if offset != -1:
+ if size is not None:
+ if size != len(contents):
+ return False
+
+ #XXX - have an incoming dir and move after upload complete
+ # SECURITY - ensure path remains under uploadpath
+ tt = string.maketrans("/","+")
+ fn = string.translate(file, tt)
+ if fn.startswith('..'):
+ raise CX(_("invalid filename used: %s") % fn)
+
+ # FIXME ... get the base dir from cobbler settings()
+ udir = "/var/log/cobbler/anamon/%s" % sys_name
+ if not os.path.isdir(udir):
+ os.mkdir(udir, 0755)
+
+ fn = "%s/%s" % (udir, fn)
+ try:
+ st = os.lstat(fn)
+ except OSError, e:
+ if e.errno == errno.ENOENT:
+ pass
+ else:
+ raise
+ else:
+ if not stat.S_ISREG(st.st_mode):
+ raise CX(_("destination not a file: %s") % fn)
+
+ fd = os.open(fn, os.O_RDWR | os.O_CREAT, 0644)
+ # log_error("fd=%r" %fd)
+ try:
+ if offset == 0 or (offset == -1 and size == len(contents)):
+ #truncate file
+ fcntl.lockf(fd, fcntl.LOCK_EX|fcntl.LOCK_NB)
+ try:
+ os.ftruncate(fd, 0)
+ # log_error("truncating fd %r to 0" %fd)
+ finally:
+ fcntl.lockf(fd, fcntl.LOCK_UN)
+ if offset == -1:
+ os.lseek(fd,0,2)
+ else:
+ os.lseek(fd,offset,0)
+ #write contents
+ fcntl.lockf(fd, fcntl.LOCK_EX|fcntl.LOCK_NB, len(contents), 0, 2)
+ try:
+ os.write(fd, contents)
+ # log_error("wrote contents")
+ finally:
+ fcntl.lockf(fd, fcntl.LOCK_UN, len(contents), 0, 2)
+ if offset == -1:
+ if size is not None:
+ #truncate file
+ fcntl.lockf(fd, fcntl.LOCK_EX|fcntl.LOCK_NB)
+ try:
+ os.ftruncate(fd, size)
+ # log_error("truncating fd %r to size %r" % (fd,size))
+ finally:
+ fcntl.lockf(fd, fcntl.LOCK_UN)
+ finally:
+ os.close(fd)
+ return True
+
def run_install_triggers(self,mode,objtype,name,ip,token=None,**rest):
"""
@@ -339,7 +559,7 @@ class CobblerXMLRPCInterface:
# time if reinstalling all of a cluster all at once.
# we can do that at "cobbler check" time.
- utils.run_triggers(None, "/var/lib/cobbler/triggers/install/%s/*" % mode, additional=[objtype,name,ip])
+ utils.run_triggers(self.api, None, "/var/lib/cobbler/triggers/install/%s/*" % mode, additional=[objtype,name,ip])
return True
@@ -366,41 +586,81 @@ class CobblerXMLRPCInterface:
self._log("get_distros",token=token)
return self.__get_all("distro",page,results_per_page)
+
+ def __find(self,find_function,criteria={},expand=False,token=None):
+ name = criteria.get("name",None)
+ if name is not None:
+ del criteria["name"]
+ if not expand:
+ data = [x.name for x in find_function(name, True, True, **criteria)]
+ else:
+ data = [x.to_datastruct() for x in find_function(name, True, True, **criteria)]
+ return self.xmlrpc_hacks(data)
+
+ def find_distro(self,criteria={},expand=False,token=None,**rest):
+ self._log("find_distro", token=token)
+ # FIXME DEBUG
+ self._log(criteria)
+ data = self.__find(self.api.find_distro,criteria,expand=expand,token=token)
+ # FIXME DEBUG
+ self._log(data)
+ return data
+
+ def find_profile(self,criteria={},expand=False,token=None,**rest):
+ self._log("find_profile", token=token)
+ data = self.__find(self.api.find_profile,criteria,expand=expand,token=token)
+ return data
+
+ def find_system(self,criteria={},expand=False,token=None,**rest):
+ self._log("find_system", token=token)
+ data = self.__find(self.api.find_system,criteria,expand=expand,token=token)
+ return data
+
+ def find_repo(self,criteria={},expand=False,token=None,**rest):
+ self._log("find_repo", token=token)
+ data = self.__find(self.api.find_repo,criteria,expand=expand,token=token)
+ return data
+
+ def find_image(self,criteria={},expand=False,token=None,**rest):
+ self._log("find_image", token=token)
+ data = self.__find(self.api.find_image,criteria,expand=expand,token=token)
+ return data
+
def get_distros_since(self,mtime):
"""
Return all of the distro objects that have been modified
after mtime.
"""
data = self.api.get_distros_since(mtime, collapse=True)
- return self._fix_none(data)
+ return self.xmlrpc_hacks(data)
def get_profiles_since(self,mtime):
"""
See documentation for get_distros_since
"""
data = self.api.get_profiles_since(mtime, collapse=True)
- return self._fix_none(data)
+ return self.xmlrpc_hacks(data)
def get_systems_since(self,mtime):
"""
See documentation for get_distros_since
"""
data = self.api.get_systems_since(mtime, collapse=True)
- return self._fix_none(data)
+ return self.xmlrpc_hacks(data)
def get_repos_since(self,mtime):
"""
See documentation for get_distros_since
"""
data = self.api.get_repos_since(mtime, collapse=True)
- return self._fix_none(data)
+ return self.xmlrpc_hacks(data)
def get_images_since(self,mtime):
"""
See documentation for get_distros_since
"""
data = self.api.get_images_since(mtime, collapse=True)
- return self._fix_none(data)
+ return self.xmlrpc_hacks(data)
def get_profiles(self,page=None,results_per_page=None,token=None,**rest):
"""
@@ -476,7 +736,7 @@ class CobblerXMLRPCInterface:
return {}
if flatten:
result = utils.flatten(result)
- return self._fix_none(result)
+ return self.xmlrpc_hacks(result)
def get_distro(self,name,flatten=False,token=None,**rest):
"""
@@ -541,8 +801,8 @@ class CobblerXMLRPCInterface:
self._log("get_distro_as_rendered",name=name,token=token)
obj = self.api.find_distro(name=name)
if obj is not None:
- return self._fix_none(utils.blender(self.api, True, obj))
- return self._fix_none({})
+ return self.xmlrpc_hacks(utils.blender(self.api, True, obj))
+ return self.xmlrpc_hacks({})
def get_profile_as_rendered(self,name,token=None,**rest):
"""
@@ -559,8 +819,8 @@ class CobblerXMLRPCInterface:
self._log("get_profile_as_rendered", name=name, token=token)
obj = self.api.find_profile(name=name)
if obj is not None:
- return self._fix_none(utils.blender(self.api, True, obj))
- return self._fix_none({})
+ return self.xmlrpc_hacks(utils.blender(self.api, True, obj))
+ return self.xmlrpc_hacks({})
def get_system_as_rendered(self,name,token=None,**rest):
"""
@@ -577,8 +837,8 @@ class CobblerXMLRPCInterface:
self._log("get_system_as_rendered",name=name,token=token)
obj = self.api.find_system(name=name)
if obj is not None:
- return self._fix_none(utils.blender(self.api, True, obj))
- return self._fix_none({})
+ return self.xmlrpc_hacks(utils.blender(self.api, True, obj))
+ return self.xmlrpc_hacks({})
def get_repo_as_rendered(self,name,token=None,**rest):
"""
@@ -595,8 +855,8 @@ class CobblerXMLRPCInterface:
self._log("get_repo_as_rendered",name=name,token=token)
obj = self.api.find_repo(name=name)
if obj is not None:
- return self._fix_none(utils.blender(self.api, True, obj))
- return self._fix_none({})
+ return self.xmlrpc_hacks(utils.blender(self.api, True, obj))
+ return self.xmlrpc_hacks({})
def get_image_as_rendered(self,name,token=None,**rest):
"""
@@ -613,8 +873,8 @@ class CobblerXMLRPCInterface:
self._log("get_image_as_rendered",name=name,token=token)
obj = self.api.find_image(name=name)
if obj is not None:
- return self._fix_none(utils.blender(self.api, True, obj))
- return self._fix_none({})
+ return self.xmlrpc_hacks(utils.blender(self.api, True, obj))
+ return self.xmlrpc_hacks({})
def get_random_mac(self,token=None,**rest):
"""
@@ -625,21 +885,29 @@ class CobblerXMLRPCInterface:
self._log("get_random_mac",token=None)
return utils.get_random_mac(self.api)
- def _fix_none(self,data):
+ def xmlrpc_hacks(self,data):
"""
- Convert None in XMLRPC to just '~'. The above
- XMLRPC module hack should do this, but let's make extra sure.
+ Convert None in XMLRPC to just '~' to make extra sure a client
+ that can't allow_none can deal with this. ALSO: a weird hack ensuring
+ that when dicts with integer keys (or other types) are transmitted
+ with string keys.
"""
if data is None:
data = '~'
elif type(data) == list:
- data = [ self._fix_none(x) for x in data ]
+ data = [ self.xmlrpc_hacks(x) for x in data ]
elif type(data) == dict:
+ data2 = {}
for key in data.keys():
- data[key] = self._fix_none(data[key])
+ keydata = data[key]
+ data2[str(key)] = self.xmlrpc_hacks(data[key])
+ return data2
+
+ else:
+ data = '~'
return data
@@ -649,50 +917,11 @@ class CobblerXMLRPCInterface:
"""
return self.api.status()
-# *********************************************************************************
-# *********************************************************************************
-
-class CobblerXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
- def __init__(self, args):
- self.allow_reuse_address = True
- SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self,args)
-
-# *********************************************************************************
-# *********************************************************************************
-
+ ######
+ # READ WRITE METHODS BELOW REQUIRE A TOKEN, use login()
+ # TO OBTAIN ONE
+ ######
-class ProxiedXMLRPCInterface:
-
- def __init__(self,api,logger,proxy_class,enable_auth_if_relevant=True):
- self.logger = logger
- self.proxied = proxy_class(api,logger,enable_auth_if_relevant)
-
- def _dispatch(self, method, params, **rest):
-
- if not hasattr(self.proxied, method):
- self.logger.error("remote:unknown method %s" % method)
- raise CX(_("Unknown remote method"))
-
- method_handle = getattr(self.proxied, method)
-
- try:
- return method_handle(*params)
- except Exception, e:
- utils.log_exc(self.logger)
- raise e
-
-# **********************************************************************
-
-class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
-
- def __init__(self,api,logger,enable_auth_if_relevant):
- self.api = api
- self.auth_enabled = enable_auth_if_relevant
- self.logger = logger
- self.token_cache = TOKEN_CACHE
- self.object_cache = OBJECT_CACHE
- self.timestamp = self.api.last_modified_time()
- random.seed(time.time())
def __next_id(self,retry=0):
"""
@@ -701,7 +930,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
"""
if retry > 10:
# I have no idea why this would happen but I want to be through :)
- raise CX(_("internal error"))
+ raise CX(_("internal error, retry exceeded"))
next_id = self.__get_random(25)
if self.object_cache.has_key(next_id):
return self.__next_id(retry=retry+1)
@@ -907,6 +1136,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
do reposync this way. Would be nice to send output over AJAX/other
later.
"""
+ # FIXME: performance
self._log("sync",token=token)
self.check_access(token,"sync")
return self.api.sync()
@@ -1136,7 +1366,6 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
object.
"""
self._log("rename_distro",object_id=object_id,token=token)
- self.api.deserialize() # FIXME: make this unneeded
obj = self.__get_object(object_id)
return self.api.rename_distro(obj,newname)
@@ -1161,7 +1390,6 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
def rename_image(self,object_id,newname,token=None):
self._log("rename_image",object_id=object_id,token=token)
self.check_access(token,"rename_image")
- self.api.deserialize() # FIXME: make this unneeded
obj = self.__get_object(object_id)
return self.api.rename_image(obj,newname)
@@ -1335,18 +1563,41 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
return rc
-# *********************************************************************
-# *********************************************************************
-class CobblerReadWriteXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
- """
- This is just a wrapper used for launching the Read/Write XMLRPC Server.
- """
+
+# *********************************************************************************
+# *********************************************************************************
+
+class CobblerXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
def __init__(self, args):
self.allow_reuse_address = True
SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self,args)
+# *********************************************************************************
+# *********************************************************************************
+
+
+class ProxiedXMLRPCInterface:
+
+ def __init__(self,api,proxy_class,enable_auth_if_relevant=True):
+ self.proxied = proxy_class(api,enable_auth_if_relevant)
+ self.logger = self.proxied.api.logger
+
+ def _dispatch(self, method, params, **rest):
+
+ if not hasattr(self.proxied, method):
+ self.logger.error("remote:unknown method %s" % method)
+ raise CX(_("Unknown remote method"))
+
+ method_handle = getattr(self.proxied, method)
+
+ try:
+ return method_handle(*params)
+ except Exception, e:
+ utils.log_exc(self.logger)
+ raise e
+
# *********************************************************************
# *********************************************************************
@@ -1360,7 +1611,9 @@ def _test_setup_modules(authn="authn_testing",authz="authz_allowall",pxe_once=1)
MODULES_TEMPLATE = "installer_templates/modules.conf.template"
DEFAULTS = "installer_templates/defaults"
- data = yaml.loadFile(DEFAULTS).next()
+ fh = open(DEFAULTS)
+ data = yaml.load(fh.read())
+ fh.close()
data["authn_module"] = authn
data["authz_module"] = authz
data["pxe_once"] = pxe_once
@@ -1379,7 +1632,9 @@ def _test_setup_settings(pxe_once=1):
MODULES_TEMPLATE = "installer_templates/settings.template"
DEFAULTS = "installer_templates/defaults"
- data = yaml.loadFile(DEFAULTS).next()
+ fh = open(DEFAULTS)
+ data = yaml.load(fh.read())
+ fh.close()
data["pxe_once"] = pxe_once
t = Template.Template(file=MODULES_TEMPLATE, searchList=[data])
@@ -1393,13 +1648,13 @@ def _test_bootstrap_restart():
assert rc1 == 0
rc2 = subprocess.call(["/sbin/service","httpd","restart"],shell=False,close_fds=True)
assert rc2 == 0
- time.sleep(2)
+ time.sleep(5)
_test_remove_objects()
def _test_remove_objects():
- api = cobbler_api.BootAPI()
+ api = cobbler_api.BootAPI() # local handle
# from ro tests
d0 = api.find_distro("distro0")
@@ -1444,8 +1699,7 @@ def test_xmlrpc_ro():
# now populate with something more useful
# using the non-remote API
- api = cobbler_api.BootAPI()
- api.deserialize() # FIXME: redundant
+ api = cobbler_api.BootAPI() # local handle
before_distros = len(api.distros())
before_profiles = len(api.profiles())
@@ -1453,10 +1707,14 @@ def test_xmlrpc_ro():
before_repos = len(api.repos())
before_images = len(api.images())
+ fake = open("/tmp/cobbler.fake","w+")
+ fake.write("")
+ fake.close()
+
distro = api.new_distro()
distro.set_name("distro0")
- distro.set_kernel("/etc/hosts")
- distro.set_initrd("/etc/hosts")
+ distro.set_kernel("/tmp/cobbler.fake")
+ distro.set_initrd("/tmp/cobbler.fake")
api.add_distro(distro)
repo = api.new_repo()
@@ -1488,7 +1746,7 @@ def test_xmlrpc_ro():
image = api.new_image()
image.set_name("image0")
- image.set_file("/etc/hosts")
+ image.set_file("/tmp/cobbler.fake")
api.add_image(image)
# reposync is required in order to create the repo config files
@@ -1676,8 +1934,8 @@ def test_xmlrpc_rw():
_test_setup_modules(authn="authn_testing",authz="authz_allowall")
_test_bootstrap_restart()
- server = xmlrpclib.Server("http://127.0.0.1/cobbler_api_rw") # remote
- api = cobbler_api.BootAPI() # local
+ server = xmlrpclib.Server("http://127.0.0.1/cobbler_api") # remote
+ api = cobbler_api.BootAPI() # local instance, /DO/ ping cobblerd
# note if authn_testing is not engaged this will not work
# test getting token, will raise remote exception on fail
@@ -1687,17 +1945,18 @@ def test_xmlrpc_rw():
# create distro
did = server.new_distro(token)
server.modify_distro(did, "name", "distro1", token)
- server.modify_distro(did, "kernel", "/etc/hosts", token)
- server.modify_distro(did, "initrd", "/etc/hosts", token)
+ server.modify_distro(did, "kernel", "/tmp/cobbler.fake", token)
+ server.modify_distro(did, "initrd", "/tmp/cobbler.fake", token)
server.modify_distro(did, "kopts", { "dog" : "fido", "cat" : "fluffy" }, token) # hash or string
server.modify_distro(did, "ksmeta", "good=sg1 evil=gould", token) # hash or string
server.modify_distro(did, "breed", "redhat", token)
server.modify_distro(did, "os-version", "rhel5", token)
server.modify_distro(did, "owners", "sam dave", token) # array or string
server.modify_distro(did, "mgmt-classes", "blip", token) # list or string
- server.modify_distro(did, "template-files", "/etc/hosts=/tmp/a /etc/fstab=/tmp/b",token) # hash or string
+ server.modify_distro(did, "template-files", "/tmp/cobbler.fake=/tmp/a /etc/fstab=/tmp/b",token) # hash or string
server.modify_distro(did, "comment", "...", token)
server.modify_distro(did, "redhat_management_key", "ALPHA", token)
+ server.modify_distro(did, "redhat_management_server", "rhn.example.com", token)
server.save_distro(did, token)
# use the non-XMLRPC API to check that it's added seeing we tested XMLRPC RW APIs above
@@ -1723,7 +1982,9 @@ def test_xmlrpc_rw():
server.modify_profile(pid, "mgmt-classes", "one two three", token)
server.modify_profile(pid, "comment", "...", token)
server.modify_profile(pid, "name_servers", ["one","two"], token)
+ server.modify_profile(pid, "name_servers_search", ["one","two"], token)
server.modify_profile(pid, "redhat_management_key", "BETA", token)
+ server.modify_distro(did, "redhat_management_server", "sat.example.com", token)
server.save_profile(pid, token)
api.deserialize()
@@ -1741,6 +2002,7 @@ def test_xmlrpc_rw():
server.modify_system(sid, 'virt-path', "/opt/images", token)
server.modify_system(sid, 'virt-type', 'qemu', token)
server.modify_system(sid, 'name_servers', 'one two three four', token)
+ server.modify_system(sid, 'name_servers_search', 'one two three four', token)
server.modify_system(sid, 'modify-interface', {
"macaddress-eth0" : "AA:BB:CC:EE:EE:EE",
"ipaddress-eth0" : "192.168.10.50",
@@ -1764,6 +2026,7 @@ def test_xmlrpc_rw():
server.modify_system(sid, "power_pass", "magic", token)
server.modify_system(sid, "power_id", "7", token)
server.modify_system(sid, "redhat_management_key", "GAMMA", token)
+ server.modify_distro(did, "redhat_management_server", "spacewalk.example.com", token)
server.save_system(sid,token)
diff --git a/cobbler/services.py b/cobbler/services.py
index 98a7a8dd..9b9f10e5 100644
--- a/cobbler/services.py
+++ b/cobbler/services.py
@@ -29,7 +29,7 @@ import string
import sys
import time
import urlgrabber
-import yaml # cobbler packaged version
+import yaml # PyYAML
# the following imports are largely for the test code
import urlgrabber
@@ -68,7 +68,6 @@ class CobblerSvc(object):
"""
if self.remote is None:
self.remote = xmlrpclib.Server(self.server, allow_none=True)
- self.remote.update()
def index(self,**args):
return "no mode specified"
@@ -241,12 +240,15 @@ def __test_setup():
# module later.
api = cobbler_api.BootAPI()
- api.deserialize() # FIXME: redundant
+
+ fake = open("/tmp/cobbler.fake","w+")
+ fake.write("")
+ fake.close()
distro = api.new_distro()
distro.set_name("distro0")
- distro.set_kernel("/etc/hosts")
- distro.set_initrd("/etc/hosts")
+ distro.set_kernel("/tmp/cobbler.fake")
+ distro.set_initrd("/tmp/cobbler.fake")
api.add_distro(distro)
repo = api.new_repo()
@@ -291,7 +293,7 @@ def __test_setup():
image = api.new_image()
image.set_name("image0")
- image.set_file("/etc/hosts")
+ image.set_file("/tmp/cobbler.fake")
api.add_image(image)
# perhaps an artifact of the test process?
@@ -374,12 +376,9 @@ def test_services_access():
url = "http://127.0.0.1/cblr/svc/op/nopxe/system/system0"
data = urlgrabber.urlread(url)
- print "NOPXE DATA: %s" % data
- time.sleep(10)
+ time.sleep(2)
- api.deserialize() # ensure we have the latest data in the API handle
sys = api.find_system("system0")
- print "NE STATUS: %s" % sys.netboot_enabled
assert str(sys.netboot_enabled).lower() not in [ "1", "true", "yes" ]
# now let's test the listing URLs since we document
@@ -415,13 +414,12 @@ def test_services_access():
url = "http://127.0.0.1/cblr/svc/op/puppet/hostname/hostname0"
data = urlgrabber.urlread(url)
- print "puppet DATA: %s" % data
assert data.find("alpha") != -1
assert data.find("beta") != -1
assert data.find("gamma") != -1
assert data.find("3") != -1
- data = yaml.load(data).next()
+ data = yaml.load(data)
assert data.has_key("classes")
assert data.has_key("parameters")
@@ -431,13 +429,11 @@ def test_services_access():
url = "http://127.0.0.1/cblr/svc/op/template/profile/profile0/path/_tmp_t1-rendered"
data = urlgrabber.urlread(url)
- print "T1: %s" % data
assert data.find("profile0") != -1
assert data.find("$profile_name") == -1
url = "http://127.0.0.1/cblr/svc/op/template/system/system0/path/_tmp_t2-rendered"
data = urlgrabber.urlread(url)
- print "T2: %s" % data
assert data.find("system0") != -1
assert data.find("$system_name") == -1
diff --git a/cobbler/settings.py b/cobbler/settings.py
index c415150b..2f2fddb3 100644
--- a/cobbler/settings.py
+++ b/cobbler/settings.py
@@ -30,14 +30,21 @@ TESTMODE = False
# we need.
DEFAULTS = {
+ "anamon_enabled" : 0,
"allow_duplicate_hostnames" : 0,
"allow_duplicate_macs" : 0,
"allow_duplicate_ips" : 0,
"bind_bin" : "/usr/sbin/named",
+ "build_reporting_enabled" : 0,
+ "build_reporting_to_address" : "",
+ "build_reporting_sender" : "",
+ "build_reporting_subject" : "",
+ "build_reporting_smtp_server" : "localhost",
"cheetah_import_whitelist" : [ "re", "random", "time" ],
"cobbler_master" : '',
"default_kickstart" : "/var/lib/cobbler/kickstarts/default.ks",
"default_name_servers" : '',
+ "default_name_servers_search" : '',
"default_password_crypted" : "\$1\$mF86/UHC\$WvcIcX2t6crBz2onWxyac.",
"default_virt_bridge" : "xenbr0",
"default_virt_type" : "auto",
@@ -68,6 +75,7 @@ DEFAULTS = {
"text" : None,
"ksdevice" : "eth0"
},
+ "kernel_options_s390x" : {},
"manage_dhcp" : 0,
"manage_dns" : 0,
"manage_xinetd" : 0,
@@ -84,6 +92,7 @@ DEFAULTS = {
"power_template_dir" : "/etc/cobbler/power",
"pxe_just_once" : 0,
"pxe_template_dir" : "/etc/cobbler/pxe",
+ "redhat_management_permissive" : 0,
"redhat_management_type" : "off",
"redhat_management_key" : "",
"redhat_management_server" : "xmlrpc.rhn.redhat.com",
@@ -94,15 +103,12 @@ DEFAULTS = {
"run_install_triggers" : 1,
"server" : "127.0.0.1",
"snippetsdir" : "/var/lib/cobbler/snippets",
- "syslog_port" : 25150,
"tftpd_bin" : "/usr/sbin/in.tftpd",
"tftpd_conf" : "/etc/xinetd.d/tftp",
"tftpd_rules" : "/etc/tftpd.rules",
"vsftpd_bin" : "/usr/sbin/vsftpd",
"webdir" : "/var/www/cobbler",
"xmlrpc_port" : 25151,
- "xmlrpc_rw_enabled" : 1,
- "xmlrpc_rw_port" : 25152,
"yum_post_install_mirror" : 1,
"yumdownloader_flags" : "--resolve",
"yumreposync_flags" : "-l"
@@ -145,8 +151,9 @@ class Settings(serializable.Serializable):
if datastruct is None:
print _("warning: not loading empty structure for %s") % self.filename()
return
-
- self._attributes = datastruct
+
+ self._attributes = DEFAULTS
+ self._attributes.update(datastruct)
return self
diff --git a/cobbler/templar.py b/cobbler/templar.py
index 8a261084..f2e4b7f1 100644
--- a/cobbler/templar.py
+++ b/cobbler/templar.py
@@ -32,13 +32,14 @@ import utils
class Templar:
- def __init__(self,config):
+ def __init__(self,config=None):
"""
Constructor
"""
- self.config = config
- self.api = config.api
- self.settings = config.settings()
+ if config is not None:
+ self.config = config
+ self.api = config.api
+ self.settings = config.settings()
def check_for_invalid_imports(self,data):
"""
@@ -130,11 +131,12 @@ class Templar:
search_table["http_server"] = repstr
for x in search_table.keys():
- data_out = data_out.replace("@@%s@@" % str(x), str(search_table[str(x)]))
+ if type(x) == str:
+ data_out = data_out.replace("@@%s@@" % str(x), str(search_table[str(x)]))
# remove leading newlines which apparently breaks AutoYAST ?
if data_out.startswith("\n"):
- data_out = data_out.strip()
+ data_out = data_out.lstrip()
if out_path is not None:
utils.mkdir(os.path.dirname(out_path))
diff --git a/cobbler/test_basic.py b/cobbler/test_basic.py
index 2a839c5a..12bdc26a 100644
--- a/cobbler/test_basic.py
+++ b/cobbler/test_basic.py
@@ -109,7 +109,7 @@ class BootTest(unittest.TestCase):
image = self.api.new_image()
self.assertTrue(image.set_name("testimage0"))
- self.assertTrue(image.set_file("/etc/hosts")) # meaningless path
+ self.assertTrue(image.set_file(self.fk_initrd)) # meaningless path
self.assertTrue(self.api.add_image(image))
@@ -134,13 +134,9 @@ class RenameTest(BootTest):
def test_renames(self):
self.__tester(self.api.find_distro, self.api.rename_distro, "testdistro0", "testdistro1")
- self.api.update() # unneccessary?
self.__tester(self.api.find_profile, self.api.rename_profile, "testprofile0", "testprofile1")
- self.api.update() # unneccessary?
self.__tester(self.api.find_system, self.api.rename_system, "testsystem0", "testsystem1")
- self.api.update() # unneccessary?
self.__tester(self.api.find_repo, self.api.rename_repo, "testrepo0", "testrepo1")
- self.api.update() # unneccessary?
self.__tester(self.api.find_image, self.api.rename_image, "testimage0", "testimage1")
@@ -763,20 +759,17 @@ class SyncContents(BootTest):
def test_blender_cache_works(self):
- # this is just a file that exists that we don't have to create
- fake_file = "/etc/hosts"
-
distro = self.api.new_distro()
self.assertTrue(distro.set_name("D1"))
- self.assertTrue(distro.set_kernel(fake_file))
- self.assertTrue(distro.set_initrd(fake_file))
+ self.assertTrue(distro.set_kernel(self.fk_kernel))
+ self.assertTrue(distro.set_initrd(self.fk_initrd))
self.assertTrue(self.api.add_distro(distro))
self.assertTrue(self.api.find_distro(name="D1"))
profile = self.api.new_profile()
self.assertTrue(profile.set_name("P1"))
self.assertTrue(profile.set_distro("D1"))
- self.assertTrue(profile.set_kickstart(fake_file))
+ self.assertTrue(profile.set_kickstart("/var/lib/cobbler/kickstarts/sample.ks"))
self.assertTrue(self.api.add_profile(profile))
assert self.api.find_profile(name="P1") != None
@@ -818,17 +811,17 @@ class SyncContents(BootTest):
class Deletions(BootTest):
- def test_invalid_delete_profile_doesnt_exist(self):
- self.failUnlessRaises(CobblerException, self.api.profiles().remove, "doesnotexist")
+ #def test_invalid_delete_profile_doesnt_exist(self):
+ # self.failUnlessRaises(CobblerException, self.api.profiles().remove, "doesnotexist")
def test_invalid_delete_profile_would_orphan_systems(self):
self.failUnlessRaises(CobblerException, self.api.profiles().remove, "testprofile0")
- def test_invalid_delete_system_doesnt_exist(self):
- self.failUnlessRaises(CobblerException, self.api.systems().remove, "doesnotexist")
+ #def test_invalid_delete_system_doesnt_exist(self):
+ # self.failUnlessRaises(CobblerException, self.api.systems().remove, "doesnotexist")
- def test_invalid_delete_distro_doesnt_exist(self):
- self.failUnlessRaises(CobblerException, self.api.distros().remove, "doesnotexist")
+ #def test_invalid_delete_distro_doesnt_exist(self):
+ # self.failUnlessRaises(CobblerException, self.api.distros().remove, "doesnotexist")
def test_invalid_delete_distro_would_orphan_profile(self):
self.failUnlessRaises(CobblerException, self.api.distros().remove, "testdistro0")
@@ -870,6 +863,22 @@ class TestListings(BootTest):
self.assertTrue(len(self.api.profiles().printable()) > 0)
self.assertTrue(len(self.api.distros().printable()) > 0)
+class TestImage(BootTest):
+
+ def test_image_file(self):
+ # ensure that only valid names are accepted and invalid ones are rejected
+ image = self.api.new_image()
+ self.assertTrue(image.set_file("nfs://hostname/path/to/filename.iso"))
+ self.assertTrue(image.set_file("nfs://mcpierce@hostname:/path/to/filename.iso"))
+ self.assertTrue(image.set_file("nfs://hostname:/path/to/filename.iso"))
+ self.assertTrue(image.set_file("nfs://hostname/filename.iso"))
+ self.assertTrue(image.set_file("hostname:/path/to/the/filename.iso"))
+ self.failUnlessRaises(CX, image.set_file, "hostname:filename.iso")
+ self.failUnlessRaises(CX, image.set_file, "path/to/filename.iso")
+ self.failUnlessRaises(CX, image.set_file, "hostname:")
+ # port is not allowed
+ self.failUnlessRaises(CX, image.set_file, "nfs://hostname:1234/path/to/the/filename.iso")
+
#class TestCLIBasic(BootTest):
#
# def test_cli(self):
diff --git a/cobbler/utils.py b/cobbler/utils.py
index 23794a83..18535638 100644
--- a/cobbler/utils.py
+++ b/cobbler/utils.py
@@ -37,6 +37,20 @@ import tempfile
import signal
from cexceptions import *
import codes
+import time
+import netaddr
+import shlex
+
+try:
+ import hashlib as fiver
+ def md5(key):
+ return fiver.md5(key)
+except ImportError:
+ # for Python < 2.5
+ import md5 as fiver
+ def md5(key):
+ return fiver.md5(key)
+
CHEETAH_ERROR_DISCLAIMER="""
# *** ERROR ***
@@ -62,12 +76,12 @@ def _(foo):
MODULE_CACHE = {}
-# import api # factor out
-
_re_kernel = re.compile(r'vmlinuz(.*)')
_re_initrd = re.compile(r'initrd(.*).img')
-def setup_logger(name, log_level=logging.INFO, log_file="/var/log/cobbler/cobbler.log"):
+def setup_logger(name, is_cobblerd=False, log_level=logging.INFO, log_file="/var/log/cobbler/cobbler.log"):
+ if is_cobblerd:
+ log_file = "/var/log/cobbler/cobblerd.log"
logger = logging.getLogger(name)
logger.setLevel(log_level)
try:
@@ -90,18 +104,6 @@ def log_exc(logger):
logger.info("Exception Info:\n%s" % string.join(traceback.format_list(traceback.extract_tb(tb))))
-def print_exc(exc,full=False):
- (t, v, tb) = sys.exc_info()
- try:
- getattr(exc, "from_cobbler")
- print >> sys.stderr, str(exc)[1:-1]
- except:
- print >> sys.stderr, t
- print >> sys.stderr, v
- if full:
- print >> sys.stderr, string.join(traceback.format_list(traceback.extract_tb(tb)))
- return 1
-
def get_exc(exc,full=True):
(t, v, tb) = sys.exc_info()
buf = ""
@@ -133,39 +135,33 @@ def trace_me():
bar = string.join(traceback.format_list(x))
return bar
+def pretty_hex(ip, length=8):
+ """
+ Pads an IP object with leading zeroes so that the result is
+ _length_ hex digits. Also do an upper().
+ """
+ hexval = "%x" % ip.value
+ if len(hexval) < length:
+ hexval = '0' * (length - len(hexval)) + hexval
+ return hexval.upper()
def get_host_ip(ip, shorten=True):
"""
Return the IP encoding needed for the TFTP boot tree.
"""
+ ip = netaddr.IP(ip)
+ cidr = ip.cidr()
- slash = None
- if ip.find("/") != -1:
- # CIDR notation
- (ip, slash) = ip.split("/")
-
- handle = sub_process.Popen("/usr/bin/gethostip %s" % ip, shell=True, stdout=sub_process.PIPE, close_fds=True)
- out = handle.stdout
- results = out.read()
- converted = results.split(" ")[-1][0:8]
-
- if slash is None:
- return converted
+ if len(cidr) == 1: # Just an IP, e.g. a /32
+ return pretty_hex(ip)
else:
- slash = int(slash)
- num = int(converted, 16)
- delta = 32 - slash
- mask = (0xFFFFFFFF << delta)
- num = num & mask
- num = "%0x" % num
- if len(num) != 8:
- num = '0' * (8 - len(num)) + num
- num = num.upper()
- if shorten:
- nibbles = delta / 4
- for x in range(0,nibbles):
- num = num[0:-1]
- return num
+ pretty = pretty_hex(cidr[0])
+ if not shorten or len(cidr) <= 8:
+ # not enough to make the last nibble insignificant
+ return pretty
+ else:
+ cutoff = (32 - cidr.prefixlen) / 4
+ return pretty[0:-cutoff]
def get_config_filename(sys,interface):
"""
@@ -393,28 +389,32 @@ def input_string_or_hash(options,delim=",",allow_multiples=True):
raise CX(_("No idea what to do with list: %s") % options)
elif type(options) == str:
new_dict = {}
- tokens = options.split(delim)
+ tokens = shlex.split(options)
for t in tokens:
- tokens2 = t.split("=")
- if len(tokens2) == 1 and tokens2[0] != '':
+ tokens2 = t.split("=",1)
+ if len(tokens2) == 1:
# this is a singleton option, no value
- tokens2.append(None)
- elif tokens2[0] == '':
- return (False, {})
+ key = tokens2[0]
+ value = None
+ else:
+ key = tokens2[0]
+ value = tokens2[1]
# if we're allowing multiple values for the same key,
# check to see if this token has already been
# inserted into the dictionary of values already
- if tokens2[0] in new_dict.keys() and allow_multiples:
+
+ if key in new_dict.keys() and allow_multiples:
# if so, check to see if there is already a list of values
# otherwise convert the dictionary value to an array, and add
# the new value to the end of the list
- if type(new_dict[tokens2[0]]) == list:
- new_dict[tokens2[0]].append(tokens2[1])
+ if type(new_dict[key]) == list:
+ new_dict[key].append(value)
else:
- new_dict[tokens2[0]] = [new_dict[tokens2[0]], tokens2[1]]
+ new_dict[key] = [new_dict[key], value]
else:
- new_dict[tokens2[0]] = tokens2[1]
+ new_dict[key] = value
+ # make sure we have no empty entries
new_dict.pop('', None)
return (True, new_dict)
elif type(options) == dict:
@@ -457,12 +457,13 @@ def blender(api_handle,remove_hashes, root_obj):
for node in tree:
__consolidate(node,results)
- # add in syslog to results (magic)
- if settings.syslog_port != 0:
- if not results.has_key("kernel_options"):
- results["kernel_options"] = {}
- syslog = "%s:%s" % (results["server"], settings.syslog_port)
- results["kernel_options"]["syslog"] = syslog
+ # hack -- s390 nodes get additional default kernel options
+ arch = results.get("arch","?")
+ if arch.startswith("s390"):
+ keyz = settings.kernel_options_s390x.keys()
+ for k in keyz:
+ if not results.has_key(k):
+ results["kernel_options"][k] = settings.kernel_options_s390x[k]
# determine if we have room to add kssendmac to the kernel options line
kernel_txt = hash_to_string(results["kernel_options"])
@@ -633,7 +634,7 @@ def hash_removals(results,subkey):
return
scan = results[subkey].keys()
for k in scan:
- if k.startswith("!") and k != "!":
+ if str(k).startswith("!") and k != "!":
remove_me = k[1:]
if results[subkey].has_key(remove_me):
del results[subkey][remove_me]
@@ -662,15 +663,33 @@ def hash_to_string(hash):
buffer = buffer + str(key) + "=" + str(value) + " "
return buffer
-def run_triggers(ref,globber,additional=[]):
+def run_triggers(api,ref,globber,additional=[]):
"""
Runs all the trigger scripts in a given directory.
ref can be a cobbler object, if not None, the name will be passed
to the script. If ref is None, the script will be called with
no argumenets. Globber is a wildcard expression indicating which
triggers to run. Example: "/var/lib/cobbler/triggers/blah/*"
+
+ As of Cobbler 1.5.X, this also runs cobbler modules that match the globbing paths.
"""
+ # Python triggers first, before shell
+
+ modules = api.get_modules_in_category(globber)
+ for m in modules:
+ arglist = []
+ if ref:
+ arglist.append(ref.name)
+ for x in additional:
+ arglist.append(x)
+ rc = m.run(api, arglist)
+ if rc != 0:
+ raise CX("cobbler trigger failed: %s" % m.__name__)
+
+ # now do the old shell triggers, which are usually going to be slower, but are easier to write
+ # and support any language
+
triggers = glob.glob(globber)
triggers.sort()
for file in triggers:
@@ -730,7 +749,7 @@ def os_release():
if not os.path.exists("/bin/rpm"):
return ("unknown", 0)
- args = ["/bin/rpm", "-q", "--whatprovides", "redhat-release"]
+ args = ["/bin/rpm", "-q", "--whatprovides", "redhat-release", "--queryformat", "%"+"{name}"+"-%" + "{version}" + "-%" + "{release}"]
cmd = sub_process.Popen(args,shell=False,stdout=sub_process.PIPE,close_fds=True)
data = cmd.communicate()[0]
data = data.rstrip().lower()
@@ -821,7 +840,7 @@ def is_safe_to_hardlink(src,dst,api):
# we're dealing with SELinux and files that are not safe to chcon
return False
-def linkfile(src, dst, symlink_ok=False, api=None):
+def linkfile(src, dst, symlink_ok=False, api=None, verbose=False):
"""
Attempt to create a link dst that points to src. Because file
systems suck we attempt several different methods or bail to
@@ -842,25 +861,30 @@ def linkfile(src, dst, symlink_ok=False, api=None):
if not is_safe_to_hardlink(src,dst,api):
# may have to remove old hardlinks for SELinux reasons
# as previous implementations were not complete
- os.remove(dst)
+ if verbose:
+ print "- removing: %s" % dst
+ os.remove(dst)
else:
- restorecon(dst,api=api)
+ # restorecon(dst,api=api,verbose=verbose)
return True
elif os.path.islink(dst):
# existing path exists and is a symlink, update the symlink
+ if verbose:
+ print "- removing: %s" % dst
os.remove(dst)
if is_safe_to_hardlink(src,dst,api):
# we can try a hardlink if the destination isn't to NFS or Samba
# this will help save space and sync time.
try:
+ if verbose:
+ print "- trying hardlink %s -> %s" % (src,dst)
rc = os.link(src, dst)
- restorecon(dst,api=api)
+ # restorecon(dst,api=api,verbose=verbose)
return rc
except (IOError, OSError):
# hardlink across devices, or link already exists
- # can result in extra call to restorecon but no
- # major harm, we'll just symlink it if we can
+ # we'll just symlink it if we can
# or otherwise copy it
pass
@@ -868,20 +892,24 @@ def linkfile(src, dst, symlink_ok=False, api=None):
# we can symlink anywhere except for /tftpboot because
# that is run chroot, so if we can symlink now, try it.
try:
+ if verbose:
+ print "- trying symlink %s -> %s" % (src,dst)
rc = os.symlink(src, dst)
- restorecon(dst,api=api)
+ # restorecon(dst,api=api,verbose=verbose)
return rc
except (IOError, OSError):
pass
# we couldn't hardlink and we couldn't symlink so we must copy
- return copyfile(src, dst, api=api)
+ return copyfile(src, dst, api=api, verbose=verbose)
-def copyfile(src,dst,api=None):
+def copyfile(src,dst,api=None,verbose=False):
try:
+ if verbose:
+ print "- copying: %s -> %s" % (src,dst)
rc = shutil.copyfile(src,dst)
- restorecon(dst,api)
+ # restorecon(dst,api,verbose=verbose)
return rc
except:
if not os.access(src,os.R_OK):
@@ -982,58 +1010,44 @@ def umount(src):
# raise CX(_("Error bind-mounting %(src)s to %(dst)s") % { "src" : src, "dst" : dst})
-def copyfile_pattern(pattern,dst,require_match=True,symlink_ok=False,api=None):
+def copyfile_pattern(pattern,dst,require_match=True,symlink_ok=False,api=None, verbose=False):
files = glob.glob(pattern)
if require_match and not len(files) > 0:
raise CX(_("Could not find files matching %s") % pattern)
for file in files:
base = os.path.basename(file)
dst1 = os.path.join(dst,os.path.basename(file))
- linkfile(file,dst1,symlink_ok=symlink_ok,api=api)
- restorecon(dst1,api=api)
-
-def restorecon(dest, api):
+ linkfile(file,dst1,symlink_ok=symlink_ok,api=api,verbose=verbose)
+ # restorecon(dst1,api=api,verbose=verbose)
- """
- Wrapper around functions to manage SELinux contexts.
- Use chcon public_content_t where we can to allow
- hardlinking between /var/www and tftpboot but use
- restorecon everywhere else.
- """
-
- if not api.is_selinux_enabled():
- return True
-
- tdest = os.path.realpath(dest)
-
- matched_path = False
- if dest.startswith("/var/www"):
- matched_path = True
- elif dest.find("/tftpboot/"):
- matched_path = True
- remoted = is_remote_file(tdest)
-
-
- if matched_path and not is_remote_file(tdest):
- # ensure the file is flagged as public_content_t
- # because it's something we've likely hardlinked
- # three ways between tftpboot, /var/www and the source
- cmd = ["/usr/bin/chcon","-t","public_content_t", tdest]
- rc = sub_process.call(cmd,shell=False,close_fds=True)
- if rc != 0:
- raise CX("chcon operation failed: %s" % cmd)
-
- if (not matched_path) or (matched_path and remoted):
- # the basic restorecon stuff...
- cmd = [ "/sbin/restorecon",dest ]
- rc = sub_process.call(cmd,shell=False,close_fds=True)
- if rc != 0:
- raise CX("restorecon operation failed: %s" % cmd)
-
- return 0
+#def restorecon(dest, api, verbose=False):
+#
+# """
+# Wrapper around functions to manage SELinux contexts.
+# Use chcon public_content_t where we can to allow
+# hardlinking between /var/www and tftpboot but use
+# restorecon everywhere else.
+# """
+#
+# if not api.is_selinux_enabled():
+# return True
+#
+# tdest = os.path.realpath(dest)
+# # remoted = is_remote_file(tdest)
+#
+# cmd = [ "/sbin/restorecon",dest ]
+# if verbose:
+# print "- %s" % " ".join(cmd)
+# rc = sub_process.call(cmd,shell=False,close_fds=True)
+# if rc != 0:
+# raise CX("restorecon operation failed: %s" % cmd)
+#
+# return 0
-def rmfile(path):
+def rmfile(path,verbose=False):
try:
+ if verbose:
+ print "- removing: %s" % path
os.unlink(path)
return True
except OSError, ioe:
@@ -1042,16 +1056,18 @@ def rmfile(path):
raise CX(_("Error deleting %s") % path)
return True
-def rmtree_contents(path):
+def rmtree_contents(path,verbose=False):
what_to_delete = glob.glob("%s/*" % path)
for x in what_to_delete:
- rmtree(x)
+ rmtree(x,verbose=verbose)
-def rmtree(path):
+def rmtree(path,verbose=False):
try:
if os.path.isfile(path):
- return rmfile(path)
+ return rmfile(path,verbose=verbose)
else:
+ if verbose:
+ print "- removing: %s" % path
return shutil.rmtree(path,ignore_errors=True)
except OSError, ioe:
traceback.print_exc()
@@ -1059,8 +1075,10 @@ def rmtree(path):
raise CX(_("Error deleting %s") % path)
return True
-def mkdir(path,mode=0777):
+def mkdir(path,mode=0777,verbose=False):
try:
+ if verbose:
+ "- mkdir: %s" % path
return os.makedirs(path,mode)
except OSError, oe:
if not oe.errno == 17: # already exists (no constant for 17?)
@@ -1072,16 +1090,24 @@ def set_redhat_management_key(self,key):
self.redhat_management_key = key
return True
-def set_arch(self,arch):
- if arch is None or arch == "":
- arch = "x86"
- if arch in [ "standard", "ia64", "x86", "i386", "ppc", "ppc64", "x86_64", "s390x" ]:
- if arch == "x86" or arch == "standard":
- # be consistent
- arch = "i386"
+def set_redhat_management_server(self,server):
+ self.redhat_management_server = server
+ return True
+
+def set_arch(self,arch,repo=False):
+ if arch is None or arch == "" or arch == "standard" or arch == "x86":
+ arch = "i386"
+
+ if repo:
+ valids = [ "i386", "x86_64", "ia64", "ppc", "ppc64", "s390", "s390x", "noarch", "src" ]
+ else:
+ valids = [ "i386", "x86_64", "ia64", "ppc", "ppc64", "s390", "s390x" ]
+
+ if arch in valids:
self.arch = arch
return True
- raise CX(_("arch choices include: x86, x86_64, ppc, ppc64, s390x and ia64"))
+
+ raise CX("arch choices include: %s" % ", ".join(valids))
def set_os_version(self,os_version):
if os_version == "" or os_version is None:
@@ -1238,8 +1264,8 @@ def set_virt_bridge(self,vbridge):
"""
The default bridge for all virtual interfaces under this profile.
"""
- if vbridge is None:
- vbridge = ""
+ if vbridge is None or vbridge == "":
+ vbridge = self.settings.default_virt_bridge
self.virt_bridge = vbridge
return True
@@ -1304,6 +1330,8 @@ def safe_filter(var):
raise CX("Invalid characters found in input")
def is_selinux_enabled():
+ if not os.path.exists("/usr/sbin/selinuxenabled"):
+ return False
args = "/usr/sbin/selinuxenabled"
selinuxenabled = sub_process.call(args,close_fds=True)
if selinuxenabled == 0:
@@ -1411,7 +1439,7 @@ def popen2(args, **kwargs):
Leftovers from borrowing some bits from Snake, replace this
function with just the subprocess call.
"""
- p = sub_process.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, **kwargs)
+ p = sub_process.Popen(args, stdout=sub_process.PIPE, stdin=sub_process.PIPE, **kwargs)
return (p.stdout, p.stdin)
if __name__ == "__main__":
diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py
index ccdfe9c0..6175ef50 100644
--- a/cobbler/webui/CobblerWeb.py
+++ b/cobbler/webui/CobblerWeb.py
@@ -128,7 +128,7 @@ class CobblerWeb(object):
})
def menu(self,**args):
- return self.__render( 'blank.tmpl', { } )
+ return self.__render( 'blank.tmpl', {} )
# ------------------------------------------------------------------------ #
# Settings
@@ -151,6 +151,85 @@ class CobblerWeb(object):
# Distributions
# ------------------------------------------------------------------------ #
+ def distro_menu(self,**spam):
+ return self.__render('blank.tmpl',{ 'more_blank' : 1})
+
+ def __search_execute(self,what,key1=None,value1=None,key2=None,value2=None,key3=None,value3=None):
+ if not self.__xmlrpc_setup():
+ return self.xmlrpc_auth_failure()
+
+ criteria={}
+ if key1 is not None and key1 != "":
+ criteria[key1] = value1.replace('"','')
+ if key2 is not None and key2 != "":
+ criteria[key2] = value2.replace('"','')
+ if key3 is not None and key3 != "":
+ criteria[key3] = value3.replace('"','')
+
+ params = {}
+ params['page'] = -1
+
+ results = []
+ if what == "distro":
+ results = params['distros'] = self.remote.find_distro(criteria,True)
+ elif what == "profile":
+ results = params['profiles'] = self.remote.find_profile(criteria,True)
+ elif what == "system":
+ results = params['systems'] = self.remote.find_system(criteria,True)
+ elif what == "image":
+ results = params['images'] = self.remote.find_image(criteria,True)
+ elif what == "repo":
+ results = params['repos'] = self.remote.find_repo(criteria,True)
+ else:
+ raise "internal error, unknown search type"
+
+
+ if len(results) > 0:
+ return self.__render( "%s_list.tmpl" % what, params)
+ else:
+ return self.__render('empty.tmpl', { 'search' : 1 })
+
+ def distro_search_execute(self,key1=None,value1=None,key2=None,value2=None,key3=None,value3=None,**rest):
+ return self.__search_execute("distro",key1,value1,key2,value2,key3,value3)
+ def profile_search_execute(self,key1=None,value1=None,key2=None,value2=None,key3=None,value3=None,**rest):
+ return self.__search_execute("profile",key1,value1,key2,value2,key3,value3)
+ def system_search_execute(self,key1=None,value1=None,key2=None,value2=None,key3=None,value3=None,**rest):
+ return self.__search_execute("system",key1,value1,key2,value2,key3,value3)
+ def image_search_execute(self,key1=None,value1=None,key2=None,value2=None,key3=None,value3=None,**rest):
+ return self.__search_execute("image",key1,value1,key2,value2,key3,value3)
+ def repo_search_execute(self,key1=None,value1=None,key2=None,value2=None,key3=None,value3=None,**rest):
+ return self.__search_execute("repo",key1,value1,key2,value2,key3,value3)
+
+ def __search(self, what):
+ caption = ""
+ dest = ""
+ if what == "distro":
+ caption = "Search distros"
+ dest = "distro_search_execute"
+ elif what == "profile":
+ caption = "Search profiles"
+ dest = "profile_search_execute"
+ elif what == "system":
+ caption = "Search systems"
+ dest = "system_search_execute"
+ elif what == "repo":
+ caption = "Search repos"
+ dest = "repo_search_execute"
+ elif what == "image":
+ caption = "Search image"
+ dest = "image_search_execute"
+ else:
+ raise "internal error, unknown object type in search"
+
+ return self.__render('search.tmpl', {
+ 'what' : what,
+ 'caption' : caption,
+ 'submit_dest' : dest
+ })
+
+ def distro_search(self,**spam):
+ return self.__search('distro')
+
def distro_list(self,page=None,limit=None,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -197,7 +276,7 @@ class CobblerWeb(object):
def distro_save(self,name=None,comment=None,oldname=None,new_or_edit=None,editmode='edit',kernel=None,
initrd=None,kopts=None,koptspost=None,ksmeta=None,owners=None,arch=None,breed=None,redhatmanagementkey=None,
- osversion=None,delete1=False,delete2=False,recursive=False,**args):
+ mgmt_classes=None,osversion=None,delete1=False,delete2=False,recursive=False,**args):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -258,6 +337,8 @@ class CobblerWeb(object):
self.remote.modify_distro(distro, 'os-version', osversion, self.token)
self.remote.modify_distro(distro, 'comment', comment, self.token)
self.remote.modify_distro(distro, 'redhat_management_key', redhatmanagementkey, self.token)
+ self.remote.modify_distro(distro, 'redhat_management_server', redhatmanagementserver, self.token)
+ self.remote.modify_distro(distro, 'mgmt_classes', mgmt_classes, self.token)
# now time to save, do we want to run duplication checks?
self.remote.save_distro(distro, self.token, editmode)
@@ -302,6 +383,9 @@ class CobblerWeb(object):
pages = total_size / results_per_page
return (page, results_per_page, pages)
+
+ def system_menu(self,**spam):
+ return self.__render('blank.tmpl',{ 'more_blank' : 1})
def system_list(self,page=None,limit=None,**spam):
@@ -323,50 +407,225 @@ class CobblerWeb(object):
else:
return self.__render('empty.tmpl',{})
- def system_save(self,name=None,oldname=None,comment=None,editmode="edit",profile=None,
- new_or_edit=None,
+
+ def system_list_action(self,actionname=None,targetlist=None,**args):
+ if not self.__xmlrpc_setup():
+ return self.xmlrpc_auth_failure()
+
+ if actionname is None:
+ return self.error_page("Actionname parameter is REQUIRED.")
+
+ if actionname == 'add':
+ return self.system_edit_new()
+
+ if targetlist is None:
+ return self.error_page("Targetlist parameter is REQUIRED.")
+
+ # Single item actions
+ if actionname == 'copy':
+ return self.system_edit_copy(targetlist)
+ if actionname == 'edit':
+ return self.system_edit(targetlist)
+
+ # Multiple items actions
+ systems=[]
+ for targetname in targetlist.split():
+ systems.append(self.remote.get_system(targetname,self.token))
+
+ return self.__render( 'system_'+actionname+'.tmpl', {
+ 'systems' : systems,
+ 'profiles' : self.remote.get_profiles(),
+ 'targetlist' : targetlist,
+ } )
+
+
+ def system_netboot(self,targetlist=None,netboot=None,**args):
+ if not self.__xmlrpc_setup():
+ return self.xmlrpc_auth_failure()
+ if targetlist is None:
+ return self.error_page("Targetlist parameter is REQUIRED.")
+ if netboot is None:
+ return self.error_page("Netboot parameter is REQUIRED.")
+ try:
+ systems=[]
+ for targetname in targetlist.split():
+ systems.append(self.remote.get_system_handle(targetname,self.token))
+ for system in systems:
+ self.remote.modify_system(system, 'netboot-enabled', netboot, self.token)
+ self.remote.save_system(system, self.token)
+ return self.system_list()
+ except Exception, e:
+ log_exc(self.apache)
+ return self.error_page("Error while saving system: %s" % str(e))
+
+
+ def system_profile(self,targetlist=None,profile=None,**args):
+ if not self.__xmlrpc_setup():
+ return self.xmlrpc_auth_failure()
+ if targetlist is None:
+ return self.error_page("Targetlist parameter is REQUIRED.")
+ if profile is None:
+ return self.error_page("Profile parameter is REQUIRED.")
+ try:
+ systems=[]
+ for targetname in targetlist.split():
+ systems.append(self.remote.get_system_handle(targetname,self.token))
+ for system in systems:
+ self.remote.modify_system(system, 'profile', profile, self.token)
+ self.remote.save_system(system, self.token)
+ return self.system_list()
+ except Exception, e:
+ log_exc(self.apache)
+ return self.error_page("Error while saving system: %s" % str(e))
+
+
+ def system_power(self,targetlist=None,power=None,**args):
+ if not self.__xmlrpc_setup():
+ return self.xmlrpc_auth_failure()
+ if targetlist is None:
+ return self.error_page("Targetlist parameter is REQUIRED.")
+ if power is None:
+ return self.error_page("Power parameter is REQUIRED.")
+ try:
+ systems=[]
+ for targetname in targetlist.split():
+ systems.append(self.remote.get_system_handle(targetname,self.token))
+ for system in systems:
+ self.remote.power_system(system, power, self.token)
+ return self.system_list()
+ except Exception, e:
+ log_exc(self.apache)
+ return self.error_page("Error while controlling power of system: %s" % str(e))
+
+
+ def system_rename(self,targetlist=None,name=None,**args):
+ if not self.__xmlrpc_setup():
+ return self.xmlrpc_auth_failure()
+ if targetlist is None:
+ return self.error_page("Targetlist parameter is REQUIRED.")
+ try:
+ systems=[]
+ for targetname in targetlist.split():
+ systems.append(self.remote.get_system_handle(targetname,self.token))
+ for system in systems:
+ self.remote.rename_system(system, name, self.token)
+ return self.system_list()
+ except Exception, e:
+ log_exc(self.apache)
+ return self.error_page("Error while renaming system: %s" % str(e))
+
+
+ def system_delete(self,targetlist=None,**args):
+ if not self.__xmlrpc_setup():
+ return self.xmlrpc_auth_failure()
+ if targetlist is None:
+ return self.error_page("Targetlist parameter is REQUIRED.")
+ try:
+ for targetname in targetlist.split():
+ self.remote.remove_system(targetname, self.token)
+ return self.system_list()
+ except Exception, e:
+ log_exc(self.apache)
+ return self.error_page("Error while deleting system: %s" % str(e))
+
+
+
+ def system_edit(self, name=None,**spam):
+ if not self.__xmlrpc_setup():
+ return self.xmlrpc_auth_failure()
+
+ if name is None:
+ return self.error_page("Name parameter is REQUIRED")
+ input_system = self.remote.get_system(name,True)
+ can_edit = self.remote.check_access_no_fail(self.token,"modify_system",name)
+
+ return self.__render( 'system_edit.tmpl', {
+ 'user' : self.username,
+ 'editmode' : 'edit',
+ 'editable' : can_edit,
+ 'system': input_system,
+ 'profiles': self.remote.get_profiles()
+ } )
+
+
+ def system_edit_new(self,**spam):
+ if not self.__xmlrpc_setup():
+ return self.xmlrpc_auth_failure()
+
+ can_edit = self.remote.check_access_no_fail(self.token,"new_system",None)
+ if not can_edit:
+ return self.__render('message.tmpl', {
+ 'message1' : "Access denied.",
+ 'message2' : "You do not have permission to create new objects."
+ })
+
+ return self.__render( 'system_edit.tmpl', {
+ 'user' : self.username,
+ 'editmode' : 'new',
+ 'editable' : True,
+ 'system': None,
+ 'profiles': self.remote.get_profiles()
+ } )
+
+
+ def system_edit_copy(self, name=None,**spam):
+ if not self.__xmlrpc_setup():
+ return self.xmlrpc_auth_failure()
+
+ if name is None:
+ return self.error_page("Name parameter is REQUIRED")
+
+ can_edit = self.remote.check_access_no_fail(self.token,"new_system",None)
+ if not can_edit:
+ return self.__render('message.tmpl', {
+ 'message1' : "Access denied.",
+ 'message2' : "You do not have permission to create new objects."
+ })
+
+ input_system = self.remote.get_system(name,True)
+
+ return self.__render( 'system_edit.tmpl', {
+ 'user' : self.username,
+ 'editmode' : 'copy',
+ 'editable' : True,
+ 'system': input_system,
+ 'profiles': self.remote.get_profiles()
+ } )
+
+
+ def system_save(self,name=None,comment=None,editmode="edit",profile=None,
kopts=None, koptspost=None, ksmeta=None, owners=None, server_override=None, netboot='n',
virtpath=None,virtram=None,virttype=None,virtcpus=None,virtfilesize=None,
- name_servers=None,
+ name_servers=None,name_servers_search=None,
power_type=None, power_user=None, power_pass=None, power_id=None, power_address=None,
- gateway=None,hostname=None,redhatmanagementkey=None,delete1=None, delete2=None, **args):
+ gateway=None,hostname=None,redhatmanagementkey=None,mgmt_classes=None,delete1=None, delete2=None, **args):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
# parameter checking
- if name is None and oldname is not None:
- name = oldname
if name is None:
return self.error_page("System name parameter is REQUIRED.")
- if (editmode == 'rename' or editmode == 'copy') and name == oldname:
- return self.error_page("The name has not been changed.")
-
- # handle deletes as a special case
- if new_or_edit == 'edit' and delete1 and delete2:
- try:
- self.remote.remove_system(name,self.token)
- except Exception, e:
- return self.error_page("could not delete %s, %s" % (name,str(e)))
- return self.system_list()
# grab a reference to the object
- if new_or_edit == "edit" and editmode in [ "edit", "rename" ] :
+ if editmode == "edit":
try:
- if editmode == "edit":
- system = self.remote.get_system_handle( name, self.token )
- else:
- system = self.remote.get_system_handle( oldname, self.token )
-
+ system = self.remote.get_system_handle( name, self.token )
except:
return self.error_page("Failed to lookup system: %s" % name)
else:
+ try:
+ system = self.remote.get_system_handle( name, self.token )
+ except:
+ system = None
+ if system is not None:
+ return self.error_page("Failed to create new system: %s already exists." % name)
system = self.remote.new_system( self.token )
# go!
try:
- if editmode != "rename" and name:
+ if editmode != "edit":
self.remote.modify_system(system, 'name', name, self.token )
self.remote.modify_system(system, 'profile', profile, self.token)
self.remote.modify_system(system, 'kopts', kopts, self.token)
@@ -390,9 +649,12 @@ class CobblerWeb(object):
self.remote.modify_system(system, 'power_id', power_id, self.token)
self.remote.modify_system(system, 'power_address', power_address, self.token)
self.remote.modify_system(system, 'name_servers', name_servers, self.token)
+ self.remote.modify_system(system, 'name_servers_search', name_servers_search, self.token)
self.remote.modify_system(system, 'gateway', gateway, self.token)
self.remote.modify_system(system, 'hostname', hostname, self.token)
self.remote.modify_system(system, 'redhat_management_key', redhatmanagementkey, self.token)
+ self.remote.modify_system(system, 'redhat_management_server', redhatmanagementserver, self.token)
+ self.remote.modify_system(system, 'mgmt_classes', mgmt_classes, self.token)
interfaces = args.get("interface_list","")
interfaces = interfaces.split(",")
@@ -439,46 +701,21 @@ class CobblerWeb(object):
log_exc(self.apache)
return self.error_page("Error while saving system: %s" % str(e))
-
-
- if editmode == "rename" and name != oldname:
- try:
- self.remote.rename_system(system, name, self.token)
- except Exception, e:
- return self.error_page("Rename unsuccessful")
-
return self.system_list()
-
- def system_edit(self, name=None,**spam):
-
- if not self.__xmlrpc_setup():
- return self.xmlrpc_auth_failure()
-
- input_system = None
- if name is not None:
- input_system = self.remote.get_system(name,True)
- can_edit = self.remote.check_access_no_fail(self.token,"modify_system",name)
- else:
- can_edit = self.remote.check_access_no_fail(self.token,"new_system",None)
- if not can_edit:
- return self.__render('message.tmpl', {
- 'message1' : "Access denied.",
- 'message2' : "You do not have permission to create new objects."
- })
-
-
- return self.__render( 'system_edit.tmpl', {
- 'user' : self.username,
- 'edit' : True,
- 'editable' : can_edit,
- 'system': input_system,
- 'profiles': self.remote.get_profiles()
- } )
+ def system_search(self,**spam):
+ return self.__search('system')
# ------------------------------------------------------------------------ #
# Profiles
# ------------------------------------------------------------------------ #
+
+ def profile_search(self,**spam):
+ return self.__search('profile')
+
+ def profile_menu(self,**spam):
+ return self.__render('blank.tmpl', { 'more_blank' : 1})
+
def profile_list(self,page=None,limit=None,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -537,7 +774,7 @@ class CobblerWeb(object):
ksmeta=None,owners=None,enablemenu=None,virtfilesize=None,virtram=None,virttype=None,
virtpath=None,repos=None,dhcptag=None,delete1=False,delete2=False,
parent=None,virtcpus=None,virtbridge=None,subprofile=None,server_override=None,
- name_servers=None,redhatmanagementkey=None,recursive=False,**args):
+ name_servers=None,name_servers_search=None,redhatmanagementkey=None,mgmt_classes=None,recursive=False,**args):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -604,7 +841,10 @@ class CobblerWeb(object):
self.remote.modify_profile(profile, 'server', server_override, self.token)
self.remote.modify_profile(profile, 'comment', comment, self.token)
self.remote.modify_profile(profile, 'name_servers', name_servers, self.token)
+ self.remote.modify_profile(profile, 'name_servers_search', name_servers_search, self.token)
self.remote.modify_profile(profile, 'redhat_management_key', redhatmanagementkey, self.token)
+ self.remote.modify_profile(profile, 'redhat_management_server', redhatmanagementserver, self.token)
+ self.remote.modify_profile(profile, 'mgmt_classes', mgmt_classes, self.token)
if repos is None:
repos = []
@@ -634,6 +874,12 @@ class CobblerWeb(object):
# Repos
# ------------------------------------------------------------------------ #
+ def repo_search(self,**spam):
+ return self.__search('repo')
+
+ def repo_menu(self,**spam):
+ return self.__render('blank.tmpl', { 'more_blank' : 1})
+
def repo_list(self,page=None,limit=None,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -748,6 +994,12 @@ class CobblerWeb(object):
# Images
# ------------------------------------------------------------------------ #
+ def image_search(self,**spam):
+ return self.__search('image')
+
+ def image_menu(self,**spam):
+ return self.__render('blank.tmpl', { 'more_blank' : 1})
+
def image_list(self,page=None,limit=None,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -871,6 +1123,9 @@ class CobblerWeb(object):
# Kickstart files
# ------------------------------------------------------------------------ #
+ def ksfile_menu(self,**spam):
+ return self.__render('blank.tmpl', { 'more_blank' : 1})
+
def ksfile_list(self,**spam):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -982,26 +1237,49 @@ class CobblerWeb(object):
index.exposed = True
menu.exposed = True
+ distro_menu.exposed = True
distro_edit.exposed = True
distro_list.exposed = True
distro_save.exposed = True
-
+ distro_search.exposed = True
+ distro_search_execute.exposed = True
+
+ profile_menu.exposed = True
subprofile_edit.exposed = True
profile_edit.exposed = True
profile_list.exposed = True
+ profile_search.exposed = True
profile_save.exposed = True
+ profile_search_execute.exposed = True
+ system_menu.exposed = True
system_edit.exposed = True
+ system_edit_new.exposed = True
+ system_edit_copy.exposed = True
system_list.exposed = True
+ system_list_action.exposed = True
+ system_netboot.exposed = True
+ system_profile.exposed = True
+ system_power.exposed = True
+ system_rename.exposed = True
+ system_delete.exposed = True
system_save.exposed = True
+ system_search.exposed = True
+ system_search_execute.exposed = True
+ repo_menu.exposed = True
repo_edit.exposed = True
repo_list.exposed = True
repo_save.exposed = True
+ repo_search.exposed = True
+ repo_search_execute.exposed = True
+ image_menu.exposed = True
image_edit.exposed = True
image_list.exposed = True
image_save.exposed = True
+ image_search.exposed = True
+ image_search_execute.exposed = True
settings_view.exposed = True
ksfile_edit.exposed = True
diff --git a/cobbler/yaml/__init__.py b/cobbler/yaml/__init__.py
deleted file mode 100644
index bd21b40e..00000000
--- a/cobbler/yaml/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-__version__ = "0.32"
-from load import loadFile, load, Parser, l
-from dump import dump, dumpToFile, Dumper, d
-from stream import YamlLoaderException, StringStream, FileStream
-from timestamp import timestamp
-import sys
-if sys.hexversion >= 0x02020000:
- from redump import loadOrdered
-
-try:
- from ypath import ypath
-except NameError:
- def ypath(expr,target='',cntx=''):
- raise NotImplementedError("ypath requires Python 2.2")
-
-if sys.hexversion < 0x02010000:
- raise 'YAML is not tested for pre-2.1 versions of Python'
diff --git a/cobbler/yaml/dump.py b/cobbler/yaml/dump.py
deleted file mode 100644
index eb34955b..00000000
--- a/cobbler/yaml/dump.py
+++ /dev/null
@@ -1,305 +0,0 @@
-"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-
-import types
-import string
-from types import StringType, UnicodeType, IntType, FloatType
-from types import DictType, ListType, TupleType, InstanceType
-from klass import hasMethod, isDictionary
-import re
-
-"""
- The methods from this module that are exported to the top
- level yaml package should remain stable. If you call
- directly into other methods of this module, be aware that
- they may change or go away in future implementations.
- Contact the authors if there are methods in this file
- that you wish to remain stable.
-"""
-
-def dump(*data):
- return Dumper().dump(*data)
-
-def d(data): return dump(data)
-
-def dumpToFile(file, *data):
- return Dumper().dumpToFile(file, *data)
-
-class Dumper:
- def __init__(self):
- self.currIndent = "\n"
- self.indent = " "
- self.keysrt = None
- self.alphaSort = 1 # legacy -- on by default
-
- def setIndent(self, indent):
- self.indent = indent
- return self
-
- def setSort(self, sort_hint):
- self.keysrt = sortMethod(sort_hint)
- return self
-
- def dump(self, *data):
- self.result = []
- self.output = self.outputToString
- self.dumpDocuments(data)
- return string.join(self.result,"")
-
- def outputToString(self, data):
- self.result.append(data)
-
- def dumpToFile(self, file, *data):
- self.file = file
- self.output = self.outputToFile
- self.dumpDocuments(data)
-
- def outputToFile(self, data):
- self.file.write(data)
-
- def dumpDocuments(self, data):
- for obj in data:
- self.anchors = YamlAnchors(obj)
- self.output("---")
- self.dumpData(obj)
- self.output("\n")
-
- def indentDump(self, data):
- oldIndent = self.currIndent
- self.currIndent += self.indent
- self.dumpData(data)
- self.currIndent = oldIndent
-
- def dumpData(self, data):
- anchor = self.anchors.shouldAnchor(data)
- # Disabling anchors because they are lame for strings that the user might want to view/edit -- mdehaan
- #
- #if anchor:
- # self.output(" &%d" % anchor )
- #else:
- # anchor = self.anchors.isAlias(data)
- # if anchor:
- # self.output(" *%d" % anchor )
- # return
- if (data is None):
- self.output(' ~')
- elif hasMethod(data, 'to_yaml'):
- self.dumpTransformedObject(data)
- elif hasMethod(data, 'to_yaml_implicit'):
- self.output(" " + data.to_yaml_implicit())
- elif type(data) is InstanceType:
- self.dumpRawObject(data)
- elif isDictionary(data):
- self.dumpDict(data)
- elif type(data) in [ListType, TupleType]:
- self.dumpList(data)
- else:
- self.dumpScalar(data)
-
- def dumpTransformedObject(self, data):
- obj_yaml = data.to_yaml()
- if type(obj_yaml) is not TupleType:
- self.raiseToYamlSyntaxError()
- (data, typestring) = obj_yaml
- if typestring:
- self.output(" " + typestring)
- self.dumpData(data)
-
- def dumpRawObject(self, data):
- self.output(' !!%s.%s' % (data.__module__, data.__class__.__name__))
- self.dumpData(data.__dict__)
-
- def dumpDict(self, data):
- keys = data.keys()
- if len(keys) == 0:
- self.output(" {}")
- return
- if self.keysrt:
- keys = sort_keys(keys,self.keysrt)
- else:
- if self.alphaSort:
- keys.sort()
- for key in keys:
- self.output(self.currIndent)
- self.dumpKey(key)
- self.output(":")
- self.indentDump(data[key])
-
- def dumpKey(self, key):
- if type(key) is TupleType:
- self.output("?")
- self.indentDump(key)
- self.output("\n")
- else:
- self.output(quote(key))
-
- def dumpList(self, data):
- if len(data) == 0:
- self.output(" []")
- return
- for item in data:
- self.output(self.currIndent)
- self.output("-")
- self.indentDump(item)
-
- def dumpScalar(self, data):
- if isUnicode(data):
- self.output(' "%s"' % repr(data)[2:-1])
- elif isMulti(data):
- self.dumpMultiLineScalar(data.splitlines())
- else:
- self.output(" ")
- self.output(quote(data))
-
- def dumpMultiLineScalar(self, lines):
- self.output(" |")
- if lines[-1] == "":
- self.output("+")
- for line in lines:
- self.output(self.currIndent)
- self.output(line)
-
- def raiseToYamlSyntaxError(self):
- raise """
-to_yaml should return tuple w/object to dump
-and optional YAML type. Example:
-({'foo': 'bar'}, '!!foobar')
-"""
-
-#### ANCHOR-RELATED METHODS
-
-def accumulate(obj,occur):
- typ = type(obj)
- if obj is None or \
- typ is IntType or \
- typ is FloatType or \
- ((typ is StringType or typ is UnicodeType) \
- and len(obj) < 32): return
- obid = id(obj)
- if 0 == occur.get(obid,0):
- occur[obid] = 1
- if typ is ListType:
- for x in obj:
- accumulate(x,occur)
- if typ is DictType:
- for (x,y) in obj.items():
- accumulate(x,occur)
- accumulate(y,occur)
- else:
- occur[obid] = occur[obid] + 1
-
-class YamlAnchors:
- def __init__(self,data):
- occur = {}
- accumulate(data,occur)
- anchorVisits = {}
- for (obid, occur) in occur.items():
- if occur > 1:
- anchorVisits[obid] = 0
- self._anchorVisits = anchorVisits
- self._currentAliasIndex = 0
- def shouldAnchor(self,obj):
- ret = self._anchorVisits.get(id(obj),None)
- if 0 == ret:
- self._currentAliasIndex = self._currentAliasIndex + 1
- ret = self._currentAliasIndex
- self._anchorVisits[id(obj)] = ret
- return ret
- return 0
- def isAlias(self,obj):
- return self._anchorVisits.get(id(obj),0)
-
-### SORTING METHODS
-
-def sort_keys(keys,fn):
- tmp = []
- for key in keys:
- val = fn(key)
- if val is None: val = '~'
- tmp.append((val,key))
- tmp.sort()
- return [ y for (x,y) in tmp ]
-
-def sortMethod(sort_hint):
- typ = type(sort_hint)
- if DictType == typ:
- return sort_hint.get
- elif ListType == typ or TupleType == typ:
- indexes = {}; idx = 0
- for item in sort_hint:
- indexes[item] = idx
- idx += 1
- return indexes.get
- else:
- return sort_hint
-
-### STRING QUOTING AND SCALAR HANDLING
-def isStr(data):
- # XXX 2.1 madness
- if type(data) == type(''):
- return 1
- if type(data) == type(u''):
- return 1
- return 0
-
-def doubleUpQuotes(data):
- return data.replace("'", "''")
-
-def quote(data):
- if not isStr(data):
- return str(data)
- single = "'"
- double = '"'
- quote = ''
- if len(data) == 0:
- return "''"
- if hasSpecialChar(data) or data[0] == single:
- data = `data`[1:-1]
- data = string.replace(data, r"\x08", r"\b")
- quote = double
- elif needsSingleQuote(data):
- quote = single
- data = doubleUpQuotes(data)
- return "%s%s%s" % (quote, data, quote)
-
-def needsSingleQuote(data):
- if re.match(r"^-?\d", data):
- return 1
- if re.match(r"\*\S", data):
- return 1
- if data[0] in ['&', ' ']:
- return 1
- if data[0] == '"':
- return 1
- if data[-1] == ' ':
- return 1
- return (re.search(r'[:]', data) or re.search(r'(\d\.){2}', data))
-
-def hasSpecialChar(data):
- # need test to drive out '#' from this
- return re.search(r'[\t\b\r\f#]', data)
-
-def isMulti(data):
- if not isStr(data):
- return 0
- if hasSpecialChar(data):
- return 0
- return re.search("\n", data)
-
-def isUnicode(data):
- return type(data) == unicode
-
-def sloppyIsUnicode(data):
- # XXX - hack to make tests pass for 2.1
- return repr(data)[:2] == "u'" and repr(data) != data
-
-import sys
-if sys.hexversion < 0x20200000:
- isUnicode = sloppyIsUnicode
-
-
-
diff --git a/cobbler/yaml/implicit.py b/cobbler/yaml/implicit.py
deleted file mode 100644
index 49d65e03..00000000
--- a/cobbler/yaml/implicit.py
+++ /dev/null
@@ -1,52 +0,0 @@
-"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-import re
-import string
-from timestamp import timestamp, matchTime
-
-DATETIME_REGEX = re.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}$")
-FLOAT_REGEX = re.compile("^[-+]?[0-9][0-9,]*\.[0-9]*$")
-SCIENTIFIC_REGEX = re.compile("^[-+]?[0-9]+(\.[0-9]*)?[eE][-+][0-9]+$")
-OCTAL_REGEX = re.compile("^[-+]?([0][0-7,]*)$")
-HEX_REGEX = re.compile("^[-+]?0x[0-9a-fA-F,]+$")
-INT_REGEX = re.compile("^[-+]?(0|[1-9][0-9,]*)$")
-
-def convertImplicit(val):
- if val == '~':
- return None
- if val == '+':
- return 1
- if val == '-':
- return 0
- if val[0] == "'" and val[-1] == "'":
- val = val[1:-1]
- return string.replace(val, "''", "\'")
- if val[0] == '"' and val[-1] == '"':
- if re.search(r"\u", val):
- val = "u" + val
- unescapedStr = eval (val)
- return unescapedStr
- if matchTime.match(val):
- return timestamp(val)
- if INT_REGEX.match(val):
- return int(cleanseNumber(val))
- if OCTAL_REGEX.match(val):
- return int(val, 8)
- if HEX_REGEX.match(val):
- return int(val, 16)
- if FLOAT_REGEX.match(val):
- return float(cleanseNumber(val))
- if SCIENTIFIC_REGEX.match(val):
- return float(cleanseNumber(val))
- return val
-
-def cleanseNumber(str):
- if str[0] == '+':
- str = str[1:]
- str = string.replace(str,',','')
- return str
-
diff --git a/cobbler/yaml/inline.py b/cobbler/yaml/inline.py
deleted file mode 100644
index d4f6439a..00000000
--- a/cobbler/yaml/inline.py
+++ /dev/null
@@ -1,44 +0,0 @@
-"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-import re
-import string
-
-class InlineTokenizer:
- def __init__(self, data):
- self.data = data
-
- def punctuation(self):
- puncts = [ '[', ']', '{', '}' ]
- for punct in puncts:
- if self.data[0] == punct:
- self.data = self.data[1:]
- return punct
-
- def up_to_comma(self):
- match = re.match('(.*?)\s*, (.*)', self.data)
- if match:
- self.data = match.groups()[1]
- return match.groups()[0]
-
- def up_to_end_brace(self):
- match = re.match('(.*?)(\s*[\]}].*)', self.data)
- if match:
- self.data = match.groups()[1]
- return match.groups()[0]
-
- def next(self):
- self.data = string.strip(self.data)
- productions = [
- self.punctuation,
- self.up_to_comma,
- self.up_to_end_brace
- ]
- for production in productions:
- token = production()
- if token:
- return token
-
diff --git a/cobbler/yaml/klass.py b/cobbler/yaml/klass.py
deleted file mode 100644
index c182fcf2..00000000
--- a/cobbler/yaml/klass.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-import new
-import re
-
-class DefaultResolver:
- def resolveType(self, data, typestring):
- match = re.match('!!(.*?)\.(.*)', typestring)
- if not match:
- raise "Invalid private type specifier"
- (modname, classname) = match.groups()
- return makeClass(modname, classname, data)
-
-def makeClass(module, classname, dict):
- exec('import %s' % (module))
- klass = eval('%s.%s' % (module, classname))
- obj = new.instance(klass)
- if hasMethod(obj, 'from_yaml'):
- return obj.from_yaml(dict)
- obj.__dict__ = dict
- return obj
-
-def hasMethod(object, method_name):
- try:
- klass = object.__class__
- except:
- return 0
- if not hasattr(klass, method_name):
- return 0
- method = getattr(klass, method_name)
- if not callable(method):
- return 0
- return 1
-
-def isDictionary(data):
- return isinstance(data, dict)
-
-try:
- isDictionary({})
-except:
- def isDictionary(data): return type(data) == type({}) # XXX python 2.1
-
-if __name__ == '__main__':
- print isDictionary({'foo': 'bar'})
- try:
- print isDictionary(dict())
- from ordered_dict import OrderedDict
- print isDictionary(OrderedDict())
- except:
- pass
diff --git a/cobbler/yaml/load.py b/cobbler/yaml/load.py
deleted file mode 100644
index 54931d67..00000000
--- a/cobbler/yaml/load.py
+++ /dev/null
@@ -1,333 +0,0 @@
-"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-import re, string
-from implicit import convertImplicit
-from inline import InlineTokenizer
-from klass import DefaultResolver
-from stream import YamlLoaderException, FileStream, StringStream, NestedDocs
-
-try:
- iter(list()) # is iter supported by this version of Python?
-except:
- # XXX - Python 2.1 does not support iterators
- class StopIteration: pass
- class iter:
- def __init__(self,parser):
- self._docs = []
- try:
- while 1:
- self._docs.append(parser.next())
- except StopIteration: pass
- self._idx = 0
- def __len__(self): return len(self._docs)
- def __getitem__(self,idx): return self._docs[idx]
- def next(self):
- if self._idx < len(self._docs):
- ret = self._docs[self._idx]
- self._idx = self._idx + 1
- return ret
- raise StopIteration
-
-def loadFile(filename, typeResolver=None):
- return loadStream(FileStream(filename),typeResolver)
-
-def load(str, typeResolver=None):
- return loadStream(StringStream(str), typeResolver)
-
-def l(str): return load(str).next()
-
-def loadStream(stream, typeResolver):
- return iter(Parser(stream, typeResolver))
-
-def tryProductions(productions, value):
- for production in productions:
- results = production(value)
- if results:
- (ok, result) = results
- if ok:
- return (1, result)
-
-def dumpDictionary(): return {}
-
-class Parser:
- def __init__(self, stream, typeResolver=None):
- try:
- self.dictionary = dict
- except:
- self.dictionary = dumpDictionary
- self.nestedDocs = NestedDocs(stream)
- self.aliases = {}
- if typeResolver:
- self.typeResolver = typeResolver
- else:
- self.typeResolver = DefaultResolver()
-
- def error(self, msg):
- self.nestedDocs.error(msg, self.line)
-
- def nestPop(self):
- line = self.nestedDocs.pop()
- if line is not None:
- self.line = line
- return 1
-
- def value(self, indicator):
- return getToken(indicator+"\s*(.*)", self.line)
-
- def getNextDocument(self): raise "getNextDocument() deprecated--use next()"
-
- def next(self):
- line = self.nestedDocs.popDocSep()
- indicator = getIndicator(line)
- if indicator:
- return self.parse_value(indicator)
- if line:
- self.nestedDocs.nestToNextLine()
- return self.parseLines()
- raise StopIteration
-
- def __iter__(self): return self
-
- def parseLines(self):
- peekLine = self.nestedDocs.peek()
- if peekLine:
- if re.match("\s*-", peekLine):
- return self.parse_collection([], self.parse_seq_line)
- else:
- return self.parse_collection(self.dictionary(), self.parse_map_line)
- raise StopIteration
-
- def parse_collection(self, items, lineParser):
- while self.nestPop():
- if self.line:
- lineParser(items)
- return items
-
- def parse_seq_line(self, items):
- value = self.value("-")
- if value is not None:
- items.append(self.parse_seq_value(value))
- else:
- self.error("missing '-' for seq")
-
- def parse_map_line(self, items):
- if (self.line == '?'):
- self.parse_map_line_nested(items)
- else:
- self.parse_map_line_simple(items, self.line)
-
- def parse_map_line_nested(self, items):
- self.nestedDocs.nestToNextLine()
- key = self.parseLines()
- if self.nestPop():
- value = self.value(':')
- if value is not None:
- items[tuple(key)] = self.parse_value(value)
- return
- self.error("key has no value for nested map")
-
- def parse_map_line_simple(self, items, line):
- map_item = self.key_value(line)
- if map_item:
- (key, value) = map_item
- key = convertImplicit(key)
- if items.has_key(key):
- self.error("Duplicate key "+key)
- items[key] = self.parse_value(value)
- else:
- self.error("bad key for map")
-
- def is_map(self, value):
- # XXX - need real tokenizer
- if len(value) == 0:
- return 0
- if value[0] == "'":
- return 0
- if re.search(':(\s|$)', value):
- return 1
-
- def parse_seq_value(self, value):
- if self.is_map(value):
- return self.parse_compressed_map(value)
- else:
- return self.parse_value(value)
-
- def parse_compressed_map(self, value):
- items = self.dictionary()
- line = self.line
- token = getToken("(\s*-\s*)", line)
- self.nestedDocs.nestBySpecificAmount(len(token))
- self.parse_map_line_simple(items, value)
- return self.parse_collection(items, self.parse_map_line)
-
- def parse_value(self, value):
- (alias, value) = self.testForRepeatOfAlias(value)
- if alias:
- return value
- (alias, value) = self.testForAlias(value)
- value = self.parse_unaliased_value(value)
- if alias:
- self.aliases[alias] = value
- return value
-
- def parse_unaliased_value(self, value):
- match = re.match(r"(!\S*)(.*)", value)
- if match:
- (url, value) = match.groups()
- value = self.parse_untyped_value(value)
- if url[:2] == '!!':
- return self.typeResolver.resolveType(value, url)
- else:
- # XXX - allows syntax, but ignores it
- return value
- return self.parse_untyped_value(value)
-
- def parseInlineArray(self, value):
- if re.match("\s*\[", value):
- return self.parseInline([], value, ']',
- self.parseInlineArrayItem)
-
- def parseInlineHash(self, value):
- if re.match("\s*{", value):
- return self.parseInline(self.dictionary(), value, '}',
- self.parseInlineHashItem)
-
- def parseInlineArrayItem(self, result, token):
- return result.append(convertImplicit(token))
-
- def parseInlineHashItem(self, result, token):
- (key, value) = self.key_value(token)
- result[key] = value
-
- def parseInline(self, result, value, end_marker, itemMethod):
- tokenizer = InlineTokenizer(value)
- tokenizer.next()
- while 1:
- token = tokenizer.next()
- if token == end_marker:
- break
- itemMethod(result, token)
- return (1, result)
-
- def parseSpecial(self, value):
- productions = [
- self.parseMultiLineScalar,
- self.parseInlineHash,
- self.parseInlineArray,
- ]
- return tryProductions(productions, value)
-
- def parse_untyped_value(self, value):
- parse = self.parseSpecial(value)
- if parse:
- (ok, data) = parse
- return data
- token = getToken("(\S.*)", value)
- if token:
- lines = [token] + \
- pruneTrailingEmpties(self.nestedDocs.popNestedLines())
- return convertImplicit(joinLines(lines))
- else:
- self.nestedDocs.nestToNextLine()
- return self.parseLines()
-
- def parseNative(self, value):
- return (1, convertImplicit(value))
-
- def parseMultiLineScalar(self, value):
- if value == '>':
- return (1, self.parseFolded())
- elif value == '|':
- return (1, joinLiteral(self.parseBlock()))
- elif value == '|+':
- return (1, joinLiteral(self.unprunedBlock()))
-
- def parseFolded(self):
- data = self.parseBlock()
- i = 0
- resultString = ''
- while i < len(data)-1:
- resultString = resultString + data[i]
- resultString = resultString + foldChar(data[i], data[i+1])
- i = i + 1
- return resultString + data[-1] + "\n"
-
- def unprunedBlock(self):
- self.nestedDocs.nestToNextLine()
- data = []
- while self.nestPop():
- data.append(self.line)
- return data
-
- def parseBlock(self):
- return pruneTrailingEmpties(self.unprunedBlock())
-
- def testForAlias(self, value):
- match = re.match("&(\S*)\s*(.*)", value)
- if match:
- return match.groups()
- return (None, value)
-
- def testForRepeatOfAlias(self, value):
- match = re.match("\*(\S+)", value)
- if match:
- alias = match.groups()[0]
- if self.aliases.has_key(alias):
- return (alias, self.aliases[alias])
- else:
- self.error("Unknown alias")
- return (None, value)
-
- def key_value(self, str):
- if str[-1] == ' ':
- self.error("Trailing spaces not allowed without quotes.")
- # XXX This allows mis-balanced " vs. ' stuff
- match = re.match("[\"'](.+)[\"']\s*:\s*(.*)", str)
- if match:
- (key, value) = match.groups()
- return (key, value)
- match = re.match("(.+?)\s*:\s*(.*)", str)
- if match:
- (key, value) = match.groups()
- if len(value) and value[0] == '#':
- value = ''
- return (key, value)
-
-def getToken(regex, value):
- match = re.search(regex, value)
- if match:
- return match.groups()[0]
-
-def pruneTrailingEmpties(data):
- while len(data) > 0 and data[-1] == '':
- data = data[:-1]
- return data
-
-def foldChar(line1, line2):
- if re.match("^\S", line1) and re.match("^\S", line2):
- return " "
- return "\n"
-
-def getIndicator(line):
- if line:
- header = r"(#YAML:\d+\.\d+\s*){0,1}"
- match = re.match("--- "+header+"(\S*.*)", line)
- if match:
- return match.groups()[-1]
-
-def joinLines(lines):
- result = ''
- for line in lines[:-1]:
- if line[-1] == '\\':
- result = result + line[:-1]
- else:
- result = result + line + " "
- return result + lines[-1]
-
-def joinLiteral(data):
- return string.join(data,"\n") + "\n"
-
diff --git a/cobbler/yaml/ordered_dict.py b/cobbler/yaml/ordered_dict.py
deleted file mode 100644
index 5bc2e3e0..00000000
--- a/cobbler/yaml/ordered_dict.py
+++ /dev/null
@@ -1,38 +0,0 @@
-"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-
-# This is extremely crude implementation of an OrderedDict.
-# If you know of a better implementation, please send it to
-# the author Steve Howell. You can find my email via
-# the YAML mailing list or wiki.
-
-class OrderedDict(dict):
- def __init__(self):
- self._keys = []
-
- def __setitem__(self, key, val):
- self._keys.append(key)
- dict.__setitem__(self, key, val)
-
- def keys(self):
- return self._keys
-
- def items(self):
- return [(key, self[key]) for key in self._keys]
-
-if __name__ == '__main__':
- data = OrderedDict()
- data['z'] = 26
- data['m'] = 13
- data['a'] = 1
- for key in data.keys():
- print "The value for %s is %s" % (key, data[key])
- print data
-
-
-
-
diff --git a/cobbler/yaml/redump.py b/cobbler/yaml/redump.py
deleted file mode 100644
index eefd68ec..00000000
--- a/cobbler/yaml/redump.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-from ordered_dict import OrderedDict
-from load import Parser
-from dump import Dumper
-from stream import StringStream
-
-def loadOrdered(stream):
- parser = Parser(StringStream(stream))
- parser.dictionary = OrderedDict
- return iter(parser)
-
-def redump(stream):
- docs = list(loadOrdered(stream))
- dumper = Dumper()
- dumper.alphaSort = 0
- return dumper.dump(*docs)
-
diff --git a/cobbler/yaml/stream.py b/cobbler/yaml/stream.py
deleted file mode 100644
index dcd65c34..00000000
--- a/cobbler/yaml/stream.py
+++ /dev/null
@@ -1,199 +0,0 @@
-"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-import re
-import string
-
-def indentLevel(line):
- n = 0
- while n < len(line) and line[n] == ' ':
- n = n + 1
- return n
-
-class LineNumberStream:
- def __init__(self, filename=None):
- self.curLine = 0
- self.filename = filename
-
- def get(self):
- line = self.getLine()
- self.curLine += 1 # used by subclass
- if line:
- line = noLineFeed(line)
- return line
-
- def lastLineRead(self):
- return self.curLine
-
-class FileStream(LineNumberStream):
- def __init__(self, filename):
- self.fp = open(filename)
- LineNumberStream.__init__(self, filename)
-
- def getLine(self):
- line = self.fp.readline()
- if line == '': line = None
- return line
-
-class StringStream(LineNumberStream):
- def __init__(self, text):
- self.lines = split(text)
- self.numLines = len(self.lines)
- LineNumberStream.__init__(self)
-
- def getLine(self):
- if self.curLine < self.numLines:
- return self.lines[self.curLine]
-
-def split(text):
- lines = string.split(text, '\n')
- if lines[-1] == '':
- lines.pop()
- return lines
-
-def eatNewLines(stream):
- while 1:
- line = stream.get()
- if line is None or len(string.strip(line)):
- return line
-
-COMMENT_LINE_REGEX = re.compile(R"\s*#")
-def isComment(line):
- return line is not None and COMMENT_LINE_REGEX.match(line)
-
-class CommentEater:
- def __init__(self, stream):
- self.stream = stream
- self.peeked = 1
- self.line = eatNewLines(stream)
- self.eatComments()
-
- def eatComments(self):
- while isComment(self.line):
- self.line = self.stream.get()
-
- def peek(self):
- if self.peeked:
- return self.line
- self.peeked = 1
- self.line = self.stream.get()
- self.eatComments()
- return self.line
-
- def lastLineRead(self):
- return self.stream.lastLineRead()
-
- def pop(self):
- data = self.peek()
- self.peeked = 0
- return data
-
-class NestedText:
- def __init__(self, stream):
- self.commentEater = CommentEater(stream)
- self.reset()
-
- def lastLineRead(self):
- return self.commentEater.lastLineRead()
-
- def reset(self):
- self.indentLevel = 0
- self.oldIndents = [0]
-
- def peek(self):
- nextLine = self.commentEater.peek()
- if nextLine is not None:
- if indentLevel(nextLine) >= self.indentLevel:
- return nextLine[self.indentLevel:]
- elif nextLine == '':
- return ''
-
- def pop(self):
- line = self.peek()
- if line is None:
- self.indentLevel = self.oldIndents.pop()
- return
- self.commentEater.pop()
- return line
-
- def popNestedLines(self):
- nextLine = self.peek()
- if nextLine is None or nextLine == '' or nextLine[0] != ' ':
- return []
- self.nestToNextLine()
- lines = []
- while 1:
- line = self.pop()
- if line is None:
- break
- lines.append(line)
- return lines
-
- def nestToNextLine(self):
- line = self.commentEater.peek()
- indentation = indentLevel(line)
- if len(self.oldIndents) > 1 and indentation <= self.indentLevel:
- self.error("Inadequate indentation", line)
- self.setNewIndent(indentation)
-
- def nestBySpecificAmount(self, adjust):
- self.setNewIndent(self.indentLevel + adjust)
-
- def setNewIndent(self, indentLevel):
- self.oldIndents.append(self.indentLevel)
- self.indentLevel = indentLevel
-
-class YamlLoaderException(Exception):
- def __init__(self, *args):
- (self.msg, self.lineNum, self.line, self.filename) = args
-
- def __str__(self):
- msg = """\
-%(msg)s:
-near line %(lineNum)d:
-%(line)s
-""" % self.__dict__
- if self.filename:
- msg += "file: " + self.filename
- return msg
-
-class NestedDocs(NestedText):
- def __init__(self, stream):
- self.filename = stream.filename
- NestedText.__init__(self,stream)
- line = NestedText.peek(self)
- self.sep = '---'
- if self.startsWithSep(line):
- self.eatenDocSep = NestedText.pop(self)
- else:
- self.eatenDocSep = self.sep
-
- def startsWithSep(self,line):
- if line and self.sep == line[:3]: return 1
- return 0
-
- def popDocSep(self):
- line = self.eatenDocSep
- self.eatenDocSep = None
- self.reset()
- return line
-
- def pop(self):
- if self.eatenDocSep is not None:
- raise "error"
- line = self.commentEater.peek()
- if line and self.startsWithSep(line):
- self.eatenDocSep = NestedText.pop(self)
- return None
- return NestedText.pop(self)
-
- def error(self, msg, line):
- raise YamlLoaderException(msg, self.lastLineRead(), line, self.filename)
-
-def noLineFeed(s):
- while s[-1:] in ('\n', '\r'):
- s = s[:-1]
- return s
diff --git a/cobbler/yaml/timestamp.py b/cobbler/yaml/timestamp.py
deleted file mode 100644
index 5c522f6e..00000000
--- a/cobbler/yaml/timestamp.py
+++ /dev/null
@@ -1,152 +0,0 @@
-"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-
-import time, re, string
-from types import ListType, TupleType
-
-PRIVATE_NOTICE = """
- This module is considered to be private implementation
- details and is subject to change. Please only use the
- objects and methods exported to the top level yaml package.
-"""
-
-#
-# Time specific operations
-#
-
-_splitTime = re.compile('\-|\s|T|t|:|\.|Z')
-matchTime = re.compile(\
- '\d+-\d+-\d+([\s|T|t]\d+:\d+:\d+.\d+(Z|(\s?[\-|\+]\d+:\d+)))?')
-
-def _parseTime(val):
- if not matchTime.match(val): raise ValueError(val)
- tpl = _splitTime.split(val)
- if not(tpl): raise ValueError(val)
- siz = len(tpl)
- sec = 0
- if 3 == siz:
- tpl += [0,0,0,0,0,-1]
- elif 7 == siz:
- tpl.append(0)
- tpl.append(-1)
- elif 8 == siz:
- if len(tpl.pop()) > 0: raise ValueError(val)
- tpl.append(0)
- tpl.append(-1)
- elif 9 == siz or 10 == siz:
- mn = int(tpl.pop())
- hr = int(tpl.pop())
- sec = (hr*60+mn)*60
- if val.find("+") > -1: sec = -sec
- if 10 == siz: tpl.pop()
- tpl.append(0)
- tpl.append(-1)
- else:
- raise ValueError(val)
- idx = 0
- while idx < 9:
- tpl[idx] = int(tpl[idx])
- idx += 1
- if tpl[1] < 1 or tpl[1] > 12: raise ValueError(val)
- if tpl[2] < 1 or tpl[2] > 31: raise ValueError(val)
- if tpl[3] > 24: raise ValueError(val)
- if tpl[4] > 61: raise ValueError(val)
- if tpl[5] > 61: raise ValueError(val)
- if tpl[0] > 2038:
- #TODO: Truncation warning
- tpl = (2038,1,18,0,0,0,0,0,-1)
- tpl = tuple(tpl)
- ret = time.mktime(tpl)
- ret = time.localtime(ret+sec)
- ret = ret[:8] + (0,)
- return ret
-
-
-class _timestamp:
- def __init__(self,val=None):
- if not val:
- self.__tval = time.gmtime()
- else:
- typ = type(val)
- if ListType == typ:
- self.__tval = tuple(val)
- elif TupleType == typ:
- self.__tval = val
- else:
- self.__tval = _parseTime(val)
- if 9 != len(self.__tval): raise ValueError
- def __getitem__(self,idx): return self.__tval[idx]
- def __len__(self): return 9
- def strftime(self,format): return time.strftime(format,self.__tval)
- def mktime(self): return time.mktime(self.__tval)
- def asctime(self): return time.asctime(self.__tval)
- def isotime(self):
- return "%04d-%02d-%02dT%02d:%02d:%02d.00Z" % self.__tval[:6]
- def __repr__(self): return "yaml.timestamp('%s')" % self.isotime()
- def __str__(self): return self.isotime()
- def to_yaml_implicit(self): return self.isotime()
- def __hash__(self): return hash(self.__tval[:6])
- def __cmp__(self,other):
- try:
- return cmp(self.__tval[:6],other.__tval[:6])
- except AttributeError:
- return -1
-
-try: # inherit from mx.DateTime functionality if available
- from mx import DateTime
- class timestamp(_timestamp):
- def __init__(self,val=None):
- _timestamp.__init__(self,val)
- self.__mxdt = DateTime.mktime(self.__tval)
- def __getattr__(self, name):
- return getattr(self.__mxdt, name)
-except:
- class timestamp(_timestamp): pass
-
-
-
-def unquote(expr):
- """
- summary: >
- Simply returns the unquoted string, and the
- length of the quoted string token at the
- beginning of the expression.
- """
- tok = expr[0]
- if "'" == tok:
- idx = 1
- odd = 0
- ret = ""
- while idx < len(expr):
- chr = expr[idx]
- if "'" == chr:
- if odd: ret += chr
- odd = not odd
- else:
- if odd:
- tok = expr[:idx]
- break
- ret += chr
- idx += 1
- if "'" == tok: tok = expr
- return (ret,len(tok))
- if '"' == tok:
- idx = 1
- esc = 0
- while idx < len(expr):
- chr = expr[idx]
- if '"' == chr and not esc:
- tok = expr[:idx] + '"'
- break
- if '\\' == chr and not esc: esc = 1
- else: esc = 0
- idx += 1
- if '"' == tok:
- raise SyntaxError("unmatched quote: " + expr)
- ret = eval(tok) #TODO: find better way to unquote
- return (ret,len(tok))
- return (expr,len(expr))
diff --git a/cobbler/yaml/ypath.py b/cobbler/yaml/ypath.py
deleted file mode 100644
index b183a231..00000000
--- a/cobbler/yaml/ypath.py
+++ /dev/null
@@ -1,469 +0,0 @@
-"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-
-from types import ListType, StringType, IntType, DictType, InstanceType
-import re
-from urllib import quote
-from timestamp import unquote
-
-noTarget = object()
-
-def escape(node):
- """
- summary: >
- This function escapes a given key so that it
- may appear within a ypath. URI style escaping
- is used so that ypath expressions can be a
- valid URI expression.
- """
- typ = type(node)
- if typ is IntType: return str(node)
- if typ is StringType:
- return quote(node,'')
- raise ValueError("TODO: Support more than just string and integer keys.")
-
-class context:
- """
- summary: >
- A ypath visit context through a YAML rooted graph.
- This is implemented as a 3-tuple including the parent
- node, the current key/index and the value. This is
- an immutable object so it can be cached.
- properties:
- key: mapping key or index within the parent collection
- value: current value within the parent's range
- parent: the parent context
- root: the very top of the yaml graph
- path: a tuple of the domain keys
- notes: >
- The context class doesn't yet handle going down the
- domain side of the tree...
- """
- def __init__(self,parent,key,value):
- """
- args:
- parent: parent context (or None if this is the root)
- key: mapping key or index for this context
- value: value of current location...
- """
- self.parent = parent
- self.key = key
- self.value = value
- if parent:
- assert parent.__class__ is self.__class__
- self.path = parent.path + (escape(key),)
- self.root = parent.root
- else:
- assert not key
- self.path = tuple()
- self.root = self
- def __setattr__(self,attname,attval):
- if attname in ('parent','key','value'):
- if self.__dict__.get(attname):
- raise ValueError("context is read-only")
- self.__dict__[attname] = attval
- def __hash__(self): return hash(self.path)
- def __cmp__(self,other):
- try:
- return cmp(self.path,other.path)
- except AttributeError:
- return -1
- def __str__(self):
- if self.path:
- return "/".join(('',)+self.path)
- else:
- return '/'
-
-def to_context(target):
- if type(target) is InstanceType:
- if target.__class__ is context:
- return target
- return context(None,None,target)
-
-def context_test():
- lst = ['value']
- map = {'key':lst}
- x = context(None,None,map)
- y = context(x,'key',lst)
- z = context(y,0,'value')
- assert ('key',) == y.path
- assert 'key' == y.key
- assert lst == y.value
- assert x == y.parent
- assert x == y.root
- assert 0 == z.key
- assert 'value' == z.value
- assert y == z.parent
- assert x == z.root
- assert hash(x)
- assert hash(y)
- assert hash(z)
- assert '/' == str(x)
- assert '/key' == str(y)
- assert '/key/0' == str(z)
-
-class null_seg:
- """
- summary: >
- This is the simplest path segment, it
- doesn't return any results and doesn't
- depend upon its context. It also happens to
- be the base class which all segments derive.
- """
- def __iter__(self):
- return self
- def next_null(self):
- raise StopIteration
- def bind(self,cntx):
- """
- summary: >
- The bind function is called whenever
- the parent context has changed.
- """
- assert(cntx.__class__ is context)
- self.cntx = cntx
- def apply(self,target):
- self.bind(to_context(target))
- return iter(self)
- def exists(self,cntx):
- try:
- self.bind(cntx)
- self.next()
- return 1
- except StopIteration:
- return 0
- next = next_null
-
-class self_seg(null_seg):
- """
- summary: >
- This path segment returns the context
- node exactly once.
- """
- def __str__(self): return '.'
- def next_self(self):
- self.next = self.next_null
- return self.cntx
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.next = self.next_self
-
-class root_seg(self_seg):
- def __str__(self): return '/'
- def bind(self,cntx):
- self_seg.bind(self,cntx.root)
-
-class parent_seg(self_seg):
- def __str__(self): return '..'
- def bind(self,cntx):
- if cntx.parent: cntx = cntx.parent
- self_seg.bind(self,cntx)
-
-class wild_seg(null_seg):
- """
- summary: >
- The wild segment simply loops through
- all of the sub-contexts for a given object.
- If there aren't any children, this isn't an
- error it just doesn't return anything.
- """
- def __str__(self): return '*'
- def next_wild(self):
- key = self.keys.next()
- return context(self.cntx,key,self.values[key])
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- typ = type(cntx.value)
- if typ is ListType:
- self.keys = iter(xrange(0,len(cntx.value)))
- self.values = cntx.value
- self.next = self.next_wild
- return
- if typ is DictType:
- self.keys = iter(cntx.value)
- self.values = cntx.value
- self.next = self.next_wild
- return
- self.next = self.next_null
-
-class trav_seg(null_seg):
- """
- summary: >
- This is a recursive traversal of the range, preorder.
- It is a recursive combination of self and wild.
- """
- def __str__(self): return '/'
- def next(self):
- while 1:
- (cntx,seg) = self.stk[-1]
- if not seg:
- seg = wild_seg()
- seg.bind(cntx)
- self.stk[-1] = (cntx,seg)
- return cntx
- try:
- cntx = seg.next()
- self.stk.append((cntx,None))
- except StopIteration:
- self.stk.pop()
- if not(self.stk):
- self.next = self.next_null
- raise StopIteration
-
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.stk = [(cntx,None)]
-
-class match_seg(self_seg):
- """
- summary: >
- Matches a particular key within the
- current context. Kinda boring.
- """
- def __str__(self): return str(self.key)
- def __init__(self,key):
- #TODO: Do better implicit typing
- try:
- key = int(key)
- except: pass
- self.key = key
- def bind(self,cntx):
- try:
- mtch = cntx.value[self.key]
- cntx = context(cntx,self.key,mtch)
- self_seg.bind(self,cntx)
- except:
- null_seg.bind(self,cntx)
-
-class conn_seg(null_seg):
- """
- summary: >
- When two segments are connected via a slash,
- this is a composite. For each context of the
- parent, it binds the child, and returns each
- context of the child.
- """
- def __str__(self):
- if self.parent.__class__ == root_seg:
- return "/%s" % self.child
- return "%s/%s" % (self.parent, self.child)
- def __init__(self,parent,child):
- self.parent = parent
- self.child = child
- def next(self):
- while 1:
- try:
- return self.child.next()
- except StopIteration:
- cntx = self.parent.next()
- self.child.bind(cntx)
-
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.parent.bind(cntx)
- try:
- cntx = self.parent.next()
- except StopIteration:
- return
- self.child.bind(cntx)
-
-
-class pred_seg(null_seg):
- def __str__(self): return "%s[%s]" % (self.parent, self.filter)
- def __init__(self,parent,filter):
- self.parent = parent
- self.filter = filter
- def next(self):
- while 1:
- ret = self.parent.next()
- if self.filter.exists(ret):
- return ret
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.parent.bind(cntx)
-
-class or_seg(null_seg):
- def __str__(self): return "%s|%s" % (self.lhs,self.rhs)
- def __init__(self,lhs,rhs):
- self.rhs = rhs
- self.lhs = lhs
- self.unq = {}
- def next(self):
- seg = self.lhs
- try:
- nxt = seg.next()
- self.unq[nxt] = nxt
- return nxt
- except StopIteration: pass
- seg = self.rhs
- while 1:
- nxt = seg.next()
- if self.unq.get(nxt,None):
- continue
- return nxt
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.lhs.bind(cntx)
- self.rhs.bind(cntx)
-
-class scalar:
- def __init__(self,val):
- self.val = val
- def __str__(self):
- return str(self.val)
- def value(self):
- return self.val
-
-class equal_pred:
- def exists_true(self,cntx): return 1
- def exists_false(self,cntx): return 0
- def exists_scalar(self,cntx):
- self.rhs.bind(cntx)
- try:
- while 1:
- cntx = self.rhs.next()
- if str(cntx.value) == self.lhs: #TODO: Remove type hack
- return 1
- except StopIteration: pass
- return 0
- def exists_segment(self,cntx):
- raise NotImplementedError()
- def __init__(self,lhs,rhs):
- if lhs.__class__ == scalar:
- if rhs.__class__ == scalar:
- if rhs.value() == lhs.value():
- self.exists = self.exists_true
- else:
- self.exists = self.exists_false
- else:
- self.exists = self.exists_scalar
- else:
- if rhs.__class__ == scalar:
- (lhs,rhs) = (rhs,lhs)
- self.exists = self.exists_scalar
- else:
- self.exists = self.exists_segment
- self.lhs = str(lhs.value()) #TODO: Remove type hack
- self.rhs = rhs
-
-matchSegment = re.compile(r"""^(\w+|/|\.|\*|\"|\')""")
-
-def parse_segment(expr):
- """
- Segments occur between the slashes...
- """
- mtch = matchSegment.search(expr)
- if not(mtch): return (None,expr)
- tok = mtch.group(); siz = len(tok)
- if '/' == tok: return (trav_seg(),expr)
- elif '.' == tok:
- if len(expr) > 1 and '.' == expr[1]:
- seg = parent_seg()
- siz = 2
- else:
- seg = self_seg()
- elif '*' == tok: seg = wild_seg()
- elif '"' == tok or "'" == tok:
- (cur,siz) = unquote(expr)
- seg = match_seg(cur)
- else:
- seg = match_seg(tok)
- return (seg,expr[siz:])
-
-matchTerm = re.compile(r"""^(\w+|/|\.|\(|\"|\')""")
-
-def parse_term(expr):
- mtch = matchTerm.search(expr)
- if not(mtch): return (None,expr)
- tok = mtch.group(); siz = len(tok)
- if '/' == tok or '.' == tok:
- return parse(expr)
- if '(' == tok:
- (term,expr) = parse_predicate(expr)
- assert ')' == expr[0]
- return (term,expr[1:])
- elif '"' == tok or "'" == tok:
- (val,siz) = unquote(expr)
- else:
- val = tok; siz = len(tok)
- return (scalar(val),expr[siz:])
-
-def parse_predicate(expr):
- (term,expr) = parse_term(expr)
- if not term: raise SyntaxError("term expected: '%s'" % expr)
- tok = expr[0]
- if '=' == tok:
- (rhs,expr) = parse_term(expr[1:])
- return (equal_pred(term,rhs),expr)
- if '(' == tok:
- raise "No functions allowed... yet!"
- if ']' == tok or ')' == tok:
- if term.__class__ is scalar:
- term = match_seg(str(term))
- return (term,expr)
- raise SyntaxError("ypath: expecting operator '%s'" % expr)
-
-def parse_start(expr):
- """
- Initial checking on the expression, and
- determine if it is relative or absolute.
- """
- if type(expr) != StringType or len(expr) < 1:
- raise TypeError("string required: " + repr(expr))
- if '/' == expr[0]:
- ypth = root_seg()
- else:
- ypth = self_seg()
- expr = '/' + expr
- return (ypth,expr)
-
-def parse(expr):
- """
- This the parser entry point, the top level node
- is always a root or self segment. The self isn't
- strictly necessary, but it keeps things simple.
- """
- (ypth,expr) = parse_start(expr)
- while expr:
- tok = expr[0]
- if '/' == tok:
- (child, expr) = parse_segment(expr[1:])
- if child: ypth = conn_seg(ypth,child)
- continue
- if '[' == tok:
- (filter, expr) = parse_predicate(expr[1:])
- assert ']' == expr[0]
- expr = expr[1:]
- ypth = pred_seg(ypth,filter)
- continue
- if '|' == tok:
- (rhs, expr) = parse(expr[1:])
- ypth = or_seg(ypth,rhs)
- continue
- if '(' == tok:
- (child,expr) = parse(expr[1:])
- assert ')' == expr[0]
- expr = expr[1:]
- ypth = conn_seg(ypth,child)
- continue
- break
- return (ypth,expr)
-
-class convert_to_value(null_seg):
- def __init__(self,itr):
- self.itr = itr
- def next(self):
- return self.itr.next().value
- def bind(self,cntx):
- self.itr.bind(cntx)
-
-def ypath(expr,target=noTarget,cntx=0):
- (ret,expr) = parse(expr)
- if expr: raise SyntaxError("ypath parse error `%s`" % expr)
- if not cntx: ret = convert_to_value(ret)
- if target is noTarget: return ret
- return ret.apply(target)