summaryrefslogtreecommitdiffstats
path: root/cobbler
diff options
context:
space:
mode:
authorMichael DeHaan <mdehaan@redhat.com>2008-05-28 11:07:39 -0400
committerMichael DeHaan <mdehaan@redhat.com>2008-05-28 11:07:39 -0400
commit36fd29894dc5859fa295aac08d2726cf77667088 (patch)
tree75ec187193a029af50a898878c9fe9f56735075b /cobbler
parent60e7b2048cb810082d9f5da189d827d0dec3747c (diff)
parent2963016f5c143a6f3c3bc711a2b6d1ed07d65abe (diff)
downloadthird_party-cobbler-36fd29894dc5859fa295aac08d2726cf77667088.tar.gz
third_party-cobbler-36fd29894dc5859fa295aac08d2726cf77667088.tar.xz
third_party-cobbler-36fd29894dc5859fa295aac08d2726cf77667088.zip
Merge branch 'devel'
Conflicts: cobbler.spec cobbler/item_system.py cobbler/webui/master.py
Diffstat (limited to 'cobbler')
-rw-r--r--cobbler/action_buildiso.py181
-rw-r--r--cobbler/action_check.py126
-rw-r--r--cobbler/action_import.py37
-rw-r--r--cobbler/action_litesync.py77
-rw-r--r--cobbler/action_replicate.py160
-rw-r--r--cobbler/action_reposync.py55
-rw-r--r--cobbler/action_status.py291
-rw-r--r--cobbler/action_sync.py977
-rw-r--r--cobbler/action_validate.py48
-rw-r--r--cobbler/api.py84
-rw-r--r--cobbler/cexceptions.py3
-rwxr-xr-xcobbler/cobbler.py39
-rw-r--r--cobbler/cobblerd.py41
-rw-r--r--cobbler/collection.py61
-rw-r--r--cobbler/collection_distros.py2
-rw-r--r--cobbler/collection_profiles.py2
-rw-r--r--cobbler/collection_repos.py2
-rw-r--r--cobbler/collection_systems.py2
-rw-r--r--cobbler/commands.py60
-rw-r--r--cobbler/config.py2
-rw-r--r--cobbler/demo_connect.py21
-rw-r--r--cobbler/item.py26
-rw-r--r--cobbler/item_distro.py19
-rw-r--r--cobbler/item_profile.py178
-rw-r--r--cobbler/item_repo.py39
-rw-r--r--cobbler/item_system.py120
-rw-r--r--cobbler/kickgen.py279
-rw-r--r--cobbler/manage_ctrl.py76
-rw-r--r--cobbler/module_loader.py2
-rw-r--r--cobbler/modules/authn_configfile.py2
-rw-r--r--cobbler/modules/authn_denyall.py43
-rw-r--r--cobbler/modules/authn_kerberos.py81
-rw-r--r--cobbler/modules/authn_ldap.py118
-rw-r--r--cobbler/modules/authn_passthru.py49
-rw-r--r--cobbler/modules/authn_testing.py42
-rw-r--r--cobbler/modules/authz_allowall.py2
-rw-r--r--cobbler/modules/authz_configfile.py64
-rw-r--r--cobbler/modules/authz_ownership.py178
-rw-r--r--cobbler/modules/cli_distro.py40
-rw-r--r--cobbler/modules/cli_misc.py61
-rw-r--r--cobbler/modules/cli_profile.py53
-rw-r--r--cobbler/modules/cli_repo.py25
-rw-r--r--cobbler/modules/cli_system.py49
-rw-r--r--cobbler/modules/manage_bind.py283
-rw-r--r--cobbler/modules/manage_dnsmasq.py194
-rw-r--r--cobbler/modules/manage_isc.py252
-rw-r--r--cobbler/modules/serializer_shelve.py2
-rw-r--r--cobbler/modules/serializer_yaml.py7
-rw-r--r--cobbler/pxegen.py324
-rw-r--r--cobbler/remote.py326
-rw-r--r--cobbler/serializable.py2
-rw-r--r--cobbler/serializer.py6
-rw-r--r--cobbler/services.py96
-rw-r--r--cobbler/settings.py43
-rw-r--r--cobbler/templar.py189
-rw-r--r--cobbler/utils.py416
-rw-r--r--cobbler/webui/CobblerWeb.py154
-rw-r--r--cobbler/webui/master.py262
-rw-r--r--cobbler/yaml/__init__.py6
-rw-r--r--cobbler/yaml/dump.py7
-rw-r--r--cobbler/yaml/implicit.py6
-rw-r--r--cobbler/yaml/inline.py6
-rw-r--r--cobbler/yaml/klass.py6
-rw-r--r--cobbler/yaml/load.py6
-rw-r--r--cobbler/yaml/ordered_dict.py7
-rw-r--r--cobbler/yaml/redump.py6
-rw-r--r--cobbler/yaml/stream.py6
-rw-r--r--cobbler/yaml/timestamp.py7
-rw-r--r--cobbler/yaml/ypath.py7
-rw-r--r--cobbler/yumgen.py110
70 files changed, 4443 insertions, 2110 deletions
diff --git a/cobbler/action_buildiso.py b/cobbler/action_buildiso.py
new file mode 100644
index 0000000..639bd6b
--- /dev/null
+++ b/cobbler/action_buildiso.py
@@ -0,0 +1,181 @@
+"""
+Builds non-live bootable CD's that have PXE-equivalent behavior
+for all cobbler profiles currently in memory.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import os
+import os.path
+import shutil
+import sub_process
+import sys
+import traceback
+import shutil
+import sub_process
+
+import utils
+from cexceptions import *
+from utils import _
+
+# FIXME: lots of overlap with pxegen.py, should consolidate
+# FIXME: disable timeouts and remove local boot for this?
+HEADER = """
+
+DEFAULT menu
+PROMPT 0
+MENU TITLE Cobbler | http://cobbler.et.redhat.com
+TIMEOUT 200
+TOTALTIMEOUT 6000
+ONTIMEOUT local
+
+LABEL local
+ MENU LABEL (local)
+ MENU DEFAULT
+ LOCALBOOT -1
+
+"""
+
+class BuildIso:
+ """
+ Handles conversion of internal state to the tftpboot tree layout
+ """
+
+ def __init__(self,config,verbose=False):
+ """
+ Constructor
+ """
+ self.verbose = verbose
+ self.config = config
+ self.api = config.api
+ self.distros = config.distros()
+ self.profiles = config.profiles()
+ self.distmap = {}
+ self.distctr = 0
+
+ def make_shorter(self,distname):
+ if self.distmap.has_key(distname):
+ return self.distmap[distname]
+ else:
+ self.distctr = self.distctr + 1
+ self.distmap[distname] = str(self.distctr)
+ return str(self.distctr)
+
+ def run(self,iso=None,tempdir=None,profiles=None):
+
+ # verify we can find isolinux.bin
+
+ if iso is None:
+ iso = "kickstart.iso"
+
+ isolinuxbin = "/usr/lib/syslinux/isolinux.bin"
+ if not os.path.exists(isolinuxbin):
+ raise CX(_("Required file not found: %s") % isolinuxbin)
+
+ # if iso is none, create it in . as "cobbler.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)
+
+ # 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")
+ utils.copyfile(isolinuxbin, os.path.join(isolinuxdir, "isolinux.bin"))
+ menu = "/var/lib/cobbler/menu.c32"
+ files = [ isolinuxbin, menu ]
+ for f in files:
+ if not os.path.exists(f):
+ raise CX(_("Required file not found: %s") % f)
+ utils.copyfile(f, os.path.join(isolinuxdir, os.path.basename(f)))
+
+ print _("- copying kernels and initrds")
+ # copy all images in included profiles to images dir
+ for x in self.api.profiles():
+ use_this = True
+ if profiles is not None:
+ which_profiles = profiles.split(",")
+ if not use_this in which_profiles:
+ use_this = False
+ dist = x.get_conceptual_parent()
+ if dist.name.find("-xen") != -1:
+ continue
+ 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))
+
+ # generate isolinux.cfg
+ print _("- generating a isolinux.cfg")
+ isolinuxcfg = os.path.join(isolinuxdir, "isolinux.cfg")
+ cfg = open(isolinuxcfg, "w+")
+ cfg.write(HEADER) # fixme, use template
+
+ for x in self.api.profiles():
+ # FIXME
+ use_this = True
+ if profiles is not None:
+ which_profiles = profiles.split(",")
+ if not use_this in which_profiles:
+ use_this = False
+ if use_this:
+ dist = x.get_conceptual_parent()
+ if dist.name.find("-xen") != -1:
+ continue
+ data = utils.blender(self.api, True, x)
+ distname = self.make_shorter(dist.name)
+
+ cfg.write("\n")
+ cfg.write("LABEL %s\n" % x.name)
+ cfg.write(" MENU LABEL %s\n" % x.name)
+ cfg.write(" kernel %s.krn\n" % distname)
+
+ if data["kickstart"].startswith("/"):
+ data["kickstart"] = "http://%s/cblr/svc/op/ks/profile/%s" % (
+ data["server"],
+ x.name
+ )
+
+ append_line = " append initrd=%s.img" % distname
+ append_line = append_line + " ks=%s " % data["kickstart"]
+ append_line = append_line + " %s\n" % data["kernel_options"]
+ cfg.write(append_line)
+
+ 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
+
+ print _("- running: %s") % cmd
+ rc = sub_process.call(cmd, shell=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
+
+
diff --git a/cobbler/action_check.py b/cobbler/action_check.py
index f7bc9d9..74af0c6 100644
--- a/cobbler/action_check.py
+++ b/cobbler/action_check.py
@@ -17,8 +17,8 @@ import os
import re
import sub_process
import action_sync
-from rhpl.translate import _, N_, textdomain, utf8
-
+import utils
+from utils import _
class BootCheck:
def __init__(self,config):
@@ -36,8 +36,9 @@ class BootCheck:
"""
status = []
self.check_name(status)
+ self.check_selinux(status)
if self.settings.manage_dhcp:
- mode = self.settings.manage_dhcp_mode.lower()
+ mode = self.config.api.get_sync().dhcp.what()
if mode == "isc":
self.check_dhcpd_bin(status)
self.check_dhcpd_conf(status)
@@ -45,8 +46,16 @@ class BootCheck:
elif mode == "dnsmasq":
self.check_dnsmasq_bin(status)
self.check_service(status,"dnsmasq")
- else:
- status.append(_("manage_dhcp_mode in /var/lib/cobbler/settings should be 'isc' or 'dnsmasq'"))
+
+ if self.settings.manage_dns:
+ mode = self.config.api.get_sync().dns.what()
+ if mode == "bind":
+ self.check_bind_bin(status)
+ self.check_service(status,"named")
+ elif mode == "dnsmasq" and not self.settings.manage_dhcp:
+ self.check_dnsmasq_bin(status)
+ self.check_service(status,"dnsmasq")
+
self.check_service(status, "cobblerd")
self.check_bootloaders(status)
@@ -56,14 +65,25 @@ class BootCheck:
self.check_httpd(status)
self.check_iptables(status)
self.check_yum(status)
+ self.check_for_default_password(status)
+ self.check_for_unreferenced_repos(status)
+ self.check_for_unsynced_repos(status)
return status
def check_service(self, status, which):
- 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)
- if rc != 0:
- status.append(_("service %s is not running") % which)
+ if utils.check_dist() == "redhat":
+ 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)
+ if rc != 0:
+ status.append(_("service %s is not running") % which)
+ elif utils.check_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)
+ if rc != 0:
+ status.append(_("service %s is not running") % which)
+ else:
+ status.append(_("Unknown distribution type, cannot check for running service %s" % which))
def check_iptables(self, status):
if os.path.exists("/etc/rc.d/init.d/iptables"):
@@ -86,18 +106,67 @@ class BootCheck:
parameters.
"""
if self.settings.server == "127.0.0.1":
- status.append(_("The 'server' field in /var/lib/cobbler/settings must be set to something other than localhost, or kickstarting features will not work. This should be a resolvable hostname or IP for the boot server as reachable by all machines that will use it."))
+ status.append(_("The 'server' field in /etc/cobbler/settings must be set to something other than localhost, or kickstarting features will not work. This should be a resolvable hostname or IP for the boot server as reachable by all machines that will use it."))
if self.settings.next_server == "127.0.0.1":
- status.append(_("For PXE to be functional, the 'next_server' field in /var/lib/cobbler/settings must be set to something other than 127.0.0.1, and should match the IP of the boot server on the PXE network."))
+ status.append(_("For PXE to be functional, the 'next_server' field in /etc/cobbler/settings must be set to something other than 127.0.0.1, and should match the IP of the boot server on the PXE network."))
+
+ def check_selinux(self,status):
+ prc = sub_process.Popen("/usr/sbin/getenforce",shell=True,stdout=sub_process.PIPE)
+ data = prc.communicate()[0]
+ if data.lower().find("disabled") == -1:
+ # permissive or enforcing or something else
+ prc2 = sub_process.Popen("/usr/sbin/getsebool -a",shell=True,stdout=sub_process.PIPE)
+ data2 = prc2.communicate()[0]
+ for line in data2.split("\n"):
+ 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"))
+
+
+ def check_for_default_password(self,status):
+ templates = utils.get_kickstart_templates(self.config.api)
+ files = []
+ for t in templates:
+ fd = open(t)
+ data = fd.read()
+ fd.close()
+ if data.find("\$1\$mF86/UHC\$WvcIcX2t6crBz2onWxyac.") != -1:
+ files.append(t)
+ if len(files) > 0:
+ status.append(_("One or more kickstart templates references default password 'cobbler' and should be changed for security reasons: %s") % ", ".join(files))
+
+
+ def check_for_unreferenced_repos(self,status):
+ repos = []
+ referenced = []
+ not_found = []
+ for r in self.config.api.repos():
+ repos.append(r.name)
+ for p in self.config.api.profiles():
+ my_repos = p.repos
+ referenced.extend(my_repos)
+ for r in referenced:
+ if r not in repos:
+ not_found.append(r)
+ if len(not_found) > 0:
+ status.append(_("One or more repos referenced by profile objects is no longer defined in cobbler: %s") % ", ".join(not_found))
+
+ def check_for_unsynced_repos(self,status):
+ need_sync = []
+ for r in self.config.repos():
+ if r.mirror_locally == 1:
+ lookfor = os.path.join(self.settings.webdir, "repo_mirror", r.name)
+ if not os.path.exists(lookfor):
+ need_sync.append(r.name)
+ if len(need_sync) > 0:
+ status.append(_("One or more repos need to be processed by cobbler reposync for the first time before kickstarting against them: %s") % ", ".join(need_sync))
+
def check_httpd(self,status):
"""
Check if Apache is installed.
"""
- if not os.path.exists(self.settings.httpd_bin):
- status.append(_("Apache doesn't appear to be installed"))
- else:
- self.check_service(status,"httpd")
+ self.check_service(status,"httpd")
def check_dhcpd_bin(self,status):
@@ -105,14 +174,22 @@ class BootCheck:
Check if dhcpd is installed
"""
if not os.path.exists(self.settings.dhcpd_bin):
- status.append(_("dhcpd isn't installed, but is enabled in /var/lib/cobbler/settings"))
+ status.append(_("dhcpd isn't installed, but management is enabled in /etc/cobbler/settings"))
def check_dnsmasq_bin(self,status):
"""
Check if dnsmasq is installed
"""
if not os.path.exists(self.settings.dnsmasq_bin):
- status.append(_("dnsmasq isn't installed, but is enabled in /var/lib/cobbler/settings"))
+ status.append(_("dnsmasq isn't installed, but management is enabled in /etc/cobbler/settings"))
+
+ def check_bind_bin(self,status):
+ """
+ Check if bind is installed.
+ """
+ if not os.path.exists(self.settings.bind_bin):
+ status.append(_("bind isn't installed, but management is enabled in /etc/cobbler/settings"))
+
def check_bootloaders(self,status):
"""
@@ -140,8 +217,9 @@ class BootCheck:
"""
Check if cobbler.conf's tftpboot directory exists
"""
- if not os.path.exists(self.settings.tftpboot):
- status.append(_("please create directory: %(dirname)s") % { "dirname" : self.settings.tftpboot })
+ bootloc = utils.tftpboot_location()
+ if not os.path.exists(bootloc):
+ status.append(_("please create directory: %(dirname)s") % { "dirname" : bootloc })
def check_tftpd_conf(self,status):
@@ -152,17 +230,15 @@ class BootCheck:
if os.path.exists(self.settings.tftpd_conf):
f = open(self.settings.tftpd_conf)
re_disable = re.compile(r'disable.*=.*yes')
- found_bootdir = False
for line in f.readlines():
if re_disable.search(line):
status.append(_("change 'disable' to 'no' in %(file)s") % { "file" : self.settings.tftpd_conf })
- if line.find("-s %s" % self.settings.tftpboot) != -1:
- found_bootdir = True
- if not found_bootdir:
- status.append(_("change 'server_args' to '-s %(args)s' in %(file)s") % { "file" : "/etc/xinetd.d/tftp", "args" : self.settings.tftpboot })
-
else:
status.append(_("file %(file)s does not exist") % { "file" : self.settings.tftpd_conf })
+
+ bootloc = utils.tftpboot_location()
+ if not os.path.exists(bootloc):
+ status.append(_("directory needs to be created: %s" % bootloc))
def check_dhcpd_conf(self,status):
diff --git a/cobbler/action_import.py b/cobbler/action_import.py
index db09818..3e726a6 100644
--- a/cobbler/action_import.py
+++ b/cobbler/action_import.py
@@ -23,7 +23,7 @@ import glob
import api
import utils
import shutil
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
WGET_CMD = "wget --mirror --no-parent --no-host-directories --directory-prefix %s/%s %s"
RSYNC_CMD = "rsync -a %s '%s' %s/ks_mirror/%s --exclude-from=/etc/cobbler/rsync.exclude --progress"
@@ -36,7 +36,7 @@ TRY_LIST = [
class Importer:
- def __init__(self,api,config,mirror,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None):
+ def __init__(self,api,config,mirror,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None,arch=None):
"""
Performs an import of a install tree (or trees) from the given
mirror address. The prefix of the distro is to be specified
@@ -59,6 +59,7 @@ class Importer:
self.distros_added = []
self.kickstart_file = kickstart_file
self.rsync_flags = rsync_flags
+ self.arch = arch
# ----------------------------------------------------------------------
@@ -67,6 +68,26 @@ class Importer:
raise CX(_("import failed. no --mirror specified"))
if self.mirror_name is None:
raise CX(_("import failed. no --name specified"))
+ if self.arch is not None:
+ self.arch = self.arch.lower()
+ if self.arch not in [ "x86", "ia64", "x86_64" ]:
+ raise CX(_("arch must be x86, x86_64, or ia64"))
+
+ mpath = os.path.join(self.settings.webdir, "ks_mirror", self.mirror_name)
+
+ if os.path.exists(mpath) and self.arch is None:
+ raise CX(_("Something already exists at this import location (%s). You must specify --arch to avoid potentially overwriting existing files.") % mpath)
+
+ if self.arch:
+ # append the arch path to the name if the arch is not already
+ # found in the name.
+ found = False
+ for x in [ "ia64", "i386", "x86_64", "x86" ]:
+ if self.mirror_name.lower().find(x) != -1:
+ found = True
+ break
+ if not found:
+ self.mirror_name = self.mirror_name + "-" + self.arch
if self.mirror_name is None:
raise CX(_("import failed. no --name specified"))
@@ -171,13 +192,6 @@ class Importer:
print _("- skipping distro %s since it wasn't imported this time") % profile.distro
continue
- # THIS IS OBSOLETE:
- #
- #if not distro.kernel.startswith("%s/ks_mirror/" % self.settings.webdir):
- # # this isn't a mirrored profile, so we won't touch it
- # print _("- skipping %s since profile isn't mirrored") % profile.name
- # continue
-
if (self.kickstart_file == None):
kdir = os.path.dirname(distro.kernel)
base_dir = "/".join(kdir.split("/")[0:-2])
@@ -266,6 +280,8 @@ class Importer:
def set_kickstart(self, profile, flavor, major, minor):
if flavor == "fedora":
+ if major >= 8:
+ return profile.set_kickstart("/etc/cobbler/sample_end.ks")
if major >= 6:
return profile.set_kickstart("/etc/cobbler/sample.ks")
if flavor == "redhat" or flavor == "centos":
@@ -442,8 +458,7 @@ class Importer:
config_file.write("baseurl=http://@@http_server@@/cobbler/ks_mirror/%s\n" % (urlseg))
config_file.write("enabled=1\n")
config_file.write("gpgcheck=0\n")
- # NOTE: yum priority defaults to 99 if that plugin is enabled
- # so don't need to add priority=99 here
+ config_file.write("priority=1\n")
config_file.close()
# don't run creatrepo twice -- this can happen easily for Xen and PXE, when
diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py
index 457f3af..ad669be 100644
--- a/cobbler/action_litesync.py
+++ b/cobbler/action_litesync.py
@@ -30,7 +30,7 @@ from cexceptions import *
import traceback
import errno
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class BootLiteSync:
@@ -49,45 +49,36 @@ class BootLiteSync:
self.systems = config.systems()
self.settings = config.settings()
self.repos = config.repos()
- self.sync = action_sync.BootSync(self.config)
+ self.sync = config.api.get_sync()
def add_single_distro(self, name):
# get the distro record
distro = self.distros.find(name=name)
if distro is None:
raise CX(_("error in distro lookup: %s") % name)
- # generate YAML file in distros/$name in webdir
- self.sync.write_distro_file(distro)
# copy image files to images/$name in webdir & tftpboot:
- self.sync.copy_single_distro_files(distro)
+ self.sync.pxegen.copy_single_distro_files(distro)
# cascade sync
kids = distro.get_children()
for k in kids:
self.add_single_profile(k.name)
def remove_single_distro(self, name):
- # delete distro YAML file in distros/$name in webdir
- self.sync.rmfile(os.path.join(self.settings.webdir, "distros", name))
+ bootloc = utils.tftpboot_location()
# delete contents of images/$name directory in webdir
- self.sync.rmtree(os.path.join(self.settings.webdir, "images", name))
+ utils.rmtree(os.path.join(self.settings.webdir, "images", name))
# delete contents of images/$name in tftpboot
- self.sync.rmtree(os.path.join(self.settings.tftpboot, "images", name))
+ utils.rmtree(os.path.join(bootloc, "images", name))
# delete potential symlink to tree in webdir/links
- self.sync.rmfile(os.path.join(self.settings.webdir, "links", name))
+ utils.rmfile(os.path.join(self.settings.webdir, "links", name))
def add_single_profile(self, name):
# get the profile object:
profile = self.profiles.find(name=name)
if profile is None:
raise CX(_("error in profile lookup"))
- # rebuild profile_list YAML file in webdir
- self.sync.write_listings()
- # add profiles/$name YAML file in webdir
- self.sync.write_profile_file(profile)
- # generate kickstart for kickstarts/$name/ks.cfg in webdir
- self.sync.validate_kickstart_for_specific_profile(profile)
# rebuild the yum configuration files for any attached repos
- self.sync.retemplate_yum_repos(profile,True)
+ self.sync.yumgen.retemplate_yum_repos(profile,True)
# cascade sync
kids = profile.get_children()
for k in kids:
@@ -97,18 +88,16 @@ class BootLiteSync:
self.add_single_system(k.name)
def remove_single_profile(self, name):
- # rebuild profile_list YAML file in webdir
- self.sync.write_listings()
# delete profiles/$name file in webdir
- self.sync.rmfile(os.path.join(self.settings.webdir, "profiles", name))
+ utils.rmfile(os.path.join(self.settings.webdir, "profiles", name))
# delete contents on kickstarts/$name directory in webdir
- self.sync.rmtree(os.path.join(self.settings.webdir, "kickstarts", name))
+ utils.rmtree(os.path.join(self.settings.webdir, "kickstarts", name))
def update_system_netboot_status(self,name):
system = self.systems.find(name=name)
if system is None:
raise CX(_("error in system lookup for %s") % name)
- self.sync.write_all_system_files(system,True)
+ self.sync.pxegen.write_all_system_files(system)
def add_single_system(self, name):
# get the system object:
@@ -116,28 +105,42 @@ class BootLiteSync:
if system is None:
raise CX(_("error in system lookup for %s") % name)
# rebuild system_list file in webdir
- self.sync.regen_ethers() # /etc/ethers, for dnsmasq & rarpd
- self.sync.regen_hosts() # /var/lib/cobbler/cobbler_hosts, pretty much for dnsmasq
- self.sync.write_listings()
- # write the PXE and YAML files for the system
- self.sync.write_all_system_files(system)
+ if self.settings.manage_dhcp:
+ self.sync.dhcp.regen_ethers()
+ if self.settings.manage_dns:
+ self.sync.dns.regen_hosts()
+ # write the PXE files for the system
+ self.sync.pxegen.write_all_system_files(system)
# per system kickstarts
- self.sync.validate_kickstart_for_specific_system(system)
- # rebuild the yum configuration files for any attached repos
- self.sync.retemplate_yum_repos(system,False)
+ self.sync.yumgen.retemplate_yum_repos(system,False)
+ if self.settings.manage_dhcp:
+ if self.settings.omapi_enabled:
+ for (name,interface) in system.interfaces.iteritems():
+ self.sync.dhcp.write_dhcp_lease(
+ self.settings.omapi_port,
+ interface["hostname"],
+ interface["mac_address"],
+ interface["ip_address"]
+ )
+
def remove_single_system(self, name):
+ bootloc = utils.tftpboot_location()
system_record = self.systems.find(name=name)
- # rebuild system_list file in webdir
- self.sync.write_listings()
- # delete system YAML file in systems/$name in webdir
- self.sync.rmfile(os.path.join(self.settings.webdir, "systems", name))
# delete contents of kickstarts_sys/$name in webdir
system_record = self.systems.find(name=name)
# delete any kickstart files related to this system
for (name,interface) in system_record.interfaces.iteritems():
filename = utils.get_config_filename(system_record,interface=name)
- self.sync.rmtree(os.path.join(self.settings.webdir, "kickstarts_sys", filename))
+ utils.rmtree(os.path.join(self.settings.webdir, "kickstarts_sys", filename))
+
+ if self.settings.manage_dhcp:
+ if self.settings.omapi_enabled:
+ for (name,interface) in system_record.interfaces.iteritems():
+ self.sync.dhcp.remove_dhcp_lease(
+ self.settings.omapi_port,
+ interface["hostname"]
+ )
# unneeded
#if not system_record.is_pxe_supported():
@@ -152,7 +155,7 @@ class BootLiteSync:
if distro is not None and distro in [ "ia64", "IA64"]:
itanic = True
if not itanic:
- self.sync.rmfile(os.path.join(self.settings.tftpboot, "pxelinux.cfg", filename))
+ utils.rmfile(os.path.join(bootloc, "pxelinux.cfg", filename))
else:
- self.sync.rmfile(os.path.join(self.settings.tftpboot, filename))
+ utils.rmfile(os.path.join(bootloc, filename))
diff --git a/cobbler/action_replicate.py b/cobbler/action_replicate.py
new file mode 100644
index 0000000..ae3adc8
--- /dev/null
+++ b/cobbler/action_replicate.py
@@ -0,0 +1,160 @@
+"""
+Replicate from a cobbler master.
+
+Copyright 2007-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+Scott Henson <shenson@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import os
+import os.path
+import xmlrpclib
+import api as cobbler_api
+from utils import _
+
+class Replicate:
+ def __init__(self,config):
+ """
+ Constructor
+ """
+ self.config = config
+ self.settings = config.settings()
+ self.api = config.api
+ self.remote = None
+ self.uri = None
+
+ # -------------------------------------------------------
+
+ def link_distro(self, distro):
+ """
+ Create the distro links
+ """
+ # find the tree location
+ dirname = os.path.dirname(distro.kernel)
+ tokens = dirname.split("/")
+ tokens = tokens[:-2]
+ base = "/".join(tokens)
+ dest_link = os.path.join(self.settings.webdir, "links", distro.name)
+
+ # create the links directory only if we are mirroring because with
+ # SELinux Apache can't symlink to NFS (without some doing)
+
+ if not os.path.exists(dest_link):
+ try:
+ os.symlink(base, dest_link)
+ except:
+ # this shouldn't happen but I've seen it ... debug ...
+ print _("- symlink creation failed: %(base)s, %(dest)s") % { "base" : base, "dest" : dest_link }
+
+ # -------------------------------------------------------
+
+ def add_distro(self, distro):
+ """
+ Add a distro that has been found
+ """
+ #Register the distro
+ if os.path.exists(distro['kernel']):
+ new_distro = self.api.new_distro()
+ new_distro.from_datastruct(distro)
+ #create the symlinks
+ self.link_distro(new_distro)
+ #Add the distro permanently
+ self.api.distros().add(new_distro, save=True)
+ print 'Added distro %s. Creating Links.' % distro['name']
+ else:
+ print 'Distro %s not here yet.' % distro['name']
+
+ # -------------------------------------------------------
+
+ def add_profile(self, profile):
+ """
+ Add a profile that has been found
+ """
+ #Register the new profile
+ new_profile = self.api.new_profile()
+ new_profile.from_datastruct(profile)
+ self.api.profiles().add(new_profile, save=True)
+ print 'Added profile %s.' % profile['name']
+
+
+ # -------------------------------------------------------
+
+ def check_profile(self, profile):
+ """
+ Check that a profile belongs to a distro
+ """
+ profiles = self.api.profiles().to_datastruct()
+ if profile not in profiles:
+ for distro in self.api.distros().to_datastruct():
+ if distro['name'] == profile['name']:
+ return True
+ return False
+
+
+ # -------------------------------------------------------
+
+ def sync_distros(self):
+ """
+ Sync distros from master
+ """
+ local_distros = self.api.distros()
+ remote_distros = self.remote.get_distros()
+
+ needsync = False
+ for distro in remote_distros:
+ if distro not in local_distros.to_datastruct():
+ print 'Found distro %s.' % distro['name']
+ self.add_distro(distro)
+ needsync = True
+
+ self.call_sync(needsync)
+
+
+ # -------------------------------------------------------
+
+ def sync_profiles(self):
+ """
+ Sync profiles from master
+ """
+ local_profiles = self.api.profiles()
+ remote_profiles = self.remote.get_profiles()
+
+ needsync = False
+ for profile in remote_profiles:
+ if self.check_profile(profile):
+ print 'Found profile %s.' % profilew['name']
+ self.add_profile(profile)
+ needsync = True
+ self.call_sync(needsync)
+
+
+ # -------------------------------------------------------
+
+ def call_sync(self, needsync):
+ if needsync:
+ self.api.sync()
+
+ # -------------------------------------------------------
+
+ def run(self, cobbler_master=None):
+ """
+ Get remote profiles and distros and sync them locally
+ """
+ if cobbler_master is not None:
+ self.uri = 'http://%s/cobbler_api' % cobbler_master
+ elif len(self.settings.cobbler_master) > 0:
+ self.uri = 'http://%s/cobbler_api' % self.settings.cobbler_master
+ else:
+ print _('No cobbler master found to replicate from, try --master.')
+ if self.uri is not None:
+ self.remote = xmlrpclib.Server(self.uri)
+ self.sync_distros()
+ self.sync_profiles()
+
diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py
index 32d38bd..57c2d08 100644
--- a/cobbler/action_reposync.py
+++ b/cobbler/action_reposync.py
@@ -25,7 +25,7 @@ from cexceptions import *
import traceback
import errno
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class RepoSync:
"""
@@ -57,8 +57,10 @@ class RepoSync:
self.verbose = verbose
for repo in self.repos:
if name is not None and repo.name != name:
+ # invoked to sync only a specific repo, this is not the one
continue
elif name is None and not repo.keep_updated:
+ # invoked to run against all repos, but this one is off
print _("- %s is set to not be updated") % repo.name
continue
@@ -71,6 +73,8 @@ class RepoSync:
if repo.is_rsync_mirror():
self.do_rsync(repo)
else:
+ # which may actually NOT reposync if the repo is set to not mirror locally
+ # but that's a technicality
self.do_reposync(repo)
self.update_permissions(repo_path)
@@ -105,7 +109,7 @@ class RepoSync:
store_path = os.path.join(self.settings.webdir, "repo_mirror")
dest_path = os.path.join(store_path, repo.name)
temp_path = os.path.join(store_path, ".origin")
- if not os.path.isdir(temp_path):
+ if not os.path.isdir(temp_path) and repo.mirror_locally:
# FIXME: there's a chance this might break the RHN D/L case
os.makedirs(temp_path)
@@ -118,18 +122,21 @@ class RepoSync:
# this is the simple non-RHN case.
# create the config file that yum will use for the copying
- temp_file = self.create_local_file(repo, temp_path, output=False)
+ if repo.mirror_locally:
+ temp_file = self.create_local_file(repo, temp_path, output=False)
- if not has_rpm_list:
+ if not has_rpm_list and repo.mirror_locally:
# if we have not requested only certain RPMs, use reposync
cmd = "/usr/bin/reposync --config=%s --repoid=%s --download_path=%s" % (temp_file, repo.name, store_path)
if repo.arch != "":
+ if repo.arch == "x86":
+ repo.arch = "i386" # FIX potential arch errors
cmd = "%s -a %s" % (cmd, repo.arch)
print _("- %s") % cmd
cmds.append(cmd)
- else:
+ elif repo.mirror_locally:
# create the output directory if it doesn't exist
if not os.path.exists(dest_path):
@@ -149,6 +156,8 @@ class RepoSync:
# this is the somewhat more-complex RHN case.
# NOTE: this requires that you have entitlements for the server and you give the mirror as rhn://$channelname
+ if not repo.mirror_locally:
+ raise CX(_("rhn:// repos do not work with --mirror-locally=1"))
if has_rpm_list:
print _("- warning: --rpm-list is not supported for RHN content")
@@ -161,7 +170,6 @@ class RepoSync:
if repo.arch != "":
cmd = "%s -a %s" % (cmd, repo.arch)
- print _("- %s") % cmd
cmds.append(cmd)
# now regardless of whether we're doing yumdownloader or reposync
@@ -169,9 +177,10 @@ class RepoSync:
# commands here. Any failure at any point stops the operation.
for cmd in cmds:
- rc = sub_process.call(cmd, shell=True)
- if rc !=0:
- raise CX(_("cobbler reposync failed"))
+ if repo.mirror_locally:
+ rc = sub_process.call(cmd, shell=True)
+ if rc !=0:
+ raise CX(_("cobbler reposync failed"))
# some more special case handling for RHN.
# create the config file now, because the directory didn't exist earlier
@@ -181,7 +190,8 @@ class RepoSync:
# now run createrepo to rebuild the index
- os.path.walk(dest_path, self.createrepo_walker, repo)
+ if repo.mirror_locally:
+ os.path.walk(dest_path, self.createrepo_walker, repo)
# create the config file the hosts will use to access the repository.
@@ -196,6 +206,9 @@ class RepoSync:
Handle copying of rsync:// and rsync-over-ssh repos.
"""
+ if not repo.mirror_locally:
+ raise CX(_("rsync:// urls must be mirrored locally, yum cannot access them directly"))
+
if repo.rpm_list != "":
print _("- warning: --rpm-list is not supported for rsync'd repositories")
dest_path = os.path.join(self.settings.webdir, "repo_mirror", repo.name)
@@ -235,22 +248,34 @@ class RepoSync:
config_file = open(fname, "w+")
config_file.write("[%s]\n" % repo.name)
config_file.write("name=%s\n" % repo.name)
+ optenabled = False
+ optgpgcheck = False
if output:
- line = "baseurl=http://${server}/cobbler/repo_mirror/%s\n" % (repo.name)
+ if repo.mirror_locally:
+ line = "baseurl=http://${server}/cobbler/repo_mirror/%s\n" % (repo.name)
+ else:
+ line = "baseurl=%s\n" % (repo.mirror)
+
config_file.write(line)
# user may have options specific to certain yum plugins
# add them to the file
for x in repo.yumopts:
config_file.write("%s=%s\n" % (x, repo.yumopts[x]))
+ if x == "enabled":
+ optenabled = True
+ if x == "gpgcheck":
+ optgpgcheck = True
else:
line = "baseurl=%s\n" % repo.mirror
http_server = "%s:%s" % (self.settings.server, self.settings.http_port)
line = line.replace("@@server@@",http_server)
config_file.write(line)
- config_file.write("enabled=1\n")
+ if not optenabled:
+ config_file.write("enabled=1\n")
config_file.write("priority=%s\n" % repo.priority)
# FIXME: potentially might want a way to turn this on/off on a per-repo basis
- config_file.write("gpgcheck=0\n")
+ if not optgpgcheck:
+ config_file.write("gpgcheck=0\n")
config_file.close()
return fname
@@ -260,7 +285,7 @@ class RepoSync:
"""
Used to run createrepo on a copied mirror.
"""
- if os.path.exists(os.path.join(dirname,"RPMS")) or repo.is_rsync_mirror():
+ if os.path.exists(dirname) or repo.is_rsync_mirror():
utils.remove_yum_olddata(dirname)
try:
cmd = "createrepo %s %s" % (repo.createrepo_flags, dirname)
@@ -289,7 +314,7 @@ class RepoSync:
if os.path.exists(getenforce):
data = sub_process.Popen(getenforce, shell=True, stdout=sub_process.PIPE).communicate()[0]
if data.lower().find("disabled") == -1:
- cmd3 = "chcon --reference /var/www %s" % repo_path
+ cmd3 = "chcon --reference /var/www %s >/dev/null 2>/dev/null" % repo_path
sub_process.call(cmd3, shell=True)
diff --git a/cobbler/action_status.py b/cobbler/action_status.py
index cc6ebd4..79f9083 100644
--- a/cobbler/action_status.py
+++ b/cobbler/action_status.py
@@ -2,7 +2,7 @@
Reports on kickstart activity by examining the logs in
/var/log/cobbler.
-Copyright 2007, Red Hat, Inc
+Copyright 2007-2008, Red Hat, Inc
Michael DeHaan <mdehaan@redhat.com>
This software may be freely redistributed under the terms of the GNU
@@ -19,213 +19,138 @@ import glob
import time
import api as cobbler_api
-from rhpl.translate import _, N_, textdomain, utf8
+#from utils import _
+# ARRAY INDEXES
+MOST_RECENT_START = 0
+MOST_RECENT_STOP = 1
+MOST_RECENT_TARGET = 2
+SEEN_START = 3
+SEEN_STOP = 4
+STATE = 5
class BootStatusReport:
+
def __init__(self,config,mode):
"""
Constructor
"""
self.config = config
self.settings = config.settings()
+ self.ip_data = {}
self.mode = mode
# -------------------------------------------------------
- def scan_apache_logfiles(self):
- results = {}
- files = [ "/var/log/httpd/access_log" ]
- for x in range(1,4):
- consider = "/var/log/httpd/access_log.%s" % x
- if os.path.exists(consider):
- files.append(consider)
+ def scan_logfiles(self):
+
+ #profile foosball ? 127.0.0.1 start 1208294043.58
+ #system neo ? 127.0.0.1 start 1208295122.86
+
+
+ files = glob.glob("/var/log/cobbler/install.log*")
for fname in files:
- fh = open(fname)
- data = fh.readline()
- while (data is not None and data != ""):
- data = fh.readline()
- tokens = data.split(None)
- if len(tokens) < 6:
- continue
- ip = tokens[0]
- stime = tokens[3].replace("[","")
- req = tokens[6]
- if req.find("/cblr") == -1:
- continue
- ttime = time.strptime(stime,"%d/%b/%Y:%H:%M:%S")
- itime = time.mktime(ttime)
- if not results.has_key(ip):
- results[ip] = {}
- results[ip][itime] = req
-
- return results
+ fd = open(fname)
+ data = fd.read()
+ for line in data.split("\n"):
+ tokens = line.split()
+ if len(tokens) == 0:
+ continue
+ (profile_or_system, name, ip, start_or_stop, ts) = tokens
+ self.catalog(profile_or_system,name,ip,start_or_stop,ts)
+ fd.close()
+
+ # ------------------------------------------------------
+
+ def catalog(self,profile_or_system,name,ip,start_or_stop,ts):
+ ip_data = self.ip_data
+
+ if not ip_data.has_key(ip):
+ ip_data[ip] = [ -1, -1, "?", 0, 0, "?" ]
+ elem = ip_data[ip]
+
+ ts = float(ts)
+
+ mrstart = elem[MOST_RECENT_START]
+ mrstop = elem[MOST_RECENT_STOP]
+ mrtarg = elem[MOST_RECENT_TARGET]
+ snstart = elem[SEEN_START]
+ snstop = elem[SEEN_STOP]
+
+
+ if start_or_stop == "start":
+ if mrstart < ts:
+ mrstart = ts
+ mrtarg = "%s:%s" % (profile_or_system, name)
+ elem[SEEN_START] = elem[SEEN_START] + 1
+
+ if start_or_stop == "stop":
+ if mrstop < ts:
+ mrstop = ts
+ mrtarg = "%s:%s" % (profile_or_system, name)
+ elem[SEEN_STOP] = elem[SEEN_STOP] + 1
+
+ elem[MOST_RECENT_START] = mrstart
+ elem[MOST_RECENT_STOP] = mrstop
+ elem[MOST_RECENT_TARGET] = mrtarg
# -------------------------------------------------------
- def scan_syslog_logfiles(self):
-
- # find all of the logged IP addrs
- filelist = glob.glob("/var/log/cobbler/syslog/*")
- filelist.sort()
- results = {}
-
- for fullname in filelist:
- #fname = os.path.basename(fullname)
- logfile = open(fullname, "r")
- # for each line in the file...
- data = logfile.readline()
- while(data is not None and data != ""):
- data = logfile.readline()
-
- try:
- (epoch, strdate, ip, request) = data.split("\t", 3)
- epoch = float(epoch)
- except:
- continue
-
- if not results.has_key(ip):
- results[ip] = {}
- results[ip][epoch] = request
-
- return results
+ def process_results(self):
+ # FIXME: this should update the times here
+
+ tnow = int(time.time())
+ for ip in self.ip_data.keys():
+ elem = self.ip_data[ip]
+
+ start = int(elem[MOST_RECENT_START])
+ stop = int(elem[MOST_RECENT_STOP])
+ if (stop > start):
+ elem[STATE] = "finished"
+ else:
+ delta = tnow - start
+ min = delta / 60
+ sec = delta % 60
+ if min > 100:
+ elem[STATE] = "unknown/stalled"
+ else:
+ elem[STATE] = "installing (%sm %ss)" % (min,sec)
+
+ return self.ip_data
+
+ def get_printable_results(self):
+ format = "%-15s|%-20s|%-17s|%-17s"
+ ip_data = self.ip_data
+ ips = ip_data.keys()
+ ips.sort()
+ line = (
+ "ip",
+ "target",
+ "start",
+ "state",
+ )
+ buf = format % line
+ for ip in ips:
+ elem = ip_data[ip]
+ line = (
+ ip,
+ elem[MOST_RECENT_TARGET],
+ time.ctime(elem[MOST_RECENT_START]),
+ elem[STATE]
+ )
+ buf = buf + "\n" + format % line
+ return buf
# -------------------------------------------------------
def run(self):
"""
Calculate and print a kickstart-status report.
- For kickstart trees not in /var/lib/cobbler (or a symlink off of there)
- tracking will be incomplete. This should be noted in the docs.
"""
- api = cobbler_api.BootAPI()
-
- apache_results = self.scan_apache_logfiles()
- syslog_results = self.scan_syslog_logfiles()
- ips = apache_results.keys()
- ips.sort()
- ips2 = syslog_results.keys()
- ips2.sort()
-
- ips.extend(ips2)
- ip_printed = {}
-
- last_recorded_time = 0
- time_collisions = 0
-
- #header = ("Name", "State", "Started", "Last Request", "Seconds", "Log Entries")
- print "%-20s | %-15s | %-25s | %-25s | %-10s | %-6s" % (
- _("Name"),
- _("State"),
- _("Last Request"),
- _("Started"),
- _("Seconds"),
- _("Log Entries")
- )
-
-
- for ip in ips:
- if ip_printed.has_key(ip):
- continue
- ip_printed[ip] = 1
- entries = {} # hash of access times and messages
- if apache_results.has_key(ip):
- times = apache_results[ip].keys()
- for logtime in times:
- request = apache_results[ip][logtime]
- if request.find("?system_done") != -1:
- entries[logtime] = "DONE"
- elif request.find("?profile_done") != -1:
- entries[logtime] = "DONE"
- else:
- entries[logtime] = "1" # don't really care what the filename was
-
- if syslog_results.has_key(ip):
- times = syslog_results[ip].keys()
- for logtime in times:
- request = syslog_results[ip][logtime]
- if request.find("methodcomplete") != -1:
- entries[logtime] = "DONE"
- elif request.find("Method =") != -1:
- entries[logtime] = "START"
- else:
- entries[logtime] = "1"
-
- obj = api.systems().find(ip_address=ip)
-
- if obj is not None:
- self.generate_report(entries,obj.name)
- else:
- self.generate_report(entries,ip)
-
+ self.scan_logfiles()
+ self.process_results()
+ print self.get_printable_results()
return True
- #-----------------------------------------
-
- def generate_report(self,entries,name):
- """
- Given the information about transferred files and kickstart finish times, attempt
- to produce a report that most describes the state of the system.
- """
- # sort the access times
- rtimes = entries.keys()
- rtimes.sort()
-
- # variables for calculating kickstart state
- last_request_time = 0
- last_start_time = 0
- last_done_time = 0
- fcount = 0
-
- if len(rtimes) == 0:
- print _("%s: ?") % name
- return
-
- # for each request time the machine has made
- for rtime in rtimes:
-
- rtime = rtime
- fname = entries[rtime]
-
- if fname == "START":
- install_state = "installing"
- last_start_time = rtime
- last_request_time = rtime
- fcount = 0
- elif fname == "DONE":
- # kickstart finished
- last_done_time = rtime
- install_state = "done"
- else:
- install_state = "?"
- last_request_time = rtime
- fcount = fcount + 1
-
- # calculate elapsed time for kickstart
- elapsed_time = 0
- if install_state == "done":
- elapsed_time = int(last_done_time - last_start_time)
- else:
- elapsed_time = int(last_request_time - last_start_time)
-
- # FIXME: IP to MAC mapping where cobbler knows about it would be nice.
- display_start = time.asctime(time.localtime(last_start_time))
- display_last = time.asctime(time.localtime(last_request_time))
-
- if display_start.find(" 1969") != -1:
- display_start = "?"
- elapsed_time = "?"
-
- # print the status line for this IP address
- print "%-20s | %-15s | %-25s | %-25s | %-10s | %-6s" % (
- name,
- install_state,
- display_start,
- display_last,
- elapsed_time,
- fcount
- )
-
-
diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py
index 139066e..55cdad5 100644
--- a/cobbler/action_sync.py
+++ b/cobbler/action_sync.py
@@ -1,10 +1,9 @@
"""
-Builds out a TFTP/cobbler boot tree based on the object tree.
+Builds out filesystem trees/data based on the object tree.
This is the code behind 'cobbler sync'.
-Copyright 2006,2007, Red Hat, Inc
+Copyright 2006-2008, Red Hat, Inc
Michael DeHaan <mdehaan@redhat.com>
-Tim Verhoeven <tim.verhoeven.be@gmail.com>
This software may be freely redistributed under the terms of the GNU
general public license.
@@ -22,19 +21,23 @@ import yaml # Howell-Clark version
import sub_process
import sys
import glob
+import traceback
+import errno
import utils
from cexceptions import *
-import traceback
-import errno
+import templar
+import pxegen
+import yumgen
import item_distro
import item_profile
+import item_repo
import item_system
from Cheetah.Template import Template
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class BootSync:
@@ -42,7 +45,7 @@ class BootSync:
Handles conversion of internal state to the tftpboot tree layout
"""
- def __init__(self,config,verbose=False):
+ def __init__(self,config,verbose=False,dhcp=None,dns=None):
"""
Constructor
"""
@@ -54,202 +57,53 @@ class BootSync:
self.systems = config.systems()
self.settings = config.settings()
self.repos = config.repos()
- self.blend_cache = {}
- self.load_snippet_cache()
+ self.templar = templar.Templar(config)
+ self.pxegen = pxegen.PXEGen(config)
+ self.dns = dns
+ self.dhcp = dhcp
+ self.yumgen = yumgen.YumGen(config)
+ self.bootloc = utils.tftpboot_location()
def run(self):
"""
Syncs the current configuration file with the config tree.
Using the Check().run_ functions previously is recommended
"""
- if not os.path.exists(self.settings.tftpboot):
- raise CX(_("cannot find directory: %s") % self.settings.tftpboot)
+ if not os.path.exists(self.bootloc):
+ raise CX(_("cannot find directory: %s") % self.bootloc)
# run pre-triggers...
utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/pre/*")
- # in case the pre-trigger modified any objects...
+ # (paranoid) in case the pre-trigger modified any objects...
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)
+ self.yumgen = yumgen.YumGen(self.config)
# execute the core of the sync operation
self.clean_trees()
- self.copy_bootloaders()
- self.copy_distros()
- self.retemplate_all_yum_repos()
- self.validate_kickstarts()
- self.build_trees()
+ self.pxegen.copy_bootloaders()
+ self.pxegen.copy_distros()
+ for x in self.systems:
+ self.pxegen.write_all_system_files(x)
+ self.yumgen.retemplate_all_yum_repos()
if self.settings.manage_dhcp:
- # these functions DRT for ISC or dnsmasq
- self.write_dhcp_file()
- self.regen_ethers()
- self.regen_hosts()
- self.make_pxe_menu()
+ self.dhcp.write_dhcp_file()
+ self.dhcp.regen_ethers()
+ if self.settings.manage_dns:
+ self.dns.regen_hosts()
+ self.dns.write_dns_files()
+ self.pxegen.make_pxe_menu()
# run post-triggers
utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/post/*")
return True
- def copy_bootloaders(self):
- """
- Copy bootloaders to the configured tftpboot directory
- NOTE: we support different arch's if defined in
- /var/lib/cobbler/settings.
- """
- for loader in self.settings.bootloaders.keys():
- path = self.settings.bootloaders[loader]
- newname = os.path.basename(path)
- destpath = os.path.join(self.settings.tftpboot, newname)
- self.copyfile(path, destpath)
- self.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.settings.tftpboot, "menu.c32"))
-
- def write_dhcp_file(self):
- """
- DHCP files are written when manage_dhcp is set in
- /var/lib/cobbler/settings.
- """
-
- settings_file = self.settings.dhcpd_conf
- template_file = "/etc/cobbler/dhcp.template"
- mode = self.settings.manage_dhcp_mode.lower()
- if mode == "dnsmasq":
- settings_file = self.settings.dnsmasq_conf
- template_file = "/etc/cobbler/dnsmasq.template"
-
- try:
- f2 = open(template_file,"r")
- except:
- raise CX(_("error writing template to file: %s") % template_file)
- template_data = ""
- template_data = f2.read()
- f2.close()
-
- # build each per-system definition
- # as configured, this only works for ISC, patches accepted
- # from those that care about Itanium. elilo seems to be unmaintained
- # so additional maintaince in other areas may be required to keep
- # this working.
-
- elilo = os.path.basename(self.settings.bootloaders["ia64"])
-
- system_definitions = {}
- counter = 0
-
- # we used to just loop through each system, but now we must loop
- # through each network interface of each system.
-
- for system in self.systems:
- profile = system.get_conceptual_parent()
- distro = profile.get_conceptual_parent()
- for (name, interface) in system.interfaces.iteritems():
-
- mac = interface["mac_address"]
- ip = interface["ip_address"]
- host = interface["hostname"]
-
- if mac is None or mac == "":
- # can't write a DHCP entry for this system
- continue
-
- counter = counter + 1
- systxt = ""
-
- if mode == "isc":
-
- # the label the entry after the hostname if possible
- if host is not None and host != "":
- systxt = "\nhost %s {\n" % host
- else:
- systxt = "\nhost generic%d {\n" % counter
-
- if distro.arch == "ia64":
- # can't use pxelinux.0 anymore
- systxt = systxt + " filename \"/%s\";\n" % elilo
- systxt = systxt + " hardware ethernet %s;\n" % mac
- if ip is not None and ip != "":
- systxt = systxt + " fixed-address %s;\n" % ip
- systxt = systxt + "}\n"
-
- else:
- # dnsmasq. don't have to write IP and other info here, but we do tag
- # each MAC based on the arch of it's distro, if it needs something other
- # than pxelinux.0 -- for these arches, and these arches only, a dnsmasq
- # reload (full "cobbler sync") would be required after adding the system
- # to cobbler, just to tag this relationship.
-
- if ip is not None and ip != "":
- if distro.arch.lower() == "ia64":
- systxt = "dhcp-host=net:ia64," + ip + "\n"
- # support for other arches needs modifications here
- else:
- systxt = ""
-
- dhcp_tag = interface["dhcp_tag"]
- if dhcp_tag == "":
- dhcp_tag = "default"
-
- if not system_definitions.has_key(dhcp_tag):
- system_definitions[dhcp_tag] = ""
- system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt
-
- # we are now done with the looping through each interface of each system
-
- metadata = {
- "insert_cobbler_system_definitions" : system_definitions.get("default",""),
- "date" : time.asctime(time.gmtime()),
- "cobbler_server" : self.settings.server,
- "next_server" : self.settings.next_server,
- "elilo" : elilo
- }
-
- # now add in other DHCP expansions that are not tagged with "default"
- for x in system_definitions.keys():
- if x == "default":
- continue
- metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x]
-
- self.apply_template(template_data, metadata, settings_file)
-
- def regen_ethers(self):
- # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date
- # every time we add a system.
- # read 'man ethers' for format info
- fh = open("/etc/ethers","w+")
- for sys in self.systems:
- for (name, interface) in sys.interfaces.iteritems():
- mac = interface["mac_address"]
- ip = interface["ip_address"]
- if mac is None or mac == "":
- # can't write this w/o a MAC address
- continue
- if ip is not None and ip != "":
- fh.write(mac.upper() + "\t" + ip + "\n")
- fh.close()
-
- def regen_hosts(self):
- # dnsmasq knows how to read this database for host info
- # (other things may also make use of this later)
- fh = open("/var/lib/cobbler/cobbler_hosts","w+")
- for sys in self.systems:
- for (name, interface) in sys.interfaces.iteritems():
- mac = interface["mac_address"]
- host = interface["hostname"]
- ip = interface["ip_address"]
- if mac is None or mac == "":
- continue
- if host is not None and host != "" and ip is not None and ip != "":
- fh.write(ip + "\t" + host + "\n")
- fh.close()
-
-
- #def templatify(self, data, metadata, outfile):
- # for x in metadata.keys():
- # template_data = template_data.replace("$%s" % x, metadata[x])
-
def clean_trees(self):
"""
Delete any previously built pxelinux.cfg tree and virt tree info and then create
@@ -267,770 +121,15 @@ class BootSync:
path = os.path.join(self.settings.webdir,x)
if os.path.isfile(path):
if not x.endswith(".py"):
- self.rmfile(path)
+ utils.rmfile(path)
if os.path.isdir(path):
- if not x in ["web", "webui", "localmirror","repo_mirror","ks_mirror","kickstarts","kickstarts_sys","distros","images","systems","profiles","links","repo_profile","repo_system"] :
+ if not x in ["web", "webui", "localmirror","repo_mirror","ks_mirror","images","links","repo_profile","repo_system","svc"] :
# delete directories that shouldn't exist
- self.rmtree(path)
+ utils.rmtree(path)
if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles","repo_profile","repo_system"]:
# clean out directory contents
- self.rmtree_contents(path)
- self.rmtree_contents(os.path.join(self.settings.tftpboot, "pxelinux.cfg"))
- self.rmtree_contents(os.path.join(self.settings.tftpboot, "images"))
-
- def copy_distros(self):
- """
- A distro is a kernel and an initrd. Copy all of them and error
- out if any files are missing. The conf file was correct if built
- via the CLI or API, though it's possible files have been moved
- since or perhaps they reference NFS directories that are no longer
- mounted.
-
- NOTE: this has to be done for both tftp and http methods
- """
- # copy is a 4-letter word but tftpboot runs chroot, thus it's required.
- for d in self.distros:
- print _("sync distro: %s") % d.name
- self.copy_single_distro_files(d)
-
- def copy_single_distro_files(self, d):
- for dirtree in [self.settings.tftpboot, self.settings.webdir]:
- distros = os.path.join(dirtree, "images")
- distro_dir = os.path.join(distros,d.name)
- self.mkdir(distro_dir)
- kernel = utils.find_kernel(d.kernel) # full path
- initrd = utils.find_initrd(d.initrd) # full path
- if kernel is None or not os.path.isfile(kernel):
- raise CX(_("kernel not found: %(file)s, distro: %(distro)s") % { "file" : d.kernel, "distro" : d.name })
- if initrd is None or not os.path.isfile(initrd):
- raise CX(_("initrd not found: %(file)s, distro: %(distro)s") % { "file" : d.initrd, "distro" : d.name })
- b_kernel = os.path.basename(kernel)
- b_initrd = os.path.basename(initrd)
- if kernel.startswith(dirtree):
- self.linkfile(kernel, os.path.join(distro_dir, b_kernel))
- else:
- self.copyfile(kernel, os.path.join(distro_dir, b_kernel))
- if initrd.startswith(dirtree):
- self.linkfile(initrd, os.path.join(distro_dir, b_initrd))
- else:
- self.copyfile(initrd, os.path.join(distro_dir, b_initrd))
-
- def validate_kickstarts(self):
- """
- Similar to what we do for distros, ensure all the kickstarts
- in conf file are valid. kickstarts are referenced by URL
- (http or ftp), can stay as is. kickstarts referenced by absolute
- path (i.e. are files path) will be mirrored over http.
- """
-
- self.validate_kickstarts_per_profile()
- self.validate_kickstarts_per_system()
- return True
-
- def validate_kickstarts_per_profile(self):
- """
- Koan provisioning (Virt + auto-ks) needs kickstarts
- per profile. Validate them as needed. Local kickstarts
- get template substitution. Since http:// kickstarts might
- get generated via magic URLs, those are *not* substituted.
- NFS kickstarts are also not substituted when referenced
- by NFS URL's as we don't copy those files over to the cobbler
- directories. They are supposed to be live such that an
- admin can update those without needing to run 'sync' again.
-
- NOTE: kickstart only uses the web directory (if it uses them at all)
- """
-
- for g in self.profiles:
- print _("sync profile: %s") % g.name
- self.validate_kickstart_for_specific_profile(g)
-
- def validate_kickstart_for_specific_profile(self,g):
- distro = g.get_conceptual_parent()
- meta = utils.blender(self.api, False, g, self.blend_cache)
- if distro is None:
- raise CX(_("profile %(profile)s references missing distro %(distro)s") % { "profile" : g.name, "distro" : g.distro })
- kickstart_path = utils.find_kickstart(meta["kickstart"])
- if kickstart_path is not None and os.path.exists(kickstart_path):
- # the input is an *actual* file, hence we have to copy it
- copy_path = os.path.join(
- self.settings.webdir,
- "kickstarts", # profile kickstarts go here
- g.name
- )
- self.mkdir(copy_path)
- dest = os.path.join(copy_path, "ks.cfg")
- try:
- meta = utils.blender(self.api, False, g, self.blend_cache)
- ksmeta = meta["ks_meta"]
- del meta["ks_meta"]
- meta.update(ksmeta) # make available at top level
- meta["yum_repo_stanza"] = self.generate_repo_stanza(g,True)
- meta["yum_config_stanza"] = self.generate_config_stanza(g,True)
- meta["kickstart_done"] = self.generate_kickstart_signal(g, None)
- meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"])
- kfile = open(kickstart_path)
- self.apply_template(kfile, meta, dest)
- kfile.close()
- except:
- traceback.print_exc() # leave this in, for now...
- msg = "err_kickstart2"
- raise CX(_("Error while rendering kickstart file %(src)s to %(dest)s") % { "src" : kickstart_path, "dest" : dest })
-
- def generate_kickstart_signal(self, profile, system=None):
- """
- Do things that we do at the end of kickstarts...
- * signal the status watcher we're done
- * disable PXE if needed
- * save the original kickstart file for debug
- """
-
- # FIXME: watcher is more of a request than a packaged file
- # we should eventually package something and let it do something important"
- pattern1 = "wget \"http://%s/cgi-bin/cobbler/nopxe.cgi?system=%s\""
- pattern2 = "wget \"http://%s/cobbler/%s/%s/ks.cfg\" -O /root/cobbler.ks"
- pattern3 = "wget \"http://%s/cgi-bin/cobbler/post_install_trigger.cgi?system=%s\""
-
- blend_this = profile
- if system:
- blend_this = system
-
- blended = utils.blender(self.api, False, blend_this, self.blend_cache)
- kickstart = blended.get("kickstart",None)
-
- buf = ""
- if system is not None:
- if str(self.settings.pxe_just_once).upper() in [ "1", "Y", "YES", "TRUE" ]:
- buf = buf + "\n" + pattern1 % (blended["http_server"], system.name)
- if kickstart and os.path.exists(kickstart):
- buf = buf + "\n" + pattern2 % (blended["http_server"], "kickstarts_sys", system.name)
- if self.settings.run_post_install_trigger:
- buf = buf + "\n" + pattern3 % (blended["http_server"], system.name)
-
- else:
- if kickstart and os.path.exists(kickstart):
- buf = buf + "\n" + pattern2 % (blended["http_server"], "kickstarts", profile.name)
-
- return buf
-
- def get_repo_segname(self, is_profile):
- if is_profile:
- return "repos_profile"
- else:
- return "repos_system"
-
- def generate_repo_stanza(self, obj, is_profile=True):
-
- """
- Automatically attaches yum repos to profiles/systems in kickstart files
- that contain the magic $yum_repo_stanza variable.
- """
-
- buf = ""
- blended = utils.blender(self.api, False, obj, self.blend_cache)
-
- configs = self.get_repo_filenames(obj,is_profile)
- for c in configs:
- name = c.split("/")[-1].replace(".repo","")
- (is_core, baseurl) = self.analyze_repo_config(c)
- buf = buf + "repo --name=%s --baseurl=%s\n" % (name, baseurl)
-
- return buf
-
- def analyze_repo_config(self, filename):
- fd = open(filename)
- data = fd.read()
- lines = data.split("\n")
- ret = False
- baseurl = None
- for line in lines:
- if line.find("ks_mirror") != -1:
- ret = True
- if line.find("baseurl") != -1:
- first, baseurl = line.split("=")
- fd.close()
- return (ret, baseurl)
-
- def get_repo_baseurl(self, server, repo_name, is_repo_mirror=True):
- """
- Construct the URL to a repo definition.
- """
- if is_repo_mirror:
- return "http://%s/cobbler/repo_mirror/%s" % (server, repo_name)
- else:
- return "http://%s/cobbler/ks_mirror/config/%s" % (server, repo_name)
-
- def get_repo_filenames(self, obj, is_profile=True):
- """
- For a given object, return the paths to repo configuration templates
- that will be used to generate per-object repo configuration files and
- baseurls
- """
-
- blended = utils.blender(self.api, False, obj, self.blend_cache)
- urlseg = self.get_repo_segname(is_profile)
-
- topdir = "%s/%s/%s/*.repo" % (self.settings.webdir, urlseg, blended["name"])
- files = glob.glob(topdir)
- return files
-
-
- def generate_config_stanza(self, obj, is_profile=True):
-
- """
- Add in automatic to configure /etc/yum.repos.d on the remote system
- if the kickstart file contains the magic $yum_config_stanza.
- """
-
- if not self.settings.yum_post_install_mirror:
- return ""
-
- urlseg = self.get_repo_segname(is_profile)
-
- distro = obj.get_conceptual_parent()
- if not is_profile:
- distro = distro.get_conceptual_parent()
-
- blended = utils.blender(self.api, False, obj, self.blend_cache)
- configs = self.get_repo_filenames(obj, is_profile)
- buf = ""
-
- # for each kickstart template we have rendered ...
- for c in configs:
-
- name = c.split("/")[-1].replace(".repo","")
- # add the line to create the yum config file on the target box
- conf = self.get_repo_config_file(blended["http_server"],urlseg,blended["name"],name)
- buf = buf + "wget \"%s\" --output-document=/etc/yum.repos.d/%s.repo\n" % (conf, name)
-
- return buf
-
- def get_repo_config_file(self,server,urlseg,obj_name,repo_name):
- """
- Construct the URL to a repo config file that is usable in kickstart
- for use with yum. This is different than the templates cobbler reposync
- creates, as this file will allow the server to migrate and have different
- variables for different subnets/profiles/etc.
- """
- return "http://%s/cblr/%s/%s/%s.repo" % (server,urlseg,obj_name,repo_name)
-
- def validate_kickstarts_per_system(self):
- """
- PXE provisioning needs kickstarts evaluated per system.
- Profiles would normally be sufficient, but not in cases
- such as static IP, where we want to be able to do templating
- on a system basis.
-
- NOTE: kickstart only uses the web directory (if it uses them at all)
- """
-
- for s in self.systems:
- print _("sync system: %s") % s.name
- self.validate_kickstart_for_specific_system(s)
-
- def validate_kickstart_for_specific_system(self,s):
- profile = s.get_conceptual_parent()
- if profile is None:
- raise CX(_("system %(system)s references missing profile %(profile)s") % { "system" : s.name, "profile" : s.profile })
- distro = profile.get_conceptual_parent()
- meta = utils.blender(self.api, False, s, self.blend_cache)
- kickstart_path = utils.find_kickstart(meta["kickstart"])
- if kickstart_path and os.path.exists(kickstart_path):
- copy_path = os.path.join(self.settings.webdir,
- "kickstarts_sys", # system kickstarts go here
- s.name
- )
- self.mkdir(copy_path)
- dest = os.path.join(copy_path, "ks.cfg")
- try:
- ksmeta = meta["ks_meta"]
- del meta["ks_meta"]
- meta.update(ksmeta) # make available at top level
- meta["yum_repo_stanza"] = self.generate_repo_stanza(s, False)
- meta["yum_config_stanza"] = self.generate_config_stanza(s, False)
- meta["kickstart_done"] = self.generate_kickstart_signal(profile, s)
- meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"])
- kfile = open(kickstart_path)
- self.apply_template(kfile, meta, dest)
- kfile.close()
- except:
- traceback.print_exc()
- raise CX(_("Error templating file %(src)s to %(dest)s") % { "src" : meta["kickstart"], "dest" : dest })
-
- def load_snippet_cache(self):
-
- # first load all of the files in /var/lib/cobbler/snippets and load them, for use
- # in adding long bits to kickstart templates without having to have them hard coded
- # inside the sync code.
-
- snippet_cache = {}
- snippets = glob.glob("%s/*" % self.settings.snippetsdir)
- for snip in snippets:
- if os.path.isdir(snip):
- continue
- snip_file = open(snip)
- data = snip_file.read()
- snip_file.close()
- snippet_cache[os.path.basename(snip)] = data
- self.snippet_cache = snippet_cache
-
-
- def apply_template(self, data_input, metadata, out_path):
- """
- Take filesystem file kickstart_input, apply metadata using
- Cheetah and save as out_path.
- """
-
- if type(data_input) != str:
- data = data_input.read()
- else:
- data = data_input
-
- # backward support for Cobbler's legacy (and slightly more readable)
- # template syntax.
- data = data.replace("TEMPLATE::","$")
-
- # replace contents of the data stream with items from the snippet cache
- # do not use Cheetah yet, Cheetah can't really be run twice on the same
- # stream and be expected to do the right thing
- newdata = ""
- for line in data.split("\n"):
- for x in self.snippet_cache:
- if not line.startswith("#"):
- line = line.replace("SNIPPET::%s" % x, self.snippet_cache[x])
- newdata = "\n".join((newdata, line))
- data = newdata
-
- # HACK: the ksmeta field may contain nfs://server:/mount in which
- # case this is likely WRONG for kickstart, which needs the NFS
- # directive instead. Do this to make the templates work.
- newdata = ""
- if metadata.has_key("tree") and metadata["tree"].startswith("nfs://"):
- for line in data.split("\n"):
- if line.find("--url") != -1 and line.find("url ") != -1:
- rest = metadata["tree"][6:] # strip off "nfs://" part
- try:
- (server, dir) = rest.split(":",2)
- except:
- raise CX(_("Invalid syntax for NFS path given during import: %s" % metadata["tree"]))
- line = "nfs --server %s --dir %s" % (server,dir)
- # but put the URL part back in so koan can still see
- # what the original value was
- line = line + "\n" + "#url --url=%s" % metadata["tree"]
- newdata = newdata + line + "\n"
- data = newdata
-
- # tell Cheetah not to blow up if it can't find a symbol for something
- data = "#errorCatcher Echo\n" + data
-
- # now do full templating scan, where we will also templatify the snippet insertions
- t = Template(source=data, searchList=[metadata])
- try:
- data_out = str(t)
- except:
- print _("There appears to be an formatting error in the template file.")
- print _("For completeness, the traceback from Cheetah has been included below.")
- raise
-
- # now apply some magic post-filtering that is used by cobbler import and some
- # other places, but doesn't use Cheetah. Forcing folks to double escape
- # things would be very unwelcome.
-
- for x in metadata:
- if type(metadata[x]) == str:
- data_out = data_out.replace("@@%s@@" % x, metadata[x])
-
- # remove leading newlines which apparently breaks AutoYAST ?
- if data_out.startswith("\n"):
- data_out = data_out.strip()
-
- if out_path is not None:
- self.mkdir(os.path.dirname(out_path))
- fd = open(out_path, "w+")
- fd.write(data_out)
- fd.close()
-
- return data_out
-
- def build_trees(self):
- """
- Now that kernels and initrds are copied and kickstarts are all valid,
- build the pxelinux.cfg tree, which contains a directory for each
- configured IP or MAC address. Also build a tree for Virt info.
-
- NOTE: some info needs to go in TFTP and HTTP directories, but not all.
- Usually it's just one or the other.
-
- """
-
- self.write_listings()
-
- # create pxelinux.cfg under tftpboot
- # and file for each MAC or IP (hex encoded 01-XX-XX-XX-XX-XX-XX)
-
- for d in self.distros:
- self.write_distro_file(d)
-
- for p in self.profiles:
- self.write_profile_file(p)
-
- for system in self.systems:
- self.write_all_system_files(system)
-
- def retemplate_all_yum_repos(self):
- for p in self.profiles:
- self.retemplate_yum_repos(p,True)
- for system in self.systems:
- self.retemplate_yum_repos(system,False)
-
- def retemplate_yum_repos(self,obj,is_profile):
- # FIXME: blender could use caching for performance
- # FIXME: make stanza generation code load stuff from the right place
- """
- Yum repository management files are in self.settings.webdir/repo_mirror/$name/config.repo
- and also potentially in listed in the source_repos structure of the distro object, however
- these files have server URLs in them that must be templated out. This function does this.
- """
- blended = utils.blender(self.api, False, obj, self.blend_cache)
-
- if is_profile:
- outseg = "repos_profile"
- else:
- outseg = "repos_system"
-
- input_files = []
-
- # chance old versions from upgrade do not have a source_repos
- # workaround for user bug
- if not blended.has_key("source_repos"):
- blended["source_repos"] = []
-
- # tack on all the install source repos IF there is more than one.
- # this is basically to support things like RHEL5 split trees
- # if there is only one, then there is no need to do this.
-
- for r in blended["source_repos"]:
- filename = self.settings.webdir + "/" + "/".join(r[0].split("/")[4:])
- input_files.append(filename)
-
- for repo in blended["repos"]:
- input_files.append(os.path.join(self.settings.webdir, "repo_mirror", repo, "config.repo"))
-
- for infile in input_files:
- if infile.find("ks_mirror") == -1:
- dispname = infile.split("/")[-2]
- else:
- dispname = infile.split("/")[-1].replace(".repo","")
- confdir = os.path.join(self.settings.webdir, outseg)
- outdir = os.path.join(confdir, blended["name"])
- self.mkdir(outdir)
- try:
- infile_h = open(infile)
- except:
- print _("WARNING: cobbler reposync needs to be run on repo (%s), then re-run cobbler sync") % dispname
- continue
- infile_data = infile_h.read()
- infile_h.close()
- outfile = os.path.join(outdir, "%s.repo" % (dispname))
- self.apply_template(infile_data, blended, outfile)
-
-
- def write_all_system_files(self,system,just_edit_pxe=False):
-
- profile = system.get_conceptual_parent()
- if profile is None:
- raise CX(_("system %(system)s references a missing profile %(profile)s") % { "system" : system.name, "profile" : system.profile})
- distro = profile.get_conceptual_parent()
- if distro is None:
- raise CX(_("profile %(profile)s references a missing distro %(distro)s") % { "profile" : system.profile, "distro" : profile.distro})
-
- # this used to just generate a single PXE config file, but now must
- # generate one record for each described NIC ...
-
- counter = 0
- for (name,interface) in system.interfaces.iteritems():
-
- ip = interface["ip_address"]
-
- f1 = utils.get_config_filename(system,interface=name)
-
- # for tftp only ...
- if distro.arch in [ "x86", "x86_64", "standard"]:
- # pxelinux wants a file named $name under pxelinux.cfg
- f2 = os.path.join(self.settings.tftpboot, "pxelinux.cfg", f1)
- if distro.arch == "ia64":
- # elilo expects files to be named "$name.conf" in the root
- # and can not do files based on the MAC address
- if ip is not None and ip != "":
- print _("Warning: Itanium system object (%s) needs an IP address to PXE") % system.name
-
- filename = "%s.conf" % utils.get_config_filename(system,interface=name)
- f2 = os.path.join(self.settings.tftpboot, filename)
-
- f3 = os.path.join(self.settings.webdir, "systems", f1)
-
- if system.netboot_enabled and system.is_pxe_supported():
- if distro.arch in [ "x86", "x86_64", "standard"]:
- self.write_pxe_file(f2,system,profile,distro,False)
- if distro.arch == "ia64":
- self.write_pxe_file(f2,system,profile,distro,True)
- else:
- # ensure the file doesn't exist
- self.rmfile(f2)
-
- if not just_edit_pxe:
- # allows netboot-disable to be highly performant
- # by not invoking the Cheetah engine
- self.write_system_file(f3,system)
-
- counter = counter + 1
-
-
- def make_pxe_menu(self):
- # only do this if there is NOT a system named default.
- default = self.systems.find(name="default")
- if default is not None:
- return
-
- fname = os.path.join(self.settings.tftpboot, "pxelinux.cfg", "default")
-
- # read the default template file
- template_src = open("/etc/cobbler/pxedefault.template")
- template_data = template_src.read()
-
- # 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)
-
- # build out the menu entries
- pxe_menu_items = ""
- for profile in profile_list:
- distro = profile.get_conceptual_parent()
- contents = self.write_pxe_file(None,None,profile,distro,False,include_header=False)
- if contents is not None:
- pxe_menu_items = pxe_menu_items + contents + "\n"
-
- # save the template.
- metadata = { "pxe_menu_items" : pxe_menu_items }
- outfile = os.path.join(self.settings.tftpboot, "pxelinux.cfg", "default")
- self.apply_template(template_data, metadata, outfile)
- template_src.close()
-
-
- def write_pxe_file(self,filename,system,profile,distro,is_ia64, include_header=True):
- """
- Write a configuration file for the boot loader(s).
- More system-specific configuration may come in later, if so
- that would appear inside the system object in api.py
-
- NOTE: relevant to tftp only
- """
-
- # ---
- # system might have netboot_enabled set to False (see item_system.py), if so,
- # don't do anything else and flag the error condition.
- if system is not None and not system.netboot_enabled:
- return None
-
- # ---
- # just some random variables
- template = None
- metadata = {}
- buffer = ""
-
- # ---
- # find kernel and initrd
- kernel_path = os.path.join("/images",distro.name,os.path.basename(distro.kernel))
- initrd_path = os.path.join("/images",distro.name,os.path.basename(distro.initrd))
-
- # Find the kickstart if we inherit from another profile
- kickstart_path = utils.blender(self.api, True, profile, self.blend_cache)["kickstart"]
-
- # ---
- # choose a template
- if system is None:
- template = "/etc/cobbler/pxeprofile.template"
- elif not is_ia64:
- template = "/etc/cobbler/pxesystem.template"
- else:
- template = "/etc/cobbler/pxesystem_ia64.template"
-
- # now build the kernel command line
- if system is not None:
- blended = utils.blender(self.api, True,system,self.blend_cache)
- else:
- blended = utils.blender(self.api, True,profile,self.blend_cache)
- kopts = blended["kernel_options"]
-
- # ---
- # generate the append line
- append_line = "append %s" % utils.hash_to_string(kopts)
- if not is_ia64:
- append_line = "%s initrd=%s" % (append_line, initrd_path)
- 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 != "":
-
- if system is not None and kickstart_path.startswith("/"):
- kickstart_path = "http://%s/cblr/kickstarts_sys/%s/ks.cfg" % (blended["http_server"], system.name)
- elif kickstart_path.startswith("/") or kickstart_path.find("/cobbler/kickstarts/") != -1:
- kickstart_path = "http://%s/cblr/kickstarts/%s/ks.cfg" % (blended["http_server"], profile.name)
-
- if distro.breed is None or distro.breed == "redhat":
- append_line = "%s ks=%s" % (append_line, kickstart_path)
- elif distro.breed == "suse":
- append_line = "%s autoyast=%s" % (append_line, kickstart_path)
- elif distro.breed == "debian":
- append_line = "%s auto=true url=%s" % (append_line, kickstart_path)
- append_line = append_line.replace("ksdevice","interface")
-
- # ---
- # store variables for templating
- metadata["menu_label"] = ""
- if not is_ia64 and system is None:
- metadata["menu_label"] = "MENU LABEL %s" % profile.name
- metadata["profile_name"] = profile.name
- metadata["kernel_path"] = kernel_path
- metadata["initrd_path"] = initrd_path
- metadata["append_line"] = append_line
-
- # ---
- # get the template
- template_fh = open(template)
- template_data = template_fh.read()
- template_fh.close()
-
- # ---
- # save file and/or return results, depending on how called.
- buffer = self.apply_template(template_data, metadata, None)
- if filename is not None:
- fd = open(filename, "w")
- fd.write(buffer)
- fd.close()
- return buffer
-
-
- def write_listings(self):
- """
- Creates a very simple index of available systems and profiles
- that cobbler knows about. Just the names, no details.
- """
- names1 = [x.name for x in self.profiles]
- names2 = [x.name for x in self.systems]
- data1 = yaml.dump(names1)
- data2 = yaml.dump(names2)
- fd1 = open(os.path.join(self.settings.webdir, "profile_list"), "w+")
- fd2 = open(os.path.join(self.settings.webdir, "system_list"), "w+")
- fd1.write(data1)
- fd2.write(data2)
- fd1.close()
- fd2.close()
-
- def write_distro_file(self,distro):
- """
- Create distro information for koan install
- """
- blended = utils.blender(self.api, True, distro, self.blend_cache)
- filename = os.path.join(self.settings.webdir,"distros",distro.name)
- fd = open(filename, "w+")
- fd.write(yaml.dump(blended))
- fd.close()
-
- def write_profile_file(self,profile):
- """
- Create profile information for virt install
-
- NOTE: relevant to http only
- """
-
- blended = utils.blender(self.api, True, profile, self.blend_cache)
- filename = os.path.join(self.settings.webdir,"profiles",profile.name)
- fd = open(filename, "w+")
- if blended.has_key("kickstart") and blended["kickstart"].startswith("/"):
- # write the file location as needed by koan
- blended["kickstart"] = "http://%s/cblr/kickstarts/%s/ks.cfg" % (blended["http_server"], profile.name)
- fd.write(yaml.dump(blended))
- fd.close()
-
- def write_system_file(self,filename,system):
- """
- Create system information for virt install
-
- NOTE: relevant to http only
- """
-
- blended = utils.blender(self.api, True, system, self.blend_cache)
- filename = os.path.join(self.settings.webdir,"systems",system.name)
- fd = open(filename, "w+")
- fd.write(yaml.dump(blended))
- fd.close()
-
- def linkfile(self, src, dst):
- """
- Attempt to create a link dst that points to src. Because file
- systems suck we attempt several different methods or bail to
- self.copyfile()
- """
-
- try:
- return os.link(src, dst)
- except (IOError, OSError):
- pass
-
- try:
- return os.symlink(src, dst)
- except (IOError, OSError):
- pass
-
- return self.copyfile(src, dst)
-
- def copyfile(self,src,dst):
- try:
- return shutil.copyfile(src,dst)
- except:
- if not os.path.samefile(src,dst):
- # accomodate for the possibility that we already copied
- # the file as a symlink/hardlink
- raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst})
-
- def rmfile(self,path):
- try:
- os.unlink(path)
- return True
- except OSError, ioe:
- if not ioe.errno == errno.ENOENT: # doesn't exist
- traceback.print_exc()
- raise CX(_("Error deleting %s") % path)
- return True
-
- def rmtree_contents(self,path):
- what_to_delete = glob.glob("%s/*" % path)
- for x in what_to_delete:
- self.rmtree(x)
-
- def rmtree(self,path):
- try:
- if os.path.isfile(path):
- return self.rmfile(path)
- else:
- return shutil.rmtree(path,ignore_errors=True)
- except OSError, ioe:
- traceback.print_exc()
- if not ioe.errno == errno.ENOENT: # doesn't exist
- raise CX(_("Error deleting %s") % path)
- return True
+ utils.rmtree_contents(path)
+ utils.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg"))
+ utils.rmtree_contents(os.path.join(self.bootloc, "images"))
- def mkdir(self,path,mode=0777):
- try:
- return os.makedirs(path,mode)
- except OSError, oe:
- if not oe.errno == 17: # already exists (no constant for 17?)
- traceback.print_exc()
- print oe.errno
- raise CX(_("Error creating") % path)
diff --git a/cobbler/action_validate.py b/cobbler/action_validate.py
index f898478..44f3a9a 100644
--- a/cobbler/action_validate.py
+++ b/cobbler/action_validate.py
@@ -15,7 +15,8 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import os
import re
import sub_process
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
+import utils
class Validate:
@@ -39,16 +40,10 @@ class Validate:
failed = False
for x in self.config.profiles():
- distro = x.get_conceptual_parent()
- if distro.breed != "redhat":
- continue
- if not self.checkfile(x.name, "%s/kickstarts/%s/ks.cfg" % (self.settings.webdir, x.name)):
+ if not self.checkfile(x, True):
failed = True
for x in self.config.systems():
- distro = x.get_conceptual_parent().get_conceptual_parent()
- if distro.breed != "redhat":
- continue
- if not self.checkfile(x.name, "%s/kickstarts_sys/%s/ks.cfg" % (self.settings.webdir, x.name)):
+ if not self.checkfile(x, False):
failed = True
if failed:
@@ -58,15 +53,32 @@ class Validate:
return failed
- def checkfile(self,name,file):
- # print _("scanning rendered kickstart template: %s" % file)
- if not os.path.exists(file):
- print _("kickstart file does not exist for: %s") % name
- return False
- rc = os.system("/usr/bin/ksvalidator %s" % file)
- if not rc == 0:
- print _("ksvalidator detected a possible problem for: %s") % name
- print _(" rendered kickstart template at: %s" % file)
+ def checkfile(self,obj,is_profile):
+ blended = utils.blender(self.config.api, False, obj)
+ ks = blended["kickstart"]
+ breed = blended["breed"]
+ if breed != "redhat":
+ print "%s has a breed of %s, skipping" % (obj.name, breed)
+ return True
+ if ks is None or ks == "":
+ print "%s has no kickstart, skipping" % obj.name
+ return True
+
+ server = blended["server"]
+ if not ks.startswith("/"):
+ url = self.kickstart
+ elif is_profile:
+ url = "http://%s/cblr/svc/?op=ks;profile=%s" % (server,obj.name)
+ else:
+ url = "http://%s/cblr/svc/?op=ks;system=%s" % (server,obj.name)
+
+ print "----------------------------"
+ print "checking url: %s" % url
+
+
+ rc = os.system("/usr/bin/ksvalidator \"%s\"" % url)
+ if rc != 0:
return False
+
return True
diff --git a/cobbler/api.py b/cobbler/api.py
index 13fc5f3..9f4a636 100644
--- a/cobbler/api.py
+++ b/cobbler/api.py
@@ -22,14 +22,17 @@ import action_import
import action_reposync
import action_status
import action_validate
+import action_buildiso
+import action_replicate
from cexceptions import *
import sub_process
import module_loader
+import kickgen
import logging
import os
import fcntl
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
ERROR = 100
INFO = 10
@@ -38,7 +41,7 @@ DEBUG = 5
# notes on locking:
# BootAPI is a singleton object
# the XMLRPC variants allow 1 simultaneous request
-# therefore we flock on /var/lib/cobbler/settings for now
+# therefore we flock on /etc/cobbler/settings for now
# on a request by request basis.
class BootAPI:
@@ -79,20 +82,11 @@ class BootAPI:
"module",
"authz_allowall"
)
+ self.kickgen = kickgen.KickGen(self._config)
self.logger.debug("API handle initialized")
def __setup_logger(self,name):
- logger = logging.getLogger(name)
- logger.setLevel(logging.INFO)
- try:
- ch = logging.FileHandler("/var/log/cobbler/cobbler.log")
- except:
- raise CX(_("No write permissions on log file. Are you root?"))
- ch.setLevel(logging.INFO)
- formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s")
- ch.setFormatter(formatter)
- logger.addHandler(ch)
- return logger
+ return utils.setup_logger(name)
def log(self,msg,args=None,debug=False):
if debug:
@@ -221,21 +215,21 @@ class BootAPI:
self.log("new_repo",[is_subobject])
return self._config.new_repo(is_subobject=is_subobject)
- def add_distro(self, ref):
+ def add_distro(self, ref, check_for_duplicate_names=False):
self.log("add_distro",[ref.name])
- return self._config.distros().add(ref,save=True)
+ return self._config.distros().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names)
- def add_profile(self, ref):
+ def add_profile(self, ref, check_for_duplicate_names=False):
self.log("add_profile",[ref.name])
- return self._config.profiles().add(ref,save=True)
+ return self._config.profiles().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names)
- def add_system(self,ref):
+ def add_system(self, ref, check_for_duplicate_names=False, check_for_duplicate_netinfo=False):
self.log("add_system",[ref.name])
- return self._config.systems().add(ref,save=True)
+ return self._config.systems().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names,check_for_duplicate_netinfo=check_for_duplicate_netinfo)
- def add_repo(self,ref):
+ def add_repo(self, ref, check_for_duplicate_names=False):
self.log("add_repo",[ref.name])
- return self._config.repos().add(ref,save=True)
+ return self._config.repos().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names)
def find_distro(self, name=None, return_list=False, **kargs):
return self._config.distros().find(name=name, return_list=return_list, **kargs)
@@ -249,6 +243,9 @@ class BootAPI:
def find_repo(self, name=None, return_list=False, **kargs):
return self._config.repos().find(name=name, return_list=return_list, **kargs)
+ def dump_vars(self, obj, format=False):
+ return obj.dump_vars(format)
+
def auto_add_repos(self):
"""
Import any repos this server knows about and mirror them.
@@ -284,7 +281,14 @@ class BootAPI:
# run cobbler reposync to apply changes
return True
-
+
+ def generate_kickstart(self,profile,system):
+ self.log("generate_kickstart")
+ if system:
+ return self.kickgen.generate_kickstart_for_system(system)
+ else:
+ return self.kickgen.generate_kickstart_for_profile(profile)
+
def check(self):
"""
See if all preqs for network booting are valid. This returns
@@ -319,9 +323,22 @@ class BootAPI:
saved with serialize() will NOT be synchronized with this command.
"""
self.log("sync")
- sync = action_sync.BootSync(self._config)
+ sync = self.get_sync()
return sync.run()
+ def get_sync(self):
+ self.dhcp = self.get_module_from_file(
+ "dhcp",
+ "module",
+ "manage_isc"
+ ).get_manager(self._config)
+ self.dns = self.get_module_from_file(
+ "dns",
+ "module",
+ "manage_bind"
+ ).get_manager(self._config)
+ return action_sync.BootSync(self._config,dhcp=self.dhcp,dns=self.dns)
+
def reposync(self, name=None):
"""
Take the contents of /var/lib/cobbler/repos and update them --
@@ -332,11 +349,11 @@ class BootAPI:
return reposync.run(name)
def status(self,mode):
- self.log("status",[mode])
- statusifier = action_status.BootStatusReport(self._config, mode)
+ self.log("status")
+ statusifier = action_status.BootStatusReport(self._config,mode)
return statusifier.run()
- def import_tree(self,mirror_url,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None):
+ def import_tree(self,mirror_url,mirror_name,network_root=None,kickstart_file=None,rsync_flags=None,arch=None):
"""
Automatically import a directory tree full of distribution files.
mirror_url can be a string that represents a path, a user@host
@@ -346,7 +363,7 @@ class BootAPI:
"""
self.log("import_tree",[mirror_url, mirror_name, network_root, kickstart_file, rsync_flags])
importer = action_import.Importer(
- self, self._config, mirror_url, mirror_name, network_root, kickstart_file, rsync_flags
+ self, self._config, mirror_url, mirror_name, network_root, kickstart_file, rsync_flags, arch
)
return importer.run()
@@ -405,3 +422,16 @@ class BootAPI:
self.log("authorize",[user,resource,arg1,arg2,rc],debug=True)
return rc
+ def build_iso(self,iso=None,profiles=None,tempdir=None):
+ builder = action_buildiso.BuildIso(self._config)
+ return builder.run(
+ iso=iso, profiles=profiles, tempdir=tempdir
+ )
+
+ def replicate(self, cobbler_master = None):
+ replicator = action_replicate.Replicate(self._config)
+ return replicator.run(cobbler_master = cobbler_master)
+
+ def get_kickstart_templates(self):
+ return utils.get_kickstar_templates(self)
+
diff --git a/cobbler/cexceptions.py b/cobbler/cexceptions.py
index a78f7f2..dba2857 100644
--- a/cobbler/cexceptions.py
+++ b/cobbler/cexceptions.py
@@ -20,6 +20,9 @@ class CobblerException(exceptions.Exception):
def __init__(self, value, *args):
self.value = value % args
+ # this is a hack to work around some odd exception handling
+ # in older pythons
+ self.from_cobbler = 1
def __str__(self):
return repr(self.value)
diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py
index c02302e..2aed672 100755
--- a/cobbler/cobbler.py
+++ b/cobbler/cobbler.py
@@ -20,10 +20,12 @@ import os
import os.path
import traceback
import optparse
+import string
import commands
+import cexceptions
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
I18N_DOMAIN = "cobbler"
####################################################
@@ -31,7 +33,6 @@ I18N_DOMAIN = "cobbler"
class BootCLI:
def __init__(self):
- textdomain(I18N_DOMAIN)
self.api = api.BootAPI()
self.loader = commands.FunctionLoader()
climods = self.api.get_modules_in_category("cli")
@@ -44,26 +45,36 @@ class BootCLI:
####################################################
+def run_upgrade_checks():
+ """
+ Cobbler tries to make manual upgrade steps unneeded, though
+ this function serves to inform users of manual steps when they /are/
+ needed.
+ """
+ # for users running pre-1.0 upgrading to 1.0
+ if os.path.exists("/var/lib/cobbler/settings"):
+ raise CX(_("/var/lib/cobbler/settings is no longer in use, remove this file to acknowledge you have migrated your configuration to /etc/cobbler/settings. Do not simply copy the file over or you will lose new configuration entries. Run 'cobbler check' and then 'cobbler sync' after making changes."))
+
def main():
"""
CLI entry point
"""
exitcode = 0
try:
- # FIXME: redo locking code?
+ run_upgrade_checks()
return BootCLI().run(sys.argv)
- except CX, exc:
- print str(exc)[1:-1] # remove framing air quotes
- except SystemExit:
- pass # probably exited from optparse, nothing extra to print
- except Exception, exc2:
- if str(type(exc2)).find("CX") == -1:
- traceback.print_exc()
- else:
- print str(exc2)[1:-1] # remove framing air quotes
+ except SystemExit, ex:
+ return 1
+ except Exception, exc:
+ (t, v, tb) = sys.exc_info()
+ try:
+ getattr(exc, "from_cobbler")
+ print str(exc)[1:-1]
+ except:
+ print t
+ print v
+ print string.join(traceback.format_list(traceback.extract_tb(tb)))
return 1
- return 1
-
if __name__ == "__main__":
sys.exit(main())
diff --git a/cobbler/cobblerd.py b/cobbler/cobblerd.py
index 3c06723..065e99e 100644
--- a/cobbler/cobblerd.py
+++ b/cobbler/cobblerd.py
@@ -16,8 +16,9 @@ import time
import os
import SimpleXMLRPCServer
import glob
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import xmlrpclib
+import binascii
from server import xmlrpclib2
import api as cobbler_api
@@ -40,6 +41,8 @@ def core(logger=None):
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)
@@ -47,6 +50,21 @@ def core(logger=None):
# part two: syslog, or syslog+avahi if avahi is installed
do_other_tasks(bootapi, settings, syslog_port, logger)
+def regen_ss_file():
+ # this is only used for Kerberos auth at the moment.
+ # it identifies XMLRPC requests from Apache that have already
+ # been cleared by Kerberos.
+
+ fd = open("/dev/urandom")
+ data = fd.read(512)
+ fd.close()
+ fd = open("/var/lib/cobbler/web.ss","w+")
+ fd.write(binascii.hexlify(data))
+ fd.close()
+ os.system("chmod 700 /var/lib/cobbler/web.ss")
+ 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()
@@ -108,7 +126,7 @@ 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,True)
+ xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerXMLRPCInterface,False)
server = remote.CobblerXMLRPCServer(('', port))
server.logRequests = 0 # don't print stuff
@@ -124,7 +142,7 @@ def do_xmlrpc(bootapi, settings, port, logger):
def do_xmlrpc_rw(bootapi,settings,port,logger):
- xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,False)
+ 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)
@@ -195,11 +213,14 @@ if __name__ == "__main__":
#main()
- bootapi = cobbler_api.BootAPI()
- settings = bootapi.settings()
- syslog_port = settings.syslog_port
- xmlrpc_port = settings.xmlrpc_port
- xmlrpc_port2 = settings.xmlrpc_rw_port
- logger = bootapi.logger_remote
- do_xmlrpc_unix(bootapi, settings, logger)
+ #bootapi = cobbler_api.BootAPI()
+ #settings = bootapi.settings()
+ #syslog_port = settings.syslog_port
+ #xmlrpc_port = settings.xmlrpc_port
+ #xmlrpc_port2 = settings.xmlrpc_rw_port
+ #logger = bootapi.logger_remote
+ #do_xmlrpc_unix(bootapi, settings, logger)
+
+ regen_ss_file()
+
diff --git a/cobbler/collection.py b/cobbler/collection.py
index 339a4b2..1b509f2 100644
--- a/cobbler/collection.py
+++ b/cobbler/collection.py
@@ -25,7 +25,7 @@ import item_profile
import item_distro
import item_repo
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class Collection(serializable.Serializable):
@@ -35,7 +35,8 @@ class Collection(serializable.Serializable):
"""
self.config = config
self.clear()
- self.log_func = self.config.api.log
+ self.api = self.config.api
+ self.log_func = self.api.log
self.lite_sync = None
def factory_produce(self,config,seed_data):
@@ -125,10 +126,10 @@ class Collection(serializable.Serializable):
k.set_parent(newname)
else:
k.set_distro(newname)
- self.config.api.profiles().add(k, save=True, with_sync=with_sync, with_triggers=with_triggers)
+ self.api.profiles().add(k, save=True, with_sync=with_sync, with_triggers=with_triggers)
elif k.COLLECTION_TYPE == "system":
k.set_profile(newname)
- self.config.api.systems().add(k, save=True, with_sync=with_sync, with_triggers=with_triggers)
+ self.api.systems().add(k, save=True, with_sync=with_sync, with_triggers=with_triggers)
elif k.COLLECTION_TYPE == "repo":
raise CX(_("internal error, not expected to have repo child objects"))
else:
@@ -139,7 +140,7 @@ class Collection(serializable.Serializable):
return True
- def add(self,ref,save=False,with_copy=False,with_triggers=True,with_sync=True,quick_pxe_update=False):
+ def add(self,ref,save=False,with_copy=False,with_triggers=True,with_sync=True,quick_pxe_update=False,check_for_duplicate_names=False,check_for_duplicate_netinfo=False):
"""
Add an object to the collection, if it's valid. Returns True
if the object was added to the collection. Returns False if the
@@ -167,6 +168,11 @@ class Collection(serializable.Serializable):
# if not saving the object, you can't run these features
with_triggers = False
with_sync = False
+
+ # Avoid adding objects to the collection
+ # if an object of the same/ip/mac already exists.
+ self.__duplication_checks(ref,check_for_duplicate_names,check_for_duplicate_netinfo)
+
if ref is None or not ref.is_valid():
raise CX(_("insufficient or invalid arguments supplied"))
@@ -220,6 +226,51 @@ class Collection(serializable.Serializable):
def _run_triggers(self,ref,globber):
return utils.run_triggers(ref,globber)
+ def __duplication_checks(self,ref,check_for_duplicate_names,check_for_duplicate_netinfo):
+ """
+ Prevents adding objects with the same name.
+ Prevents adding or editing to provide the same IP, or MAC.
+ Enforcement is based on whether the API caller requests it.
+ """
+
+ # always protect against duplicate names
+ if check_for_duplicate_names:
+ match = None
+ if isinstance(ref, item_system.System):
+ match = self.api.find_system(ref.name)
+ elif isinstance(ref, item_profile.Profile):
+ match = self.api.find_profile(ref.name)
+ elif isinstance(ref, item_distro.Distro):
+ match = self.api.find_distro(ref.name)
+ elif isinstance(ref, item_repo.Repo):
+ match = self.api.find_repo(ref.name)
+
+ if match:
+ raise CX(_("An object already exists with that name. Try 'edit'?"))
+
+ # the duplicate mac/ip checks can be disabled.
+ if not check_for_duplicate_netinfo:
+ return
+
+ if isinstance(ref, item_system.System):
+ for (name, intf) in ref.interfaces.iteritems():
+ match_ip = []
+ match_mac = []
+ input_mac = intf["mac_address"]
+ input_ip = intf["ip_address"]
+ if not self.api.settings().allow_duplicate_macs and input_mac is not None and input_mac != "":
+ match_mac = self.api.find_system(mac_address=input_mac,return_list=True)
+ if not self.api.settings().allow_duplicate_ips and input_ip is not None and input_ip != "":
+ match_ip = self.api.find_system(ip_address=input_ip,return_list=True)
+ # it's ok to conflict with your own net info.
+
+ for x in match_mac:
+ if x.name != ref.name:
+ raise CX(_("Can't save system %s. The MAC address (%s) is already used by system %s (%s)") % (ref.name, intf["mac_address"], x.name, name))
+ for x in match_ip:
+ if x.name != ref.name:
+ raise CX(_("Can't save system %s. The IP address (%s) is already used by system %s (%s)") % (ref.name, intf["ip_address"], x.name, name))
+
def printable(self):
"""
Creates a printable representation of the collection suitable
diff --git a/cobbler/collection_distros.py b/cobbler/collection_distros.py
index f78dd64..5857b9a 100644
--- a/cobbler/collection_distros.py
+++ b/cobbler/collection_distros.py
@@ -18,7 +18,7 @@ import collection
import item_distro as distro
from cexceptions import *
import action_litesync
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class Distros(collection.Collection):
diff --git a/cobbler/collection_profiles.py b/cobbler/collection_profiles.py
index f00a8dc..139c94e 100644
--- a/cobbler/collection_profiles.py
+++ b/cobbler/collection_profiles.py
@@ -20,7 +20,7 @@ import utils
import collection
from cexceptions import *
import action_litesync
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
#--------------------------------------------
diff --git a/cobbler/collection_repos.py b/cobbler/collection_repos.py
index 44bef2a..91e0667 100644
--- a/cobbler/collection_repos.py
+++ b/cobbler/collection_repos.py
@@ -18,7 +18,7 @@ import item_repo as repo
import utils
import collection
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
TESTMODE = False
diff --git a/cobbler/collection_systems.py b/cobbler/collection_systems.py
index 140a981..8a967be 100644
--- a/cobbler/collection_systems.py
+++ b/cobbler/collection_systems.py
@@ -18,7 +18,7 @@ import utils
import collection
from cexceptions import *
import action_litesync
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
#--------------------------------------------
diff --git a/cobbler/commands.py b/cobbler/commands.py
index d97a6e1..705249d 100644
--- a/cobbler/commands.py
+++ b/cobbler/commands.py
@@ -14,7 +14,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import optparse
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import sys
HELP_FORMAT = "%-20s%s"
@@ -195,6 +195,14 @@ class CobblerFunction:
Boilerplate for objects that offer add/edit/delete/remove/copy functionality.
"""
+ if "dumpvars" in self.args:
+ if not self.options.name:
+ raise CX(_("name is required"))
+ obj = collect_fn().find(self.options.name)
+ if obj is None:
+ raise CX(_("object not found"))
+ return obj
+
if "remove" in self.args:
recursive = False
# only applies to distros/profiles and is not supported elsewhere
@@ -219,11 +227,12 @@ class CobblerFunction:
self.reporting_list_names2(collect_fn(),self.options.name)
return None
+ if not self.options.name:
+ raise CX(_("name is required"))
+
if "add" in self.args:
obj = new_fn(is_subobject=subobject)
else:
- if not self.options.name:
- raise CX(_("name is required"))
if "delete" in self.args:
collect_fn().remove(self.options.name, with_delete=True)
return None
@@ -241,6 +250,14 @@ class CobblerFunction:
Boilerplate for objects that offer add/edit/delete/remove/copy functionality.
"""
+ if "dumpvars" in self.args:
+ print obj.dump_vars(True)
+ return True
+
+ clobber = False
+ if "add" in self.args:
+ clobber = options.clobber
+
if "copy" in self.args: # or "rename" in self.args:
if self.options.newname:
obj = obj.make_clone()
@@ -251,10 +268,45 @@ class CobblerFunction:
opt_sync = not options.nosync
opt_triggers = not options.notriggers
+ # ** WARNING: COMPLICATED **
+ # what operation we call depends on what type of object we are editing
+ # and what the operation is. The details behind this is that the
+ # add operation has special semantics around adding objects that might
+ # clobber other objects, and we don't want that to happen. Edit
+ # does not have to check for named clobbering but still needs
+ # to check for IP/MAC clobbering in some scenarios (FIXME).
+ # this is all enforced by collections.py though we need to make
+ # the apppropriate call to add to invoke the safety code in the right
+ # places -- and not in places where the safety code will generate
+ # errors under legit circumstances.
+
if not ("rename" in self.args):
- rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers)
+ if "add" in self.args:
+ if obj.COLLECTION_TYPE == "system":
+ # duplicate names and netinfo are both bad.
+ if not clobber:
+ rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=True, check_for_duplicate_netinfo=True)
+ else:
+ rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=False, check_for_duplicate_netinfo=True)
+ else:
+ # duplicate names are bad
+ if not clobber:
+ rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=True, check_for_duplicate_netinfo=False)
+ else:
+ rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_names=False, check_for_duplicate_netinfo=False)
+ else:
+ check_dup = False
+ if not "copy" in self.args:
+ check_dup = True
+ # FIXME: this ensures duplicate prevention on copy, but not
+ # rename?
+ rc = collect_fn().add(obj, save=True, with_sync=opt_sync, with_triggers=opt_triggers, check_for_duplicate_netinfo=check_dup)
+
else:
+ # we are renaming here, so duplicate netinfo checks also
+ # need to be made.(FIXME)
rc = collect_fn().rename(obj, self.options.newname)
+
return rc
def reporting_sorter(self, a, b):
diff --git a/cobbler/config.py b/cobbler/config.py
index 258b241..4a1b9a4 100644
--- a/cobbler/config.py
+++ b/cobbler/config.py
@@ -29,7 +29,7 @@ import modules.serializer_yaml as serializer_yaml
import settings
import serializer
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class Config:
diff --git a/cobbler/demo_connect.py b/cobbler/demo_connect.py
index 0fa058b..94fa390 100644
--- a/cobbler/demo_connect.py
+++ b/cobbler/demo_connect.py
@@ -11,12 +11,25 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""
-from server.xmlrpcclient import ServerProxy
+from xmlrpclib import ServerProxy
+import optparse
if __name__ == "__main__":
- sp = ServerProxy("httpu:///var/lib/cobbler/sock")
- print sp.login("<system>","")
-
+ p = optparse.OptionParser()
+ p.add_option("-u","--user",dest="user",default="test")
+ p.add_option("-p","--pass",dest="password",default="test")
+
+ # 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")
+ (options, args) = p.parse_args()
+ print "- trying to login with user=%s" % options.user
+ token = sp.login(options.user,options.password)
+ print "- token: %s" % token
+ print "- authenticated ok, now seeing if user is authorized"
+ check = sp.check_access(token,"imaginary_method_name")
+ print "- access ok? %s" % check
diff --git a/cobbler/item.py b/cobbler/item.py
index fd79012..992a18d 100644
--- a/cobbler/item.py
+++ b/cobbler/item.py
@@ -16,7 +16,8 @@ import exceptions
import serializable
import utils
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
+import pprint
class Item(serializable.Serializable):
@@ -51,7 +52,6 @@ class Item(serializable.Serializable):
self.clear(is_subobject) # reset behavior differs for inheritance cases
self.parent = '' # all objects by default are not subobjects
self.children = {} # caching for performance reasons, not serialized
-
self.log_func = self.config.api.log
def clear(self):
@@ -122,6 +122,16 @@ class Item(serializable.Serializable):
self.name = name
return True
+ def set_owners(self,data):
+ """
+ The owners field is a comment unless using an authz module that pays attention to it,
+ 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)
+ self.owners = owners
+ return True
+
def set_kernel_options(self,options):
"""
Kernel options are a space delimited list,
@@ -189,8 +199,9 @@ class Item(serializable.Serializable):
if key in [ "mac_address", "ip_address", "subnet", "gateway", "virt_bridge", "dhcp_tag", "hostname" ]:
key_found_already = True
for (name, interface) in data["interfaces"].iteritems():
- if interface[key].lower() == value.lower():
- return True
+ if value is not None:
+ if interface[key].lower() == value.lower():
+ return True
if not data.has_key(key):
if not key_found_already:
@@ -202,3 +213,10 @@ class Item(serializable.Serializable):
else:
return False
+ def dump_vars(self,data,format=True):
+ raw = utils.blender(self.config.api, False, self)
+ if format:
+ return pprint.pformat(raw)
+ else:
+ return raw
+
diff --git a/cobbler/item_distro.py b/cobbler/item_distro.py
index f52ad0b..1a4c296 100644
--- a/cobbler/item_distro.py
+++ b/cobbler/item_distro.py
@@ -20,7 +20,7 @@ import weakref
import os
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class Distro(item.Item):
@@ -32,6 +32,7 @@ class Distro(item.Item):
Reset this object.
"""
self.name = None
+ self.owners = self.settings.default_ownership
self.kernel = (None, '<<inherit>>')[is_subobject]
self.initrd = (None, '<<inherit>>')[is_subobject]
self.kernel_options = ({}, '<<inherit>>')[is_subobject]
@@ -60,6 +61,7 @@ class Distro(item.Item):
"""
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')
@@ -75,6 +77,8 @@ class Distro(item.Item):
if self.ks_meta != "<<inherit>>" and type(self.ks_meta) != dict:
self.set_ksmeta(self.ks_meta)
+ self.set_owners(self.owners)
+
return self
def set_kernel(self,kernel):
@@ -165,7 +169,8 @@ class Distro(item.Item):
'breed' : self.breed,
'source_repos' : self.source_repos,
'parent' : self.parent,
- 'depth' : self.depth
+ 'depth' : self.depth,
+ 'owners' : self.owners
}
def printable(self):
@@ -175,12 +180,13 @@ class Distro(item.Item):
kstr = utils.find_kernel(self.kernel)
istr = utils.find_initrd(self.initrd)
buf = _("distro : %s\n") % self.name
- buf = buf + _("kernel : %s\n") % kstr
+ buf = buf + _("breed : %s\n") % self.breed
+ buf = buf + _("architecture : %s\n") % self.arch
buf = buf + _("initrd : %s\n") % istr
+ buf = buf + _("kernel : %s\n") % kstr
buf = buf + _("kernel options : %s\n") % self.kernel_options
- buf = buf + _("architecture : %s\n") % self.arch
buf = buf + _("ks metadata : %s\n") % self.ks_meta
- buf = buf + _("breed : %s\n") % self.breed
+ buf = buf + _("owners : %s\n") % self.owners
return buf
def remote_methods(self):
@@ -191,7 +197,8 @@ class Distro(item.Item):
'kopts' : self.set_kernel_options,
'arch' : self.set_arch,
'ksmeta' : self.set_ksmeta,
- 'breed' : self.set_breed
+ 'breed' : self.set_breed,
+ 'owners' : self.set_owners
}
diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py
index f229d4c..0e16f46 100644
--- a/cobbler/item_profile.py
+++ b/cobbler/item_profile.py
@@ -16,7 +16,7 @@ import utils
import item
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class Profile(item.Item):
@@ -34,6 +34,7 @@ class Profile(item.Item):
Reset this object.
"""
self.name = None
+ self.owners = self.settings.default_ownership
self.distro = (None, '<<inherit>>')[is_subobject]
self.kickstart = (self.settings.default_kickstart , '<<inherit>>')[is_subobject]
self.kernel_options = ({}, '<<inherit>>')[is_subobject]
@@ -57,6 +58,7 @@ class Profile(item.Item):
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.kickstart = self.load_item(seed_data,'kickstart')
self.kernel_options = self.load_item(seed_data,'kernel_options')
@@ -68,7 +70,10 @@ class Profile(item.Item):
# backwards compatibility
if type(self.repos) != list:
- self.set_repos(self.repos)
+ # ensure we are formatted correctly though if some repo
+ # defs don't exist on this side, don't fail as we need
+ # to convert everything -- cobbler check can report it
+ self.set_repos(self.repos,bypass_check=True)
self.set_parent(self.parent)
# virt specific
@@ -85,7 +90,9 @@ class Profile(item.Item):
if self.ks_meta != "<<inherit>>" and type(self.ks_meta) != dict:
self.set_ksmeta(self.ks_meta)
if self.repos != "<<inherit>>" and type(self.ks_meta) != list:
- self.set_repos(self.repos)
+ self.set_repos(self.repos,bypass_check=True)
+
+ self.set_owners(self.owners)
return self
@@ -134,46 +141,7 @@ class Profile(item.Item):
self.server = server
return True
- def set_repos(self,repos):
-
- # WARNING: hack
- repos = utils.fix_mod_python_select_submission(repos)
-
-
- # allow the magic inherit string to persist
- if repos == "<<inherit>>":
- # FIXME: this is not inheritable in the WebUI presently ?
- self.repos = "<<inherit>>"
- return
-
- # store as an array regardless of input type
- if repos is None:
- repolist = []
- elif type(repos) != list:
- # allow backwards compatibility support of string input
- repolist = repos.split(None)
- else:
- repolist = repos
-
-
- # make sure there are no empty strings
- try:
- repolist.remove('')
- except:
- pass
-
- self.repos = []
-
- # if any repos don't exist, fail the operation
- ok = True
- for r in repolist:
- if self.config.repos().find(name=r) is not None:
- self.repos.append(r)
- else:
- print _("warning: repository not found: %s" % r)
-
- return True
-
+
def set_kickstart(self,kickstart):
"""
Sets the kickstart. This must be a NFS, HTTP, or FTP URL.
@@ -188,108 +156,27 @@ class Profile(item.Item):
raise CX(_("kickstart not found"))
def set_virt_cpus(self,num):
- """
- For Virt only. Set the number of virtual CPUs to give to the
- virtual machine. This is fed to virtinst RAW, so cobbler
- will not yelp if you try to feed it 9999 CPUs. No formatting
- like 9,999 please :)
- """
- if num == "<<inherit>>":
- self.virt_cpus = "<<inherit>>"
- return True
-
- try:
- num = int(str(num))
- except:
- raise CX(_("invalid number of virtual CPUs"))
-
- self.virt_cpus = num
- return True
+ return utils.set_virt_cpus(self,num)
def set_virt_file_size(self,num):
- """
- For Virt only.
- Specifies the size of the virt image in gigabytes.
- Older versions of koan (x<0.6.3) interpret 0 as "don't care"
- Newer versions (x>=0.6.4) interpret 0 as "no disks"
- """
- # num is a non-negative integer (0 means default)
- # can also be a comma seperated list -- for usage with multiple disks
-
- if num == "<<inherit>>":
- self.virt_file_size = "<<inherit>>"
- return True
-
- if type(num) == str and num.find(",") != -1:
- tokens = num.split(",")
- for t in tokens:
- # hack to run validation on each
- self.set_virt_file_size(t)
- # if no exceptions raised, good enough
- self.virt_file_size = num
- return True
-
- try:
- inum = int(num)
- if inum != float(num):
- return CX(_("invalid virt file size"))
- if inum >= 0:
- self.virt_file_size = inum
- return True
- raise CX(_("invalid virt file size"))
- except:
- raise CX(_("invalid virt file size"))
-
+ return utils.set_virt_file_size(self,num)
+
def set_virt_ram(self,num):
- """
- For Virt only.
- Specifies the size of the Virt RAM in MB.
- 0 tells Koan to just choose a reasonable default.
- """
-
- if num == "<<inherit>>":
- self.virt_ram = "<<inherit>>"
- return True
-
- # num is a non-negative integer (0 means default)
- try:
- inum = int(num)
- if inum != float(num):
- return CX(_("invalid virt ram size"))
- if inum >= 0:
- self.virt_ram = inum
- return True
- return CX(_("invalid virt ram size"))
- except:
- return CX(_("invalid virt ram size"))
+ return utils.set_virt_ram(self,num)
def set_virt_type(self,vtype):
- """
- Virtualization preference, can be overridden by koan.
- """
-
- if vtype == "<<inherit>>":
- self.virt_type == "<<inherit>>"
- return True
-
- if vtype.lower() not in [ "qemu", "xenpv", "xenfv", "vmware", "auto" ]:
- raise CX(_("invalid virt type"))
- self.virt_type = vtype
- return True
+ return utils.set_virt_type(self,vtype)
def set_virt_bridge(self,vbridge):
- """
- The default bridge for all virtual interfaces under this profile.
- """
self.virt_bridge = vbridge
return True
def set_virt_path(self,path):
- """
- Virtual storage location suggestion, can be overriden by koan.
- """
- self.virt_path = path
- return True
+ return utils.set_virt_path(self,path)
+
+ def set_repos(self,repos,bypass_check=False):
+ return utils.set_repos(self,repos,bypass_check)
+
def get_parent(self):
"""
@@ -328,6 +215,7 @@ class Profile(item.Item):
"""
return {
'name' : self.name,
+ 'owners' : self.owners,
'distro' : self.distro,
'kickstart' : self.kickstart,
'kernel_options' : self.kernel_options,
@@ -342,7 +230,8 @@ class Profile(item.Item):
'virt_type' : self.virt_type,
'virt_path' : self.virt_path,
'dhcp_tag' : self.dhcp_tag,
- 'server' : self.server
+ 'server' : self.server,
+
}
def printable(self):
@@ -354,20 +243,22 @@ class Profile(item.Item):
buf = buf + _("parent : %s\n") % self.parent
else:
buf = buf + _("distro : %s\n") % self.distro
- buf = buf + _("kickstart : %s\n") % self.kickstart
+ buf = buf + _("dhcp tag : %s\n") % self.dhcp_tag
buf = buf + _("kernel options : %s\n") % self.kernel_options
+ buf = buf + _("kickstart : %s\n") % self.kickstart
buf = buf + _("ks metadata : %s\n") % self.ks_meta
+ buf = buf + _("owners : %s\n") % self.owners
+ buf = buf + _("repos : %s\n") % self.repos
+ buf = buf + _("server : %s\n") % self.server
+ buf = buf + _("virt bridge : %s\n") % self.virt_bridge
+ buf = buf + _("virt cpus : %s\n") % self.virt_cpus
buf = buf + _("virt file size : %s\n") % self.virt_file_size
+ buf = buf + _("virt path : %s\n") % self.virt_path
buf = buf + _("virt ram : %s\n") % self.virt_ram
buf = buf + _("virt type : %s\n") % self.virt_type
- buf = buf + _("virt path : %s\n") % self.virt_path
- buf = buf + _("virt bridge : %s\n") % self.virt_bridge
- buf = buf + _("virt cpus : %s\n") % self.virt_cpus
- buf = buf + _("repos : %s\n") % self.repos
- buf = buf + _("dhcp tag : %s\n") % self.dhcp_tag
- buf = buf + _("server : %s\n") % self.server
return buf
+
def remote_methods(self):
return {
'name' : self.set_name,
@@ -385,6 +276,7 @@ class Profile(item.Item):
'virt-bridge' : self.set_virt_bridge,
'virt-cpus' : self.set_virt_cpus,
'dhcp-tag' : self.set_dhcp_tag,
- 'server' : self.set_server
+ 'server' : self.set_server,
+ 'owners' : self.set_owners
}
diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py
index ca9e94f..3b9b839 100644
--- a/cobbler/item_repo.py
+++ b/cobbler/item_repo.py
@@ -15,7 +15,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import utils
import item
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class Repo(item.Item):
@@ -31,6 +31,7 @@ class Repo(item.Item):
def clear(self,is_subobject=False):
self.parent = None
self.name = None
+ # FIXME: subobject code does not really make sense for repos
self.mirror = (None, '<<inherit>>')[is_subobject]
self.keep_updated = ('y', '<<inherit>>')[is_subobject]
self.priority = (99, '<<inherit>>')[is_subobject]
@@ -39,6 +40,8 @@ class Repo(item.Item):
self.depth = 2 # arbitrary, as not really apart of the graph
self.arch = "" # use default arch
self.yumopts = {}
+ self.owners = self.settings.default_ownership
+ self.mirror_locally = 1
def from_datastruct(self,seed_data):
self.parent = self.load_item(seed_data, 'parent')
@@ -51,9 +54,12 @@ class Repo(item.Item):
self.arch = self.load_item(seed_data, 'arch')
self.depth = self.load_item(seed_data, 'depth', 2)
self.yumopts = self.load_item(seed_data, 'yumopts', {})
+ self.owners = self.load_item(seed_data, 'owners', self.settings.default_ownership)
+ self.mirror_locally = self.load_item(seed_data, 'mirror_locally', '1')
- # force this to be saved as a boolean
+ # coerce types from input file
self.set_keep_updated(self.keep_updated)
+ self.set_owners(self.owners)
return self
@@ -63,6 +69,13 @@ class Repo(item.Item):
reposync/repotrack integration over HTTP might come later.
"""
self.mirror = mirror
+ if self.arch is None or self.arch == "":
+ if mirror.find("x86_64") != -1:
+ self.set_arch("x86_64")
+ elif mirror.find("x86") != -1 or mirror.find("i386") != -1:
+ self.set_arch("i386")
+ elif mirror.find("ia64") != -1:
+ self.set_arch("ia64")
return True
def set_keep_updated(self,keep_updated):
@@ -153,7 +166,9 @@ class Repo(item.Item):
def to_datastruct(self):
return {
'name' : self.name,
+ 'owners' : self.owners,
'mirror' : self.mirror,
+ 'mirror_locally' : self.mirror_locally,
'keep_updated' : self.keep_updated,
'priority' : self.priority,
'rpm_list' : self.rpm_list,
@@ -164,14 +179,24 @@ class Repo(item.Item):
'yumopts' : self.yumopts
}
+ def set_mirror_locally(self,value):
+ value = str(value).lower()
+ if value in [ "yes", "y", "1", "on", "true" ]:
+ self.mirror_locally = 1
+ else:
+ self.mirror_locally = 0
+ return True
+
def printable(self):
buf = _("repo : %s\n") % self.name
- buf = buf + _("mirror : %s\n") % self.mirror
+ buf = buf + _("arch : %s\n") % self.arch
+ buf = buf + _("createrepo_flags : %s\n") % self.createrepo_flags
buf = buf + _("keep updated : %s\n") % self.keep_updated
+ buf = buf + _("mirror : %s\n") % self.mirror
+ buf = buf + _("mirror locally : %s\n") % self.mirror_locally
+ buf = buf + _("owners : %s\n") % self.owners
buf = buf + _("priority : %s\n") % self.priority
buf = buf + _("rpm list : %s\n") % self.rpm_list
- buf = buf + _("createrepo_flags : %s\n") % self.createrepo_flags
- buf = buf + _("arch : %s\n") % self.arch
buf = buf + _("yum options : %s\n") % self.yumopts
return buf
@@ -202,6 +227,8 @@ class Repo(item.Item):
'priority' : self.set_priority,
'rpm-list' : self.set_rpm_list,
'createrepo-flags' : self.set_createrepo_flags,
- 'yumopts' : self.set_yumopts
+ 'yumopts' : self.set_yumopts,
+ 'owners' : self.set_owners,
+ 'mirror-locally' : self.set_mirror_locally
}
diff --git a/cobbler/item_system.py b/cobbler/item_system.py
index dadd8d1..09f169d 100644
--- a/cobbler/item_system.py
+++ b/cobbler/item_system.py
@@ -15,7 +15,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import utils
import item
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
class System(item.Item):
@@ -30,6 +30,7 @@ class System(item.Item):
def clear(self,is_subobject=False):
self.name = None
+ self.owners = self.settings.default_ownership
self.profile = None
self.kernel_options = {}
self.ks_meta = {}
@@ -37,9 +38,15 @@ class System(item.Item):
self.netboot_enabled = True
self.depth = 2
self.kickstart = "<<inherit>>" # use value in profile
+ self.server = "<<inherit>>" # "" (or settings)
self.virt_path = "<<inherit>>" # ""
self.virt_type = "<<inherit>>" # ""
- self.server = "<<inherit>>" # "" (or settings)
+ 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>>" # ""
def delete_interface(self,name):
"""
@@ -80,16 +87,25 @@ class System(item.Item):
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.profile = self.load_item(seed_data, 'profile')
self.kernel_options = self.load_item(seed_data, 'kernel_options', {})
self.ks_meta = self.load_item(seed_data, 'ks_meta', {})
self.depth = self.load_item(seed_data, 'depth', 2)
self.kickstart = self.load_item(seed_data, 'kickstart', '<<inherit>>')
- self.virt_path = self.load_item(seed_data, 'virt_path', '<<inherit>>')
- self.virt_type = self.load_item(seed_data, 'virt_type', '<<inherit>>')
self.netboot_enabled = self.load_item(seed_data, 'netboot_enabled', True)
self.server = self.load_item(seed_data, 'server', '<<inherit>>')
+ # virt specific
+ self.virt_path = self.load_item(seed_data, 'virt_path', '<<inherit>>')
+ self.virt_type = self.load_item(seed_data, 'virt_type', '<<inherit>>')
+ self.virt_ram = self.load_item(seed_data,'virt_ram','<<inherit>>')
+ self.virt_file_size = self.load_item(seed_data,'virt_file_size','<<inherit>>')
+ self.virt_path = self.load_item(seed_data,'virt_path','<<inherit>>')
+ self.virt_type = self.load_item(seed_data,'virt_type','<<inherit>>')
+ self.virt_bridge = self.load_item(seed_data,'virt_bridge','<<inherit>>')
+ self.virt_cpus = self.load_item(seed_data,'virt_cpus','<<inherit>>')
+
# backwards compat, these settings are now part of the interfaces data structure
# and will contain data only in upgrade scenarios.
@@ -126,8 +142,9 @@ class System(item.Item):
# explicitly re-call the set_name function to possibily populate MAC/IP.
self.set_name(self.name)
- # coerce this into a boolean
+ # coerce types from input file
self.set_netboot_enabled(self.netboot_enabled)
+ self.set_owners(self.owners)
return self
@@ -154,7 +171,7 @@ class System(item.Item):
raise CX(_("name must be a string"))
for x in name:
if not x.isalnum() and not x in [ "_", "-", ".", ":", "+" ] :
- raise CX(_("invalid characters in name"))
+ raise CX(_("invalid characters in name: %s") % x)
if utils.is_mac(name):
if intf["mac_address"] == "":
@@ -212,11 +229,12 @@ class System(item.Item):
"""
if self.name == "default":
return True
- mac = self.get_mac_address(interface)
- ip = self.get_ip_address(interface)
- if mac is None and ip is None:
- return False
- return True
+ for (name,x) in self.interfaces.iteritems():
+ mac = x.get("mac_address",None)
+ ip = x.get("ip_address",None)
+ if mac is not None or ip is not None:
+ return True
+ return False
def set_dhcp_tag(self,dhcp_tag,interface="intf0"):
intf = self.__get_interface(interface)
@@ -231,7 +249,7 @@ class System(item.Item):
def set_ip_address(self,address,interface="intf0"):
"""
Assign a IP or hostname in DHCP when this MAC boots.
- Only works if manage_dhcp is set in /var/lib/cobbler/settings
+ Only works if manage_dhcp is set in /etc/cobbler/settings
"""
intf = self.__get_interface(interface)
if address == "" or utils.is_ip(address):
@@ -273,21 +291,20 @@ class System(item.Item):
return True
raise CX(_("invalid profile name"))
- def set_virt_path(self,path):
- """
- Virtual storage location suggestion, can be overriden by koan.
- """
- self.virt_path = path
- return True
+ def set_virt_cpus(self,num):
+ return utils.set_virt_cpus(self,num)
+
+ def set_virt_file_size(self,num):
+ return utils.set_virt_file_size(self,num)
+
+ def set_virt_ram(self,num):
+ return utils.set_virt_ram(self,num)
def set_virt_type(self,vtype):
- """
- Virtualization preference, can be overridden by koan.
- """
- if vtype.lower() not in [ "qemu", "xenpv", "xenfv", "vmware", "auto" ]:
- raise CX(_("invalid virt type"))
- self.virt_type = vtype
- return True
+ return utils.set_virt_type(self,vtype)
+
+ def set_virt_path(self,path):
+ return utils.set_virt_path(self,path)
def set_netboot_enabled(self,netboot_enabled):
"""
@@ -336,6 +353,9 @@ class System(item.Item):
abstraction layer -- assigning systems to defined and repeatable
roles.
"""
+ if kickstart is None or kickstart == "" or kickstart == "delete":
+ self.kickstart = "<<inherit>>"
+ return True
if utils.find_kickstart(kickstart):
self.kickstart = kickstart
return True
@@ -344,32 +364,44 @@ class System(item.Item):
def to_datastruct(self):
return {
- 'name' : self.name,
- 'profile' : self.profile,
- 'kernel_options' : self.kernel_options,
- 'ks_meta' : self.ks_meta,
- 'netboot_enabled' : self.netboot_enabled,
- 'parent' : self.parent,
- 'depth' : self.depth,
- 'kickstart' : self.kickstart,
- 'virt_type' : self.virt_type,
- 'virt_path' : self.virt_path,
- 'interfaces' : self.interfaces,
- 'server' : self.server
+ 'name' : self.name,
+ 'kernel_options' : self.kernel_options,
+ '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,
+ '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
+
}
def printable(self):
buf = _("system : %s\n") % self.name
buf = buf + _("profile : %s\n") % self.profile
buf = buf + _("kernel options : %s\n") % self.kernel_options
+ buf = buf + _("kickstart : %s\n") % self.kickstart
buf = buf + _("ks metadata : %s\n") % self.ks_meta
buf = buf + _("netboot enabled? : %s\n") % self.netboot_enabled
- buf = buf + _("kickstart : %s\n") % self.kickstart
- buf = buf + _("virt type : %s\n") % self.virt_type
- buf = buf + _("virt path : %s\n") % self.virt_path
+ buf = buf + _("owners : %s\n") % self.owners
buf = buf + _("server : %s\n") % self.server
+ buf = buf + _("virt cpus : %s\n") % self.virt_cpus
+ buf = buf + _("virt file size : %s\n") % self.virt_file_size
+ buf = buf + _("virt path : %s\n") % self.virt_path
+ buf = buf + _("virt ram : %s\n") % self.virt_ram
+ buf = buf + _("virt type : %s\n") % self.virt_type
+
+
counter = 0
for (name,x) in self.interfaces.iteritems():
buf = buf + _("interface : %s\n") % (name)
@@ -414,6 +446,12 @@ class System(item.Item):
'virt-type' : self.set_virt_type,
'modify-interface' : self.modify_interface,
'delete-interface' : self.delete_interface,
- 'server' : self.set_server
+ 'virt-path' : self.set_virt_path,
+ 'virt-ram' : self.set_virt_ram,
+ 'virt-type' : self.set_virt_type,
+ 'virt-cpus' : self.set_virt_cpus,
+ 'virt-file-size' : self.set_virt_file_size,
+ 'server' : self.set_server,
+ 'owners' : self.set_owners
}
diff --git a/cobbler/kickgen.py b/cobbler/kickgen.py
new file mode 100644
index 0000000..f302d00
--- /dev/null
+++ b/cobbler/kickgen.py
@@ -0,0 +1,279 @@
+"""
+Builds out filesystem trees/data based on the object tree.
+This is the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+
+class KickGen:
+ """
+ Handles conversion of internal state to the tftpboot tree layout
+ """
+
+ def __init__(self,config):
+ """
+ Constructor
+ """
+ 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)
+
+ def generate_kickstart_for_profile(self,g):
+
+ g = self.api.find_profile(name=g)
+ if g is None:
+ return "# profile not found"
+
+ distro = g.get_conceptual_parent()
+ meta = utils.blender(self.api, False, g)
+ if distro is None:
+ raise CX(_("profile %(profile)s references missing distro %(distro)s") % { "profile" : g.name, "distro" : g.distro })
+ kickstart_path = utils.find_kickstart(meta["kickstart"])
+ if kickstart_path is not None and os.path.exists(kickstart_path):
+ # the input is an *actual* file, hence we have to copy it
+ try:
+ meta = utils.blender(self.api, False, g)
+ ksmeta = meta["ks_meta"]
+ del meta["ks_meta"]
+ meta.update(ksmeta) # make available at top level
+ meta["yum_repo_stanza"] = self.generate_repo_stanza(g,True)
+ meta["yum_config_stanza"] = self.generate_config_stanza(g,True)
+ meta["kickstart_done"] = self.generate_kickstart_signal(0, g, None)
+ meta["kickstart_start"] = self.generate_kickstart_signal(1, g, None)
+ meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"])
+ kfile = open(kickstart_path)
+ data = self.templar.render(kfile, meta, None, g)
+ kfile.close()
+ return data
+ except:
+ utils.log_exc(self.api.logger)
+ return _("# Error while rendering kickstart file, see /var/log/cobbler/cobbler.log for details")
+
+ def generate_kickstart_signal(self, is_pre=0, profile=None, system=None):
+ """
+ Do things that we do at the start/end of kickstarts...
+ * start: signal the status watcher we're starting
+ * end: signal the status watcher we're done
+ * end: disable PXE if needed
+ * end: save the original kickstart file for debug
+ """
+
+ nopxe = "\nwget \"http://%s/cblr/svc/op/nopxe/system/%s\" -O /dev/null"
+ saveks = "\nwget \"http://%s/cblr/svc/op/ks/%s/%s\" -O /root/cobbler.ks"
+ runpost = "\nwget \"http://%s/cblr/svc/op/trig/mode/post/%s/%s\" -O /dev/null"
+ runpre = "\nwget \"http://%s/cblr/svc/op/trig/mode/pre/%s/%s\" -O /dev/null"
+
+ what = "profile"
+ blend_this = profile
+ if system:
+ what = "system"
+ blend_this = system
+
+ blended = utils.blender(self.api, False, blend_this)
+ kickstart = blended.get("kickstart",None)
+
+ buf = ""
+ srv = blended["http_server"]
+ if system is not None:
+ if not is_pre:
+ if str(self.settings.pxe_just_once).upper() in [ "1", "Y", "YES", "TRUE" ]:
+ buf = buf + nopxe % (srv, system.name)
+ if kickstart and os.path.exists(kickstart):
+ buf = buf + saveks % (srv, "system", system.name)
+ if self.settings.run_install_triggers:
+ buf = buf + runpost % (srv, what, system.name)
+ else:
+ if self.settings.run_install_triggers:
+ buf = buf + runpre % (srv, what, system.name)
+
+ else:
+ if not is_pre:
+ if kickstart and os.path.exists(kickstart):
+ buf = buf + saveks % (srv, "profile", profile.name)
+ if self.settings.run_install_triggers:
+ buf = buf + runpost % (srv, what, profile.name)
+ else:
+ if self.settings.run_install_triggers:
+ buf = buf + runpre % (srv, what, profile.name)
+
+ return buf
+
+ def generate_repo_stanza(self, obj, is_profile=True):
+
+ """
+ Automatically attaches yum repos to profiles/systems in kickstart files
+ that contain the magic $yum_repo_stanza variable.
+ """
+
+ buf = ""
+ blended = utils.blender(self.api, False, obj)
+ configs = self.get_repo_filenames(obj,is_profile)
+ repos = self.repos
+
+ # FIXME: this really should be dynamically generated as with the kickstarts
+ for c in configs:
+ name = c.split("/")[-1].replace(".repo","")
+ (is_core, baseurl) = self.analyze_repo_config(c)
+ for repo in repos:
+ if repo.name == name:
+ if not repo.yumopts.has_key('enabled') or repo.yumopts['enabled'] == '1':
+ if repo.mirror_locally:
+ buf = buf + "repo --name=%s --baseurl=%s\n" % (name, baseurl)
+ else:
+ buf = buf + "repo --name=%s --baseurl=%s\n" % (name, repo.mirror)
+
+ return buf
+
+ def analyze_repo_config(self, filename):
+ fd = open(filename)
+ data = fd.read()
+ lines = data.split("\n")
+ ret = False
+ baseurl = None
+ for line in lines:
+ if line.find("ks_mirror") != -1:
+ ret = True
+ if line.find("baseurl") != -1:
+ try:
+ first, baseurl = line.split("=",1)
+ except:
+ raise CX(_("error scanning repo: %s" % filename))
+ fd.close()
+ return (ret, baseurl)
+
+ def get_repo_baseurl(self, server, repo_name, is_repo_mirror=True):
+ """
+ Construct the URL to a repo definition.
+ """
+ if is_repo_mirror:
+ return "http://%s/cobbler/repo_mirror/%s" % (server, repo_name)
+ else:
+ return "http://%s/cobbler/ks_mirror/config/%s" % (server, repo_name)
+
+ def get_repo_filenames(self, obj, is_profile=True):
+ """
+ For a given object, return the paths to repo configuration templates
+ that will be used to generate per-object repo configuration files and
+ baseurls
+ """
+
+ blended = utils.blender(self.api, False, obj)
+ urlseg = self.get_repo_segname(is_profile)
+
+ topdir = "%s/%s/%s/*.repo" % (self.settings.webdir, urlseg, blended["name"])
+ files = glob.glob(topdir)
+ return files
+
+
+ def get_repo_segname(self, is_profile):
+ if is_profile:
+ return "repos_profile"
+ else:
+ return "repos_system"
+
+
+ def generate_config_stanza(self, obj, is_profile=True):
+
+ """
+ Add in automatic to configure /etc/yum.repos.d on the remote system
+ if the kickstart file contains the magic $yum_config_stanza.
+ """
+
+ if not self.settings.yum_post_install_mirror:
+ return ""
+
+ urlseg = self.get_repo_segname(is_profile)
+
+ distro = obj.get_conceptual_parent()
+ if not is_profile:
+ distro = distro.get_conceptual_parent()
+
+ blended = utils.blender(self.api, False, obj)
+ configs = self.get_repo_filenames(obj, is_profile)
+ buf = ""
+
+ # for each kickstart template we have rendered ...
+ for c in configs:
+
+ name = c.split("/")[-1].replace(".repo","")
+ # add the line to create the yum config file on the target box
+ conf = self.get_repo_config_file(blended["http_server"],urlseg,blended["name"],name)
+ buf = buf + "wget \"%s\" --output-document=/etc/yum.repos.d/%s.repo\n" % (conf, name)
+
+ return buf
+
+ def get_repo_config_file(self,server,urlseg,obj_name,repo_name):
+ """
+ Construct the URL to a repo config file that is usable in kickstart
+ for use with yum. This is different than the templates cobbler reposync
+ creates, as this file will allow the server to migrate and have different
+ variables for different subnets/profiles/etc.
+ """
+ return "http://%s/cblr/%s/%s/%s.repo" % (server,urlseg,obj_name,repo_name)
+
+ def generate_kickstart_for_system(self,s):
+
+
+ s = self.api.find_system(name=s)
+ if s is None:
+ return "# system not found"
+
+ profile = s.get_conceptual_parent()
+ if profile is None:
+ raise CX(_("system %(system)s references missing profile %(profile)s") % { "system" : s.name, "profile" : s.profile })
+ distro = profile.get_conceptual_parent()
+ meta = utils.blender(self.api, False, s)
+ kickstart_path = utils.find_kickstart(meta["kickstart"])
+ if kickstart_path and os.path.exists(kickstart_path):
+ try:
+ ksmeta = meta["ks_meta"]
+ del meta["ks_meta"]
+ meta.update(ksmeta) # make available at top level
+ meta["yum_repo_stanza"] = self.generate_repo_stanza(s, False)
+ meta["yum_config_stanza"] = self.generate_config_stanza(s, False)
+ meta["kickstart_done"] = self.generate_kickstart_signal(0, profile, s)
+ meta["kickstart_start"] = self.generate_kickstart_signal(1, profile, s)
+ meta["kernel_options"] = utils.hash_to_string(meta["kernel_options"])
+ kfile = open(kickstart_path)
+ data = self.templar.render(kfile, meta, None, s)
+ kfile.close()
+ return data
+ except:
+ traceback.print_exc()
+ raise CX(_("Error templating file"))
+
diff --git a/cobbler/manage_ctrl.py b/cobbler/manage_ctrl.py
new file mode 100644
index 0000000..a01910a
--- /dev/null
+++ b/cobbler/manage_ctrl.py
@@ -0,0 +1,76 @@
+"""
+Builds out DHCP info
+This is the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+import popen2
+from shlex import shlex
+
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+class ManageCtrl:
+ """
+ Handles conversion of internal state to the tftpboot tree layout
+ """
+
+ def __init__(self,config,verbose=False,dns=None,dhcp=None):
+ """
+ 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.dns = dns
+
+ def write_dhcp_lease(self,port,host,ip,mac):
+ return dhcp.write_dhcp_lease(port,host,ip,mac)
+
+ def remove_dhcp_lease(self,port,host):
+ return dhcp.remove_dhcp_lease(port,host)
+
+ def write_dhcp_file(self):
+ return dhcp.write_dhcp_file()
+
+ def regen_ethers(self):
+ return dhcp.regen_ethers()
+
+ def regen_hosts(self):
+ return dns.regen_hosts()
+
+ def write_dns_files(self):
+ return dns.write_bind_files()
diff --git a/cobbler/module_loader.py b/cobbler/module_loader.py
index b695a04..d584ce3 100644
--- a/cobbler/module_loader.py
+++ b/cobbler/module_loader.py
@@ -19,7 +19,7 @@ import distutils.sysconfig
import os
import sys
import glob
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import ConfigParser
MODULE_CACHE = {}
diff --git a/cobbler/modules/authn_configfile.py b/cobbler/modules/authn_configfile.py
index ffe87a7..be453be 100644
--- a/cobbler/modules/authn_configfile.py
+++ b/cobbler/modules/authn_configfile.py
@@ -17,7 +17,7 @@ import distutils.sysconfig
import ConfigParser
import sys
import os
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import md5
import traceback
diff --git a/cobbler/modules/authn_denyall.py b/cobbler/modules/authn_denyall.py
new file mode 100644
index 0000000..91e27d4
--- /dev/null
+++ b/cobbler/modules/authn_denyall.py
@@ -0,0 +1,43 @@
+"""
+Authentication module that denies everything.
+Used to disable the WebUI by default.
+
+Copyright 2007-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import distutils.sysconfig
+import sys
+
+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 authenticate(api_handle,username,password):
+ """
+ Validate a username/password combo, returning True/False
+
+ Thanks to http://trac.edgewall.org/ticket/845 for supplying
+ the algorithm info.
+ """
+
+ # debugging only (not safe to enable)
+ # api_handle.logger.debug("backend authenticate (%s,%s)" % (username,password))
+
+ return False
+
+
diff --git a/cobbler/modules/authn_kerberos.py b/cobbler/modules/authn_kerberos.py
deleted file mode 100644
index 7f85db6..0000000
--- a/cobbler/modules/authn_kerberos.py
+++ /dev/null
@@ -1,81 +0,0 @@
-"""
-Authentication module that uses kerberos.
-
-Copyright 2007, Red Hat, Inc
-Michael DeHaan <mdehaan@redhat.com>
-
-This software may be freely redistributed under the terms of the GNU
-general public license.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-"""
-
-# NOTE: this is not using 'straight up' kerberos in that we
-# relay passwords through cobblerd for authentication, that may
-# be done later. It does of course check against kerberos,
-# however.
-
-# ALSO NOTE: we're calling out to a Perl program to make
-# this work. You must install Authen::Simple::Kerberos
-# from CPAN and the Kerberos libraries for this to work.
-# See the Cobbler Wiki for more info.
-
-# ALSO ALSO NOTE: set kerberos_realm in /var/lib/cobbler/settings
-# to something appropriate or this will never work. CASING
-# MATTERS. example.com != EXAMPLE.COM.
-
-import distutils.sysconfig
-import ConfigParser
-import sys
-import os
-from rhpl.translate import _, N_, textdomain, utf8
-import md5
-import traceback
-# since sub_process isn't available on older OS's
-try:
- import sub_process as subprocess
-except:
- import subprocess
-
-plib = distutils.sysconfig.get_python_lib()
-mod_path="%s/cobbler" % plib
-sys.path.insert(0, mod_path)
-
-import cexceptions
-import utils
-
-def register():
- """
- The mandatory cobbler module registration hook.
- """
- return "authn"
-
-def authenticate(api_handle,username,password):
- """
- Validate a username/password combo, returning True/False
- Uses cobbler_auth_helper
- """
-
- realm = self.api.settings().kerberos_realm
- api_handle.logger.debug("authenticating %s against %s" % (username,realm))
-
- rc = subprocess.call([
- "/usr/bin/cobbler_auth_help",
- "--method=kerberos",
- "--username=%s" % username,
- "--password=%s" % password,
- "--realm=%s" % realm
- ])
- print rc
- if rc == 42:
- api_handle.logger.debug("authenticated ok")
- # authentication ok (FIXME: log)
- return True
- else:
- api_handle.logger.debug("authentication failed")
- # authentication failed
- return False
-
-
diff --git a/cobbler/modules/authn_ldap.py b/cobbler/modules/authn_ldap.py
new file mode 100644
index 0000000..ff31750
--- /dev/null
+++ b/cobbler/modules/authn_ldap.py
@@ -0,0 +1,118 @@
+"""
+Authentication module that uses ldap
+Settings in /etc/cobbler/authn_ldap.conf
+Choice of authentication module is in /etc/cobbler/modules.conf
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import distutils.sysconfig
+import sys
+import os
+from utils import _
+import md5
+import traceback
+
+# we'll import this just a bit later
+# to keep it from being a requirement
+# import ldap
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+import cexceptions
+import utils
+import api as cobbler_api
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+
+ return "authn"
+
+def authenticate(api_handle,username,password):
+ """
+ Validate an ldap bind, returning True/False
+ """
+
+ import ldap
+
+ server = api_handle.settings().ldap_server
+ basedn = api_handle.settings().ldap_base_dn
+ port = api_handle.settings().ldap_port
+ tls = api_handle.settings().ldap_tls
+ anon_bind = api_handle.settings().ldap_anonymous_bind
+ prefix = api_handle.settings().ldap_search_prefix
+
+ # form our ldap uri based on connection port
+ if port == '389':
+ uri = 'ldap://' + server
+ elif port == '636':
+ uri = 'ldaps://' + server
+ else:
+ uri = 'ldap://' + "%s:%s" % (server,port)
+
+ # connect to LDAP host
+ dir = ldap.initialize(uri)
+
+ # start_tls if tls is 'on', 'true' or 'yes'
+ # and we're not already using old-SSL
+ tls = str(tls).lower()
+ if port != '636':
+ if tls in [ "on", "true", "yes", "1" ]:
+ try:
+ dir.start_tls_s()
+ except:
+ traceback.print_exc()
+ return False
+
+ # if we're not allowed to search anonymously,
+ # grok the search bind settings and attempt to bind
+ anon_bind = str(anon_bind).lower()
+ if anon_bind not in [ "on", "true", "yes", "1" ]:
+ searchdn = api_handle.settings().ldap_search_bind_dn
+ searchpw = api_handle.settings().ldap_search_passwd
+
+ if searchdn == '' or searchpw == '':
+ raise "Missing search bind settings"
+
+ try:
+ dir.simple_bind_s(searchdn, searchpw)
+ except:
+ traceback.print_exc()
+ return False
+
+ # perform a subtree search in basedn to find the full dn of the user
+ # TODO: what if username is a CN? maybe it goes into the config file as well?
+ filter = prefix + username
+ result = dir.search_s(basedn, ldap.SCOPE_SUBTREE, filter, [])
+ if result:
+ for dn,entry in result:
+ # username _should_ be unique so we should only have one result
+ # ignore entry; we don't need it
+ pass
+ else:
+ return False
+
+ try:
+ # attempt to bind as the user
+ dir.simple_bind_s(dn,password)
+ dir.unbind()
+ return True
+ except:
+ # traceback.print_exc()
+ return False
+ # catch-all
+ return False
+
+if __name__ == "__main__":
+ api_handle = cobbler_api.BootAPI()
+ print authenticate(api_handle, "guest", "guest")
+
diff --git a/cobbler/modules/authn_passthru.py b/cobbler/modules/authn_passthru.py
new file mode 100644
index 0000000..ebbe79a
--- /dev/null
+++ b/cobbler/modules/authn_passthru.py
@@ -0,0 +1,49 @@
+"""
+Authentication module that defers to Apache and trusts
+what Apache trusts.
+
+Copyright 2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import distutils.sysconfig
+import sys
+import os
+from utils import _
+import traceback
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+import cexceptions
+import utils
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "authn"
+
+def authenticate(api_handle,username,password):
+ """
+ Validate a username/password combo, returning True/False
+ Uses cobbler_auth_helper
+ """
+
+ fd = open("/var/lib/cobbler/web.ss")
+ data = fd.read()
+ if password == data:
+ rc = 1
+ else:
+ rc = 0
+ fd.close()
+ return data
+
diff --git a/cobbler/modules/authn_testing.py b/cobbler/modules/authn_testing.py
new file mode 100644
index 0000000..cd74cdf
--- /dev/null
+++ b/cobbler/modules/authn_testing.py
@@ -0,0 +1,42 @@
+"""
+Authentication module that denies everything.
+Unsafe demo. Allows anyone in with testing/testing.
+
+Copyright 2007-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import distutils.sysconfig
+import sys
+
+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 authenticate(api_handle,username,password):
+ """
+ Validate a username/password combo, returning True/False
+
+ Thanks to http://trac.edgewall.org/ticket/845 for supplying
+ the algorithm info.
+ """
+
+ if username == "testing" and password == "testing":
+ return True
+ return False
+
+
diff --git a/cobbler/modules/authz_allowall.py b/cobbler/modules/authz_allowall.py
index 1b05630..10d4b17 100644
--- a/cobbler/modules/authz_allowall.py
+++ b/cobbler/modules/authz_allowall.py
@@ -16,7 +16,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import distutils.sysconfig
import ConfigParser
import sys
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
diff --git a/cobbler/modules/authz_configfile.py b/cobbler/modules/authz_configfile.py
new file mode 100644
index 0000000..84343e2
--- /dev/null
+++ b/cobbler/modules/authz_configfile.py
@@ -0,0 +1,64 @@
+"""
+Authorization module that allow users listed in
+/etc/cobbler/users.conf to be permitted to access resources.
+For instance, when using authz_ldap, you want to use authn_configfile,
+not authz_allowall, which will most likely NOT do what you want.
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import distutils.sysconfig
+import ConfigParser
+import sys
+import os
+from utils import _
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+import cexceptions
+import utils
+
+CONFIG_FILE='/etc/cobbler/users.conf'
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "authz"
+
+def __parse_config():
+ if not os.path.exists(CONFIG_FILE):
+ return []
+ config = ConfigParser.SafeConfigParser()
+ config.read(CONFIG_FILE)
+ alldata = {}
+ groups = config.sections()
+ for g in groups:
+ alldata[str(g)] = {}
+ opts = config.options(g)
+ for o in opts:
+ alldata[g][o] = 1
+ return alldata
+
+
+def authorize(api_handle,user,resource,arg1=None,arg2=None):
+ """
+ Validate a user against a resource.
+ All users in the file are permitted by this module.
+ """
+
+ data = __parse_config()
+ for g in data:
+ if user in data[g]:
+ return 1
+ return 0
+
+if __name__ == "__main__":
+ print __parse_config()
diff --git a/cobbler/modules/authz_ownership.py b/cobbler/modules/authz_ownership.py
new file mode 100644
index 0000000..fbd00f9
--- /dev/null
+++ b/cobbler/modules/authz_ownership.py
@@ -0,0 +1,178 @@
+"""
+Authorization module that allow users listed in
+/etc/cobbler/users.conf to be permitted to access resources, with
+the further restriction that cobbler objects can be edited to only
+allow certain users/groups to access those specific objects.
+
+Copyright 2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import distutils.sysconfig
+import ConfigParser
+import sys
+import os
+from cobbler.utils import _
+
+plib = distutils.sysconfig.get_python_lib()
+mod_path="%s/cobbler" % plib
+sys.path.insert(0, mod_path)
+
+import cexceptions
+import utils
+
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "authz"
+
+def __parse_config():
+ etcfile='/etc/cobbler/users.conf'
+ if not os.path.exists(etcfile):
+ raise CX(_("/etc/cobbler/users.conf does not exist"))
+ config = ConfigParser.ConfigParser()
+ config.read(etcfile)
+ alldata = {}
+ sections = config.sections()
+ for g in sections:
+ alldata[str(g)] = {}
+ opts = config.options(g)
+ for o in opts:
+ alldata[g][o] = 1
+ return alldata
+
+def __authorize_kickstart(api_handle, user, user_groups, kickstart):
+ # the authorization rules for kickstart editing are a bit
+ # of a special case. Non-admin users can edit a kickstart
+ # only if all objects that depend on that kickstart are
+ # editable by the user in question.
+ #
+ # Example:
+ # if Pinky owns ProfileA
+ # and the Brain owns ProfileB
+ # and both profiles use the same kickstart template
+ # and neither Pinky nor the Brain is an admin
+ # neither is allowed to edit the kickstart template
+ # because they would make unwanted changes to each other
+ #
+ # In the above scenario the UI will explain the problem
+ # and ask that the user asks the admin to resolve it if required.
+ # NOTE: this function is only called by authorize so admin users are
+ # cleared before this function is called.
+
+ lst = api_handle.find_profile(kickstart=kickstart, return_list=True)
+ lst.extend(api_handle.find_system(kickstart=kickstart, return_list=True))
+ for obj in lst:
+ if not __is_user_allowed(obj, user, user_groups):
+ return 0
+ return 1
+
+def __is_user_allowed(obj, user, user_groups):
+ if obj.owners == []:
+ # no ownership restrictions, cleared
+ return 1
+ for allowed in obj.owners:
+ if user == allowed:
+ # user match
+ return 1
+ # else look for a group match
+ for group in user_groups:
+ if group == allowed and user in user_groups[group]:
+ return 1
+ return 0
+
+
+
+def authorize(api_handle,user,resource,arg1=None,arg2=None):
+ """
+ Validate a user against a resource.
+ All users in the file are permitted by this module.
+ """
+
+ # everybody can get read-only access to everything
+ # if they pass authorization, they don't have to be in users.conf
+ if resource is not None:
+ for x in [ "get", "read", "/cobbler/web" ]:
+ if resource.startswith(x):
+ return 1
+
+ user_groups = __parse_config()
+
+ # classify the type of operation
+ modify_operation = False
+ for criteria in ["save","remove","modify","write","edit"]:
+ if resource.find(criteria) != -1:
+ modify_operation = True
+
+ # FIXME: is everyone allowed to copy? I think so.
+ # FIXME: deal with the problem of deleted parents and promotion
+
+ found_user = False
+ for g in user_groups:
+ for x in user_groups[g]:
+ if x == user:
+ found_user = True
+ # if user is in the admin group, always authorize
+ # regardless of the ownership of the object.
+ if g == "admins" or g == "admin":
+ return 1
+ break
+
+ if not found_user:
+ # if the user isn't anywhere in the file, reject regardless
+ # they can still use read-only XMLRPC
+ return 0
+ if not modify_operation:
+ # sufficient to allow access for non save/remove ops to all
+ # users for now, may want to refine later.
+ return 1
+
+ # now we have a modify_operation op, so we must check ownership
+ # of the object. remove ops pass in arg1 as a string name,
+ # saves pass in actual objects, so we must treat them differently.
+ # kickstarts are even more special so we call those out to another
+ # function, rather than going through the rest of the code here.
+
+ if resource.find("kickstart") != -1:
+ return __authorize_kickstart(api_handle,user,user_groups,arg1)
+
+ obj = None
+ if resource.find("remove") != -1:
+ if resource == "remove_distro":
+ obj = api_handle.find_distro(arg1)
+ elif resource == "remove_profile":
+ obj = api_handle.find_profile(arg1)
+ elif resource == "remove_system":
+ obj = api_handle.find_system(arg1)
+ elif resource == "remove_repo":
+ obj = api_handle.find_system(arg1)
+ elif resource.find("save") != -1 or resource.find("modify") != -1:
+ obj = arg1
+
+ # if the object has no ownership data, allow access regardless
+ if obj.owners is None or obj.owners == []:
+ return 1
+
+ return __is_user_allowed(obj,user,user_groups)
+
+
+if __name__ == "__main__":
+ # real tests are contained in tests/tests.py
+ import api as cobbler_api
+ api = cobbler_api.BootAPI()
+ print __parse_config()
+ print authorize(api, "admin1", "sync")
+ d = api.find_distro("F9B-i386")
+ d.set_owners(["allowed"])
+ api.add_distro(d)
+ print authorize(api, "admin1", "save_distro", d)
+ print authorize(api, "basement2", "save_distro", d)
diff --git a/cobbler/modules/cli_distro.py b/cobbler/modules/cli_distro.py
index 35f5a4b..b7a8fa7 100644
--- a/cobbler/modules/cli_distro.py
+++ b/cobbler/modules/cli_distro.py
@@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import commands
import cexceptions
@@ -33,13 +33,16 @@ class DistroFunction(commands.CobblerFunction):
return "distro"
def subcommands(self):
- return [ "add", "edit", "copy", "rename", "remove", "list", "report" ]
+ return [ "add", "edit", "copy", "rename", "remove", "list", "report", "dumpvars" ]
def add_options(self, p, args):
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--arch", dest="arch", help="ex: x86, x86_64, ia64")
p.add_option("--breed", dest="breed", help="ex: redhat, debian, suse")
+ if self.matches_args(args,["add"]):
+ p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true")
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--initrd", dest="initrd", help="absolute path to initrd.img (REQUIRED)")
p.add_option("--kernel", dest="kernel", help="absolute path to vmlinuz (REQUIRED)")
p.add_option("--kopts", dest="kopts", help="ex: 'noipv6'")
@@ -47,12 +50,16 @@ class DistroFunction(commands.CobblerFunction):
p.add_option("--name", dest="name", help="ex: 'RHEL-5-i386' (REQUIRED)")
+
+
if self.matches_args(args,["copy","rename"]):
p.add_option("--newname", dest="newname", help="for copy/rename commands")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed")
- if not self.matches_args(args,["report","list"]):
+ if not self.matches_args(args,["dumpvars","report","list"]):
p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution")
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
+ p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module")
if self.matches_args(args,["remove"]):
p.add_option("--recursive", action="store_true", dest="recursive", help="also delete child objects")
@@ -63,16 +70,19 @@ class DistroFunction(commands.CobblerFunction):
if obj is None:
return True
- if self.options.kernel:
- obj.set_kernel(self.options.kernel)
- if self.options.initrd:
- obj.set_initrd(self.options.initrd)
- if self.options.kopts:
- obj.set_kernel_options(self.options.kopts)
- if self.options.ksmeta:
- obj.set_ksmeta(self.options.ksmeta)
- if self.options.breed:
- obj.set_breed(self.options.breed)
+ if not "dumpvars" in self.args:
+ if self.options.kernel:
+ obj.set_kernel(self.options.kernel)
+ if self.options.initrd:
+ obj.set_initrd(self.options.initrd)
+ if self.options.kopts:
+ obj.set_kernel_options(self.options.kopts)
+ if self.options.ksmeta:
+ obj.set_ksmeta(self.options.ksmeta)
+ if self.options.breed:
+ obj.set_breed(self.options.breed)
+ if self.options.owners:
+ obj.set_owners(self.options.owners)
return self.object_manipulator_finish(obj, self.api.distros, self.options)
diff --git a/cobbler/modules/cli_misc.py b/cobbler/modules/cli_misc.py
index ebbba47..c72b11d 100644
--- a/cobbler/modules/cli_misc.py
+++ b/cobbler/modules/cli_misc.py
@@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import commands
from cexceptions import *
HELP_FORMAT = commands.HELP_FORMAT
@@ -73,11 +73,12 @@ class ImportFunction(commands.CobblerFunction):
return "import"
def add_options(self, p, args):
+ p.add_option("--arch", dest="arch", help="explicitly specify the architecture being imported (RECOMENDED)")
p.add_option("--path", dest="mirror", help="local path or rsync location (REQUIRED)")
p.add_option("--mirror", dest="mirror_alt", help="alias for --path")
p.add_option("--name", dest="name", help="name, ex 'RHEL-5', (REQUIRED)")
- p.add_option("--available-as", dest="available_as", help="do not mirror, use this as install tree")
- p.add_option("--kickstart", dest="kickstart_file", help="use the kickstart file specified as the profile's kickstart file")
+ p.add_option("--available-as", dest="available_as", help="do not mirror, use this as install tree base")
+ p.add_option("--kickstart", dest="kickstart_file", help="use the kickstart file specified as the profile's kickstart file, do not auto-assign")
p.add_option("--rsync-flags", dest="rsync_flags", help="pass additional flags to rsync")
def run(self):
@@ -92,7 +93,8 @@ class ImportFunction(commands.CobblerFunction):
self.options.name,
network_root=self.options.available_as,
kickstart_file=self.options.kickstart_file,
- rsync_flags=self.options.rsync_flags
+ rsync_flags=self.options.rsync_flags,
+ arch=self.options.arch
)
@@ -182,11 +184,6 @@ class ReportFunction(commands.CobblerFunction):
self.reporting_print_sorted(self.api.repos())
return True
-## FIXME: add legacy command translator to keep things simple
-## cobbler system report foo --> cobbler report --what=systems --name=foo
-## cobbler system report --> cobbler report --what=systems
-## ditto for "cobbler list"
-
########################################################
class StatusFunction(commands.CobblerFunction):
@@ -198,7 +195,7 @@ class StatusFunction(commands.CobblerFunction):
return "status"
def run(self):
- return self.api.status("text") # no other output modes supported yet
+ return self.api.status("text") # no other output modes supported yet
########################################################
@@ -243,6 +240,46 @@ class ValidateKsFunction(commands.CobblerFunction):
return self.api.validateks()
########################################################
+
+class BuildIsoFunction(commands.CobblerFunction):
+
+ def add_options(self,p,args):
+ p.add_option("--iso", dest="isoname", help="(OPTIONAL) output ISO to this path")
+ p.add_option("--profiles", dest="profiles", help="(OPTIONAL) use these profiles only")
+ p.add_option("--tempdir", dest="tempdir", help="(OPTIONAL) working directory")
+
+ def help_me(self):
+ return HELP_FORMAT % ("cobbler buildiso","")
+
+ def command_name(self):
+ return "buildiso"
+
+ def run(self):
+ return self.api.build_iso(
+ iso=self.options.isoname,
+ profiles=self.options.profiles,
+ tempdir=self.options.tempdir
+ )
+
+########################################################
+
+class ReplicateFunction(commands.CobblerFunction):
+
+ def help_me(self):
+ return HELP_FORMAT % ("cobbler replicate","[ARGS|--help]")
+
+ def command_name(self):
+ return "replicate"
+
+ def add_options(self, p, args):
+ p.add_option("--master", dest="master", help="Cobbler server to replicate from.")
+
+ def run(self):
+ return self.api.replicate(cobbler_master = self.options.master)
+
+
+
+########################################################
# MODULE HOOKS
def register():
@@ -253,9 +290,11 @@ def register():
def cli_functions(api):
return [
+ BuildIsoFunction(api),
CheckFunction(api), ImportFunction(api), ReserializeFunction(api),
ListFunction(api), ReportFunction(api), StatusFunction(api),
- SyncFunction(api), RepoSyncFunction(api), ValidateKsFunction(api)
+ SyncFunction(api), RepoSyncFunction(api), ValidateKsFunction(api),
+ ReplicateFunction(api)
]
return []
diff --git a/cobbler/modules/cli_profile.py b/cobbler/modules/cli_profile.py
index e9d1d23..45b7b2e 100644
--- a/cobbler/modules/cli_profile.py
+++ b/cobbler/modules/cli_profile.py
@@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import commands
import cexceptions
@@ -33,10 +33,15 @@ class ProfileFunction(commands.CobblerFunction):
return "profile"
def subcommands(self):
- return [ "add", "edit", "copy", "rename", "remove", "list", "report" ]
+ return [ "add", "edit", "copy", "rename", "remove", "list", "report", "dumpvars" ]
def add_options(self, p, args):
- if not self.matches_args(args,["remove","report","list"]):
+
+ if self.matches_args(args,["add"]):
+ p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true")
+
+
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--distro", dest="distro", help="ex: 'RHEL-5-i386' (REQUIRED)")
p.add_option("--dhcp-tag", dest="dhcp_tag", help="for use in advanced DHCP configuration")
@@ -46,18 +51,20 @@ class ProfileFunction(commands.CobblerFunction):
p.add_option("--kopts", dest="kopts", help="ex: 'noipv6'")
p.add_option("--name", dest="name", help="a name for the profile (REQUIRED)")
+
if "copy" in args or "rename" in args:
p.add_option("--newname", dest="newname")
- if not self.matches_args(args,["remove","report", "list"]):
+ if not self.matches_args(args,["dumpvars","remove","report", "list"]):
p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed")
- if not self.matches_args(args,["report", "list"]):
+ if not self.matches_args(args,["dumpvars","report", "list"]):
p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution")
+ p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module")
if self.matches_args(args,["remove"]):
p.add_option("--recursive", action="store_true", dest="recursive", help="also delete child objects")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--repos", dest="repos", help="names of cobbler repos")
p.add_option("--server-override", dest="server_override", help="overrides value in settings file")
p.add_option("--virt-bridge", dest="virt_bridge", help="ex: 'virbr0'")
@@ -69,8 +76,7 @@ class ProfileFunction(commands.CobblerFunction):
def run(self):
-
- if self.matches_args(self.args,["report","list","remove"]) or not self.options.inherit:
+ if self.matches_args(self.args,["report","list","remove","dumpvars"]) or not self.options.inherit:
obj = self.object_manipulator_start(self.api.new_profile,self.api.profiles,subobject=False)
else:
obj = self.object_manipulator_start(self.api.new_profile,self.api.profiles,subobject=True)
@@ -78,20 +84,23 @@ class ProfileFunction(commands.CobblerFunction):
if obj is None:
return True
- if self.options.inherit: obj.set_parent(self.options.inherit)
- if self.options.distro: obj.set_distro(self.options.distro)
- if self.options.kickstart: obj.set_kickstart(self.options.kickstart)
- if self.options.kopts: obj.set_kernel_options(self.options.kopts)
- if self.options.ksmeta: obj.set_ksmeta(self.options.ksmeta)
- if self.options.virt_file_size: obj.set_virt_file_size(self.options.virt_file_size)
- if self.options.virt_ram: obj.set_virt_ram(self.options.virt_ram)
- if self.options.virt_bridge: obj.set_virt_bridge(self.options.virt_bridge)
- if self.options.virt_type: obj.set_virt_type(self.options.virt_type)
- if self.options.virt_cpus: obj.set_virt_cpus(self.options.virt_cpus)
- if self.options.repos: obj.set_repos(self.options.repos)
- if self.options.virt_path: obj.set_virt_path(self.options.virt_path)
- if self.options.dhcp_tag: obj.set_dhcp_tag(self.options.dhcp_tag)
- if self.options.server_override: obj.set_server(self.options.server)
+ if not self.matches_args(self.args,["dumpvars"]):
+ if self.options.inherit: obj.set_parent(self.options.inherit)
+ if self.options.distro: obj.set_distro(self.options.distro)
+ if self.options.kickstart: obj.set_kickstart(self.options.kickstart)
+ if self.options.kopts: obj.set_kernel_options(self.options.kopts)
+ if self.options.ksmeta: obj.set_ksmeta(self.options.ksmeta)
+ if self.options.virt_file_size: obj.set_virt_file_size(self.options.virt_file_size)
+ if self.options.virt_ram: obj.set_virt_ram(self.options.virt_ram)
+ if self.options.virt_bridge: obj.set_virt_bridge(self.options.virt_bridge)
+ if self.options.virt_type: obj.set_virt_type(self.options.virt_type)
+ if self.options.virt_cpus: obj.set_virt_cpus(self.options.virt_cpus)
+ if self.options.repos: obj.set_repos(self.options.repos)
+ if self.options.virt_path: obj.set_virt_path(self.options.virt_path)
+ if self.options.dhcp_tag: obj.set_dhcp_tag(self.options.dhcp_tag)
+ if self.options.server_override: obj.set_server(self.options.server)
+
+ if self.options.owners: obj.set_owners(self.options.owners)
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 96afa6f..af6a9d1 100644
--- a/cobbler/modules/cli_repo.py
+++ b/cobbler/modules/cli_repo.py
@@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import commands
import cexceptions
@@ -33,20 +33,25 @@ class RepoFunction(commands.CobblerFunction):
return "repo"
def subcommands(self):
- return [ "add", "edit", "copy", "rename", "remove", "list", "report" ]
+ return [ "add", "edit", "copy", "rename", "remove", "list", "report", "dumpvars" ]
def add_options(self, p, args):
- if not self.matches_args(args,["remove","report","list"]):
+
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--arch", dest="arch", help="overrides repo arch if required")
+ if self.matches_args(args,["add"]):
+ p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true")
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--createrepo-flags", dest="createrepo_flags", help="additional flags for createrepo")
p.add_option("--keep-updated", dest="keep_updated", help="update on each reposync, yes/no")
p.add_option("--name", dest="name", help="ex: 'Fedora-8-updates-i386' (REQUIRED)")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--mirror", dest="mirror", help="source to mirror (REQUIRED)")
+ p.add_option("--mirror-locally", dest="mirror_locally", help="mirror or use external directly? (default 1)")
p.add_option("--priority", dest="priority", help="set priority")
p.add_option("--rpm-list", dest="rpm_list", help="just mirror these rpms")
p.add_option("--yumopts", dest="yumopts", help="ex: pluginvar=abcd")
@@ -55,10 +60,12 @@ class RepoFunction(commands.CobblerFunction):
p.add_option("--newname", dest="newname", help="used for copy/edit")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed")
- if not self.matches_args(args,["report","list"]):
+ if not self.matches_args(args,["dumpvars","report","list"]):
p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution")
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
+ p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module")
def run(self):
@@ -66,6 +73,8 @@ class RepoFunction(commands.CobblerFunction):
obj = self.object_manipulator_start(self.api.new_repo,self.api.repos)
if obj is None:
return True
+ if self.matches_args(self.args,["dumpvars"]):
+ return self.object_manipulator_finish(obj, self.api.profiles, self.options)
if self.options.arch: obj.set_arch(self.options.arch)
if self.options.createrepo_flags: obj.set_createrepo_flags(self.options.createrepo_flags)
@@ -73,8 +82,12 @@ class RepoFunction(commands.CobblerFunction):
if self.options.keep_updated: obj.set_keep_updated(self.options.keep_updated)
if self.options.priority: obj.set_priority(self.options.priority)
if self.options.mirror: obj.set_mirror(self.options.mirror)
+ if self.options.mirror_locally: obj.set_mirror_locally(self.options.mirror_locally)
if self.options.yumopts: obj.set_yumopts(self.options.yumopts)
+ if self.options.owners:
+ obj.set_owners(self.options.owners)
+
return self.object_manipulator_finish(obj, self.api.repos, self.options)
diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py
index c463b8c..d8f8edf 100644
--- a/cobbler/modules/cli_system.py
+++ b/cobbler/modules/cli_system.py
@@ -19,7 +19,7 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _, get_random_mac
import commands
import cexceptions
@@ -33,11 +33,14 @@ class SystemFunction(commands.CobblerFunction):
return "system"
def subcommands(self):
- return [ "add", "edit", "copy", "rename", "remove", "report", "list" ]
+ return [ "add", "edit", "copy", "rename", "remove", "report", "list", "dumpvars" ]
def add_options(self, p, args):
- if not self.matches_args(args,["remove","report","list"]):
+ if self.matches_args(args,["add"]):
+ p.add_option("--clobber", dest="clobber", help="allow add to overwrite existing objects", action="store_true")
+
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--dhcp-tag", dest="dhcp_tag", help="for use in advanced DHCP configurations")
p.add_option("--gateway", dest="gateway", help="for static IP / templating usage")
p.add_option("--hostname", dest="hostname", help="ex: server.example.org")
@@ -50,25 +53,30 @@ class SystemFunction(commands.CobblerFunction):
p.add_option("--name", dest="name", help="a name for the system (REQUIRED)")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--netboot-enabled", dest="netboot_enabled", help="PXE on (1) or off (0)")
if self.matches_args(args,["copy","rename"]):
p.add_option("--newname", dest="newname", help="for use with copy/edit")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
p.add_option("--no-sync", action="store_true", dest="nosync", help="suppress sync for speed")
- if not self.matches_args(args,["report","list"]):
+ if not self.matches_args(args,["dumpvars","report","list"]):
p.add_option("--no-triggers", action="store_true", dest="notriggers", help="suppress trigger execution")
- if not self.matches_args(args,["remove","report","list"]):
+ if not self.matches_args(args,["dumpvars","remove","report","list"]):
+ p.add_option("--owners", dest="owners", help="specify owners for authz_ownership module")
p.add_option("--profile", dest="profile", help="name of cobbler profile (REQUIRED)")
p.add_option("--server-override", dest="server_override", help="overrides server value in settings file")
p.add_option("--subnet", dest="subnet", help="for static IP / templating usage")
- p.add_option("--virt-bridge", dest="virt_bridge", help="ex: virbr0")
- p.add_option("--virt-path", dest="virt_path", help="path, partition, or volume")
- p.add_option("--virt-type", dest="virt_type", help="ex: xenpv, qemu, xenfv")
+
+ p.add_option("--virt-bridge", dest="virt_bridge", help="ex: 'virbr0'")
+ p.add_option("--virt-cpus", dest="virt_cpus", help="integer (default: 1)")
+ p.add_option("--virt-file-size", dest="virt_file_size", help="size in GB")
+ p.add_option("--virt-path", dest="virt_path", help="path, partition, or volume")
+ p.add_option("--virt-ram", dest="virt_ram", help="size in MB")
+ p.add_option("--virt-type", dest="virt_type", help="ex: 'xenpv', 'qemu'")
def run(self):
@@ -76,6 +84,8 @@ class SystemFunction(commands.CobblerFunction):
obj = self.object_manipulator_start(self.api.new_system,self.api.systems)
if obj is None:
return True
+ if self.matches_args(self.args,["dumpvars"]):
+ return self.object_manipulator_finish(obj, self.api.profiles, self.options)
if self.options.profile: obj.set_profile(self.options.profile)
if self.options.kopts: obj.set_kernel_options(self.options.kopts)
@@ -83,8 +93,13 @@ class SystemFunction(commands.CobblerFunction):
if self.options.kickstart: obj.set_kickstart(self.options.kickstart)
if self.options.netboot_enabled: obj.set_netboot_enabled(self.options.netboot_enabled)
if self.options.server_override: obj.set_server(self.options.server_override)
- if self.options.virt_path: obj.set_virt_path(self.options.virt_path)
+
+ if self.options.virt_file_size: obj.set_virt_file_size(self.options.virt_file_size)
+ if self.options.virt_ram: obj.set_virt_ram(self.options.virt_ram)
if self.options.virt_type: obj.set_virt_type(self.options.virt_type)
+ if self.options.virt_cpus: obj.set_virt_cpus(self.options.virt_cpus)
+ if self.options.virt_path: obj.set_virt_path(self.options.virt_path)
+
if self.options.interface:
my_interface = "intf%s" % self.options.interface
@@ -92,15 +107,23 @@ class SystemFunction(commands.CobblerFunction):
my_interface = "intf0"
if self.options.hostname: obj.set_hostname(self.options.hostname, my_interface)
- if self.options.mac: obj.set_mac_address(self.options.mac, my_interface)
+ if self.options.mac:
+ if self.options.mac.lower() == 'random':
+ obj.set_mac_address(get_random_mac(self.api), my_interface)
+ else:
+ obj.set_mac_address(self.options.mac, my_interface)
if self.options.ip: obj.set_ip_address(self.options.ip, my_interface)
if self.options.subnet: obj.set_subnet(self.options.subnet, my_interface)
if self.options.gateway: obj.set_gateway(self.options.gateway, my_interface)
if self.options.dhcp_tag: obj.set_dhcp_tag(self.options.dhcp_tag, my_interface)
if self.options.virt_bridge: obj.set_virt_bridge(self.options.virt_bridge, my_interface)
- return self.object_manipulator_finish(obj, self.api.systems, self.options)
+ if self.options.owners:
+ obj.set_owners(self.options.owners)
+
+ rc = self.object_manipulator_finish(obj, self.api.systems, self.options)
+ return rc
########################################################
diff --git a/cobbler/modules/manage_bind.py b/cobbler/modules/manage_bind.py
new file mode 100644
index 0000000..339fbf0
--- /dev/null
+++ b/cobbler/modules/manage_bind.py
@@ -0,0 +1,283 @@
+"""
+This is some of the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+John Eckersberg <jeckersb@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+import popen2
+import re
+from shlex import shlex
+
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "manage"
+
+
+class BindManager:
+
+ def what(self):
+ return "isc_and_bind"
+
+ def __init__(self,config,verbose=False):
+ """
+ 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)
+
+ def regen_hosts(self):
+ pass # not used
+
+ def __forward_zones(self):
+ """
+ Returns a map of zones and the records that belong
+ in them
+ """
+ zones = {}
+ for zone in self.settings.manage_forward_zones:
+ zones[zone] = []
+
+ for sys in self.systems:
+ for (name, interface) in sys.interfaces.iteritems():
+ host = interface["hostname"]
+ ip = interface["ip_address"]
+ if not host or not ip:
+ # gotsta have some hostname and ip or else!
+ continue
+ if host.find(".") == -1:
+ continue
+
+ # match the longest zone!
+ # e.g. if you have a host a.b.c.d.e
+ # if manage_forward_zones has:
+ # - c.d.e
+ # - b.c.d.e
+ # then a.b.c.d.e should go in b.c.d.e
+ best_match = ''
+ for zone in zones.keys():
+ if re.search('\.%s$' % zone, host) and len(zone) > len(best_match):
+ best_match = zone
+
+ if best_match == '': # no match
+ continue
+
+ # strip the zone off the hostname and append the
+ # remainder + ip to the zone list
+ host = host.replace(best_match, '')
+ if host[-1] == '.': # strip trailing '.' if it's there
+ host = host[:-1]
+ zones[best_match].append((host, ip))
+
+ # axe zones that are defined in manage_forward_zones
+ # but don't actually match any hosts
+ for (k,v) in zones.items():
+ if v == []:
+ zones.pop(k)
+
+ return zones
+
+ def __reverse_zones(self):
+ """
+ Returns a map of zones and the records that belong
+ in them
+ """
+ zones = {}
+ for zone in self.settings.manage_reverse_zones:
+ zones[zone] = []
+
+ for sys in self.systems:
+ for (name, interface) in sys.interfaces.iteritems():
+ host = interface["hostname"]
+ ip = interface["ip_address"]
+ if not host or not ip:
+ # gotsta have some hostname and ip or else!
+ continue
+
+ # match the longest zone!
+ # e.g. if you have an ip 1.2.3.4
+ # if manage_reverse_zones has:
+ # - 1.2
+ # - 1.2.3
+ # then 1.2.3.4 should go in 1.2.3
+ best_match = ''
+ for zone in zones.keys():
+ if re.search('^%s\.' % zone, ip) and len(zone) > len(best_match):
+ best_match = zone
+
+ if best_match == '': # no match
+ continue
+
+ # strip the zone off the front of the ip
+ # reverse the rest of the octets
+ # append the remainder + hostname
+ ip = ip.replace(best_match, '', 1)
+ if ip[0] == '.': # strip leading '.' if it's there
+ ip = ip[1:]
+ tokens = ip.split('.')
+ tokens.reverse()
+ ip = '.'.join(tokens)
+ zones[best_match].append((ip, host + '.'))
+
+ # axe zones that are defined in manage_forward_zones
+ # but don't actually match any hosts
+ for (k,v) in zones.items():
+ if v == []:
+ zones.pop(k)
+
+ return zones
+
+
+ def __write_named_conf(self):
+ """
+ Write out the named.conf main config file from the template.
+ """
+ settings_file = self.settings.named_conf
+ template_file = "/etc/cobbler/named.template"
+ forward_zones = self.settings.manage_forward_zones
+ reverse_zones = self.settings.manage_reverse_zones
+
+ metadata = {'zone_include': ''}
+ for zone in self.__forward_zones().keys():
+ txt = """
+zone "%(zone)s." {
+ type master;
+ file "%(zone)s";
+};
+""" % {'zone': zone}
+ metadata['zone_include'] = metadata['zone_include'] + txt
+
+ for zone in self.__reverse_zones().keys():
+ tokens = zone.split('.')
+ tokens.reverse()
+ arpa = '.'.join(tokens) + '.in-addr.arpa'
+ txt = """
+zone "%(arpa)s." {
+ type master;
+ file "%(zone)s";
+};
+""" % {'arpa': arpa, 'zone': zone}
+ metadata['zone_include'] = metadata['zone_include'] + txt
+
+ try:
+ f2 = open(template_file,"r")
+ except:
+ raise CX(_("error reading template from file: %s") % template_file)
+ template_data = ""
+ template_data = f2.read()
+ f2.close()
+
+ self.templar.render(template_data, metadata, settings_file, None)
+
+ def __write_zone_files(self):
+ """
+ Write out the forward and reverse zone files for all configured zones
+ """
+ default_template_file = "/etc/cobbler/zone.template"
+ cobbler_server = self.settings.server
+ serial = int(time.time())
+ forward = self.__forward_zones()
+ reverse = self.__reverse_zones()
+
+ try:
+ f2 = open(default_template_file,"r")
+ except:
+ raise CX(_("error reading template from file: %s") % default_template_file)
+ default_template_data = ""
+ default_template_data = f2.read()
+ f2.close()
+
+ for (zone, hosts) in forward.iteritems():
+ metadata = {
+ 'cobbler_server': cobbler_server,
+ 'serial': serial,
+ 'host_record': ''
+ }
+
+ # grab zone-specific template if it exists
+ try:
+ fd = open('/etc/cobbler/zone_templates/%s' % zone)
+ template_data = fd.read()
+ fd.close()
+ except:
+ template_data = default_template_data
+
+ for host in hosts:
+ txt = '%s\tIN\tA\t%s\n' % host
+ metadata['host_record'] = metadata['host_record'] + txt
+
+ self.templar.render(template_data, metadata, '/var/named/' + zone, None)
+
+ for (zone, hosts) in reverse.iteritems():
+ metadata = {
+ 'cobbler_server': cobbler_server,
+ 'serial': serial,
+ 'host_record': ''
+ }
+
+ # grab zone-specific template if it exists
+ try:
+ fd = open('/etc/cobbler/zone_templates/%s' % zone)
+ template_data = fd.read()
+ fd.close()
+ except:
+ template_data = default_template_data
+
+ for host in hosts:
+ txt = '%s\tIN\tPTR\t%s\n' % host
+ metadata['host_record'] = metadata['host_record'] + txt
+
+ self.templar.render(template_data, metadata, '/var/named/' + zone, None)
+
+
+ def write_dns_files(self):
+ """
+ BIND files are written when manage_dns is set in
+ /var/lib/cobbler/settings.
+ """
+
+ self.__write_named_conf()
+ self.__write_zone_files()
+
+def get_manager(config):
+ return BindManager(config)
+
diff --git a/cobbler/modules/manage_dnsmasq.py b/cobbler/modules/manage_dnsmasq.py
new file mode 100644
index 0000000..465995f
--- /dev/null
+++ b/cobbler/modules/manage_dnsmasq.py
@@ -0,0 +1,194 @@
+
+"""
+This is some of the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+John Eckersberg <jeckersb@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+import popen2
+from shlex import shlex
+import utils
+from cexceptions import *
+import templar
+import item_distro
+import item_profile
+import item_repo
+import item_system
+from utils import _
+
+def register():
+ return "manage"
+
+class DnsmasqManager:
+ """
+ Handles conversion of internal state to the tftpboot tree layout
+ """
+
+ def __init__(self,config,verbose=False,dhcp=None):
+ """
+ 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)
+
+ def what(self):
+ return "dnsmasq"
+
+ def write_dhcp_lease(self,port,host,ip,mac):
+ pass
+
+ def remove_dhcp_lease(self,port,host):
+ pass
+
+
+ def write_dhcp_file(self):
+ """
+ DHCP files are written when manage_dhcp is set in
+ /var/lib/cobbler/settings.
+ """
+
+ settings_file = self.settings.dnsmasq_conf
+ template_file = "/etc/cobbler/dnsmasq.template"
+
+ try:
+ f2 = open(template_file,"r")
+ except:
+ raise CX(_("error writing template to file: %s") % template_file)
+ template_data = ""
+ template_data = f2.read()
+ f2.close()
+
+ # build each per-system definition
+ # as configured, this only works for ISC, patches accepted
+ # from those that care about Itanium. elilo seems to be unmaintained
+ # so additional maintaince in other areas may be required to keep
+ # this working.
+
+ elilo = os.path.basename(self.settings.bootloaders["ia64"])
+
+ system_definitions = {}
+ counter = 0
+
+ # we used to just loop through each system, but now we must loop
+ # through each network interface of each system.
+
+ for system in self.systems:
+ profile = system.get_conceptual_parent()
+ distro = profile.get_conceptual_parent()
+ for (name, interface) in system.interfaces.iteritems():
+
+ mac = interface["mac_address"]
+ ip = interface["ip_address"]
+ host = interface["hostname"]
+
+ if mac is None or mac == "":
+ # can't write a DHCP entry for this system
+ continue
+
+ counter = counter + 1
+ systxt = ""
+
+ # dnsmasq. don't have to write IP and other info here, but we do tag
+ # each MAC based on the arch of it's distro, if it needs something other
+ # than pxelinux.0 -- for these arches, and these arches only, a dnsmasq
+ # reload (full "cobbler sync") would be required after adding the system
+ # to cobbler, just to tag this relationship.
+
+ if ip is not None and ip != "":
+ if distro.arch.lower() == "ia64":
+ systxt = "dhcp-host=net:ia64," + ip + "\n"
+ # support for other arches needs modifications here
+ else:
+ systxt = ""
+
+ dhcp_tag = interface["dhcp_tag"]
+ if dhcp_tag == "":
+ dhcp_tag = "default"
+
+ if not system_definitions.has_key(dhcp_tag):
+ system_definitions[dhcp_tag] = ""
+ system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt
+
+ # we are now done with the looping through each interface of each system
+
+ metadata = {
+ "omapi_enabled" : self.settings.omapi_enabled,
+ "omapi_port" : self.settings.omapi_port,
+ "insert_cobbler_system_definitions" : system_definitions.get("default",""),
+ "date" : time.asctime(time.gmtime()),
+ "cobbler_server" : self.settings.server,
+ "next_server" : self.settings.next_server,
+ "elilo" : elilo
+ }
+
+ # now add in other DHCP expansions that are not tagged with "default"
+ for x in system_definitions.keys():
+ if x == "default":
+ continue
+ metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x]
+
+ self.templar.render(template_data, metadata, settings_file, None)
+
+ def regen_ethers(self):
+ # dnsmasq knows how to read this database of MACs -> IPs, so we'll keep it up to date
+ # every time we add a system.
+ # read 'man ethers' for format info
+ fh = open("/etc/ethers","w+")
+ for sys in self.systems:
+ for (name, interface) in sys.interfaces.iteritems():
+ mac = interface["mac_address"]
+ ip = interface["ip_address"]
+ if mac is None or mac == "":
+ # can't write this w/o a MAC address
+ continue
+ if ip is not None and ip != "":
+ fh.write(mac.upper() + "\t" + ip + "\n")
+ fh.close()
+
+ def regen_hosts(self):
+ # dnsmasq knows how to read this database for host info
+ # (other things may also make use of this later)
+ fh = open("/var/lib/cobbler/cobbler_hosts","w+")
+ for sys in self.systems:
+ for (name, interface) in sys.interfaces.iteritems():
+ mac = interface["mac_address"]
+ host = interface["hostname"]
+ ip = interface["ip_address"]
+ if mac is None or mac == "":
+ continue
+ if host is not None and host != "" and ip is not None and ip != "":
+ fh.write(ip + "\t" + host + "\n")
+ fh.close()
+
+ def write_dns_files(self):
+ # already taken care of by the regen_hosts()
+ pass
+
+def get_manager(config):
+ return DnsmasqManager(config)
+
diff --git a/cobbler/modules/manage_isc.py b/cobbler/modules/manage_isc.py
new file mode 100644
index 0000000..c398270
--- /dev/null
+++ b/cobbler/modules/manage_isc.py
@@ -0,0 +1,252 @@
+"""
+This is some of the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+John Eckersberg <jeckersb@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+import popen2
+from shlex import shlex
+
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+
+def register():
+ """
+ The mandatory cobbler module registration hook.
+ """
+ return "manage"
+
+
+class IscManager:
+
+ def what(self):
+ return "isc_and_bind"
+
+ def __init__(self,config,verbose=False):
+ """
+ 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)
+
+ def write_dhcp_lease(self,port,host,ip,mac):
+ """
+ Use DHCP's API to create a DHCP entry in the
+ /var/lib/dhcpd/dhcpd.leases file
+ #Code from http://svn.osgdc.org/browse/kusu/kusu
+ # /trunk/src/kits/base/packages/kusu-base-installer/lib/kusu/nodefun.py?r=3025
+ # FIXME: should use subprocess
+ """
+ try:
+ fromchild, tochild = popen2.popen2(self.settings.omshell_bin)
+ tochild.write("port %s\n" % port)
+ tochild.flush()
+ tochild.write("connect\n")
+ tochild.flush()
+ tochild.write("new host\n")
+ tochild.flush()
+ tochild.write('set name = \"%s\"\n' % host)
+ tochild.flush()
+ tochild.write("set ip-address = %s\n" % ip)
+ tochild.flush()
+ tochild.write("set hardware-address = %s\n" % mac.lower())
+ tochild.flush()
+ tochild.write("set hardware-type = 1\n")
+ tochild.flush()
+ tochild.write("create\n")
+ tochild.flush()
+ tochild.close()
+ fromchild.close()
+ except IOError:
+ # FIXME: just catch 32 (broken pipe) and show a warning
+ pass
+
+ def remove_dhcp_lease(self,port,host):
+ """
+ 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)
+ try:
+ tochild.write("port %s\n" % port)
+ tochild.flush()
+ tochild.write("connect\n")
+ tochild.flush()
+ tochild.write("new host\n")
+ tochild.flush()
+ tochild.write('set name = \"%s\"\n' % host)
+ tochild.flush()
+ tochild.write("open\n") # opens register with host information
+ tochild.flush()
+ tochild.write("remove\n")
+ tochild.flush()
+ tochild.close()
+ fromchild.close()
+ except IOError:
+ # FIXME: convert this to subprocess.
+ # FIXME: catch specific errors only (32/broken pipe)
+ pass
+
+ def write_dhcp_file(self):
+ """
+ DHCP files are written when manage_dhcp is set in
+ /var/lib/cobbler/settings.
+ """
+
+ settings_file = self.settings.dhcpd_conf
+ template_file = "/etc/cobbler/dhcp.template"
+ mode = self.settings.manage_dhcp_mode.lower()
+
+ try:
+ f2 = open(template_file,"r")
+ except:
+ raise CX(_("error writing template to file: %s") % template_file)
+ template_data = ""
+ template_data = f2.read()
+ f2.close()
+
+ # build each per-system definition
+ # as configured, this only works for ISC, patches accepted
+ # from those that care about Itanium. elilo seems to be unmaintained
+ # so additional maintaince in other areas may be required to keep
+ # this working.
+
+ elilo = os.path.basename(self.settings.bootloaders["ia64"])
+
+ system_definitions = {}
+ counter = 0
+
+
+ # Clean system definitions in /var/lib/dhcpd/dhcpd.leases just in
+ # case to avoid conflicts with the hosts we're defining and to clean
+ # possible removed hosts (only if using OMAPI)
+ if self.settings.omapi_enabled and self.settings.omapi_port:
+ if os.path.exists("/var/lib/dhcpd/dhcpd.leases"):
+ file = open('/var/lib/dhcpd/dhcpd.leases')
+ item = shlex(file)
+ while 1:
+ elem = item.get_token()
+ if not elem:
+ break
+ if elem == 'host':
+ hostremove = item.get_token()
+ self.remove_dhcp_lease(self.settings.omapi_port,hostremove)
+
+ # we used to just loop through each system, but now we must loop
+ # through each network interface of each system.
+
+ for system in self.systems:
+ profile = system.get_conceptual_parent()
+ distro = profile.get_conceptual_parent()
+ for (name, interface) in system.interfaces.iteritems():
+
+ mac = interface["mac_address"]
+ ip = interface["ip_address"]
+ host = interface["hostname"]
+
+ if mac is None or mac == "":
+ # can't write a DHCP entry for this system
+ continue
+
+ counter = counter + 1
+ systxt = ""
+
+
+ # the label the entry after the hostname if possible
+ if host is not None and host != "":
+ systxt = "\nhost %s {\n" % host
+ if self.settings.isc_set_host_name:
+ systxt = systxt + " option host-name = \"%s\";\n" % host
+ else:
+ systxt = "\nhost generic%d {\n" % counter
+
+ if distro.arch == "ia64":
+ # can't use pxelinux.0 anymore
+ systxt = systxt + " filename \"/%s\";\n" % elilo
+ systxt = systxt + " hardware ethernet %s;\n" % mac
+ if ip is not None and ip != "":
+ systxt = systxt + " fixed-address %s;\n" % ip
+ systxt = systxt + "}\n"
+
+ # If we have all values defined and we're using omapi,
+ # we will just create entries dinamically into DHCPD
+ # without requiring a restart (but file will be written
+ # as usual for having it working after restart)
+
+ if ip is not None and ip != "":
+ if mac is not None and mac != "":
+ if host is not None and host != "":
+ if self.settings.omapi_enabled and self.settings.omapi_port:
+ self.remove_dhcp_lease(self.settings.omapi_port,host)
+ self.write_dhcp_lease(self.settings.omapi_port,host,ip,mac)
+
+ dhcp_tag = interface["dhcp_tag"]
+ if dhcp_tag == "":
+ dhcp_tag = "default"
+
+ if not system_definitions.has_key(dhcp_tag):
+ system_definitions[dhcp_tag] = ""
+ system_definitions[dhcp_tag] = system_definitions[dhcp_tag] + systxt
+
+ # we are now done with the looping through each interface of each system
+
+ metadata = {
+ "omapi_enabled" : self.settings.omapi_enabled,
+ "omapi_port" : self.settings.omapi_port,
+ "insert_cobbler_system_definitions" : system_definitions.get("default",""),
+ "date" : time.asctime(time.gmtime()),
+ "cobbler_server" : self.settings.server,
+ "next_server" : self.settings.next_server,
+ "elilo" : elilo
+ }
+
+ # now add in other DHCP expansions that are not tagged with "default"
+ for x in system_definitions.keys():
+ if x == "default":
+ continue
+ metadata["insert_cobbler_system_definitions_%s" % x] = system_definitions[x]
+
+ self.templar.render(template_data, metadata, settings_file, None)
+
+ def regen_ethers(self):
+ pass # ISC/BIND do not use this
+
+
+def get_manager(config):
+ return IscManager(config)
+
diff --git a/cobbler/modules/serializer_shelve.py b/cobbler/modules/serializer_shelve.py
index 246a92a..09495dd 100644
--- a/cobbler/modules/serializer_shelve.py
+++ b/cobbler/modules/serializer_shelve.py
@@ -24,7 +24,7 @@ import os
import sys
import glob
import traceback
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import os
import shelve
import time
diff --git a/cobbler/modules/serializer_yaml.py b/cobbler/modules/serializer_yaml.py
index 6425bd1..c4bd359 100644
--- a/cobbler/modules/serializer_yaml.py
+++ b/cobbler/modules/serializer_yaml.py
@@ -21,7 +21,7 @@ plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler" % plib
sys.path.insert(0, mod_path)
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import utils
import yaml # Howell-Clark version
import cexceptions
@@ -66,7 +66,10 @@ def get_filename(collection_type):
ending = collection_type
if not ending.endswith("s"):
ending = ending + "s"
- return "/var/lib/cobbler/%s" % ending
+ if ending != "settings":
+ return "/var/lib/cobbler/%s" % ending
+ else:
+ return "/etc/cobbler/settings"
def deserialize_raw(collection_type):
filename = get_filename(collection_type)
diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py
new file mode 100644
index 0000000..a438583
--- /dev/null
+++ b/cobbler/pxegen.py
@@ -0,0 +1,324 @@
+"""
+Builds out filesystem trees/data based on the object tree.
+This is the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+
+class PXEGen:
+ """
+ Handles building out PXE stuff
+ """
+
+ def __init__(self,config):
+ """
+ Constructor
+ """
+ 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.bootloc = utils.tftpboot_location()
+
+ def copy_bootloaders(self):
+ """
+ Copy bootloaders to the configured tftpboot directory
+ NOTE: we support different arch's if defined in
+ /etc/cobbler/settings.
+ """
+ for loader in self.settings.bootloaders.keys():
+ path = self.settings.bootloaders[loader]
+ newname = os.path.basename(path)
+ destpath = os.path.join(self.bootloc, newname)
+ utils.copyfile(path, destpath)
+ utils.copyfile("/var/lib/cobbler/menu.c32", os.path.join(self.bootloc, "menu.c32"))
+
+ # Copy memtest to tftpboot if package is installed on system
+ for memtest in glob.glob('/boot/memtest*'):
+ base = os.path.basename(memtest)
+ utils.copyfile(memtest,os.path.join(self.bootloc,"images",base))
+
+ def copy_distros(self):
+ """
+ A distro is a kernel and an initrd. Copy all of them and error
+ out if any files are missing. The conf file was correct if built
+ via the CLI or API, though it's possible files have been moved
+ since or perhaps they reference NFS directories that are no longer
+ mounted.
+
+ NOTE: this has to be done for both tftp and http methods
+ """
+ # copy is a 4-letter word but tftpboot runs chroot, thus it's required.
+ for d in self.distros:
+ self.copy_single_distro_files(d)
+
+ def copy_single_distro_files(self, d):
+ for dirtree in [self.bootloc, self.settings.webdir]:
+ distros = os.path.join(dirtree, "images")
+ distro_dir = os.path.join(distros,d.name)
+ utils.mkdir(distro_dir)
+ kernel = utils.find_kernel(d.kernel) # full path
+ initrd = utils.find_initrd(d.initrd) # full path
+ if kernel is None or not os.path.isfile(kernel):
+ raise CX(_("kernel not found: %(file)s, distro: %(distro)s") % { "file" : d.kernel, "distro" : d.name })
+ if initrd is None or not os.path.isfile(initrd):
+ raise CX(_("initrd not found: %(file)s, distro: %(distro)s") % { "file" : d.initrd, "distro" : d.name })
+ b_kernel = os.path.basename(kernel)
+ b_initrd = os.path.basename(initrd)
+ if kernel.startswith(dirtree):
+ utils.linkfile(kernel, os.path.join(distro_dir, b_kernel))
+ else:
+ utils.copyfile(kernel, os.path.join(distro_dir, b_kernel))
+ if initrd.startswith(dirtree):
+ utils.linkfile(initrd, os.path.join(distro_dir, b_initrd))
+ else:
+ utils.copyfile(initrd, os.path.join(distro_dir, b_initrd))
+
+ def write_all_system_files(self,system):
+
+ profile = system.get_conceptual_parent()
+ if profile is None:
+ raise CX(_("system %(system)s references a missing profile %(profile)s") % { "system" : system.name, "profile" : system.profile})
+ distro = profile.get_conceptual_parent()
+ if distro is None:
+ raise CX(_("profile %(profile)s references a missing distro %(distro)s") % { "profile" : system.profile, "distro" : profile.distro})
+
+ # this used to just generate a single PXE config file, but now must
+ # generate one record for each described NIC ...
+
+ counter = 0
+ for (name,interface) in system.interfaces.iteritems():
+
+ ip = interface["ip_address"]
+
+ f1 = utils.get_config_filename(system,interface=name)
+
+ # for tftp only ...
+ if distro.arch in [ "x86", "x86_64", "standard"]:
+ # pxelinux wants a file named $name under pxelinux.cfg
+ f2 = os.path.join(self.bootloc, "pxelinux.cfg", f1)
+ if distro.arch == "ia64":
+ # elilo expects files to be named "$name.conf" in the root
+ # and can not do files based on the MAC address
+ if ip is not None and ip != "":
+ print _("Warning: Itanium system object (%s) needs an IP address to PXE") % system.name
+
+ filename = "%s.conf" % utils.get_config_filename(system,interface=name)
+ f2 = os.path.join(self.bootloc, filename)
+
+ f3 = os.path.join(self.settings.webdir, "systems", f1)
+
+ if system.netboot_enabled and system.is_pxe_supported():
+ if distro.arch in [ "x86", "x86_64", "standard"]:
+ self.write_pxe_file(f2,system,profile,distro,False)
+ if distro.arch == "ia64":
+ self.write_pxe_file(f2,system,profile,distro,True)
+ else:
+ # ensure the file doesn't exist
+ utils.rmfile(f2)
+
+ counter = counter + 1
+
+
+ def make_pxe_menu(self):
+ # only do this if there is NOT a system named default.
+ default = self.systems.find(name="default")
+ if default is not None:
+ return
+
+ fname = os.path.join(self.bootloc, "pxelinux.cfg", "default")
+
+ # read the default template file
+ template_src = open("/etc/cobbler/pxedefault.template")
+ template_data = template_src.read()
+
+ # 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)
+
+ # build out the menu entries
+ pxe_menu_items = ""
+ for profile in profile_list:
+ distro = profile.get_conceptual_parent()
+ # xen distros can be ruled out as they won't boot
+ if distro.name.find("-xen") != -1:
+ continue
+ contents = self.write_pxe_file(None,None,profile,distro,False,include_header=False)
+ if contents is not None:
+ pxe_menu_items = pxe_menu_items + contents + "\n"
+
+ # if we have any memtest files in images, make entries for them
+ # after we list the profiles
+ memtests = glob.glob(self.bootloc + "/images/memtest*")
+ if len(memtests) > 0:
+ pxe_menu_items = pxe_menu_items + "\n\n"
+ for memtest in glob.glob(self.bootloc + '/images/memtest*'):
+ base = os.path.basename(memtest)
+ contents = self.write_memtest_pxe("/images/%s" % base)
+ pxe_menu_items = pxe_menu_items + contents + "\n"
+
+ # save the template.
+ metadata = { "pxe_menu_items" : pxe_menu_items }
+ outfile = os.path.join(self.bootloc, "pxelinux.cfg", "default")
+ self.templar.render(template_data, metadata, outfile, None)
+ template_src.close()
+
+ def write_memtest_pxe(self,filename):
+ """
+ Write a configuration file for memtest
+ """
+
+ # just some random variables
+ template = None
+ metadata = {}
+ buffer = ""
+
+ template = "/etc/cobbler/pxeprofile.template"
+
+ # store variables for templating
+ metadata["menu_label"] = "MENU LABEL %s" % os.path.basename(filename)
+ metadata["profile_name"] = os.path.basename(filename)
+ metadata["kernel_path"] = "/images/%s" % os.path.basename(filename)
+ metadata["initrd_path"] = ""
+ metadata["append_line"] = ""
+
+ # get the template
+ template_fh = open(template)
+ template_data = template_fh.read()
+ template_fh.close()
+
+ # return results
+ buffer = self.templar.render(template_data, metadata, None)
+ return buffer
+
+
+
+ def write_pxe_file(self,filename,system,profile,distro,is_ia64, include_header=True):
+ """
+ Write a configuration file for the boot loader(s).
+ More system-specific configuration may come in later, if so
+ that would appear inside the system object in api.py
+
+ NOTE: relevant to tftp only
+ """
+
+ # ---
+ # system might have netboot_enabled set to False (see item_system.py), if so,
+ # don't do anything else and flag the error condition.
+ if system is not None and not system.netboot_enabled:
+ return None
+
+ # ---
+ # just some random variables
+ template = None
+ metadata = {}
+ buffer = ""
+
+ # ---
+ # find kernel and initrd
+ kernel_path = os.path.join("/images",distro.name,os.path.basename(distro.kernel))
+ 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"]
+
+ # ---
+ # choose a template
+ if system is None:
+ template = "/etc/cobbler/pxeprofile.template"
+ elif not is_ia64:
+ template = "/etc/cobbler/pxesystem.template"
+ else:
+ template = "/etc/cobbler/pxesystem_ia64.template"
+
+ # now build the kernel command line
+ if system is not None:
+ blended = utils.blender(self.api, True, system)
+ else:
+ blended = utils.blender(self.api, True,profile)
+ kopts = blended["kernel_options"]
+
+ # generate the append line
+ append_line = "append %s" % utils.hash_to_string(kopts)
+ if not is_ia64:
+ append_line = "%s initrd=%s" % (append_line, initrd_path)
+ 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 != "":
+
+ 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:
+ kickstart_path = "http://%s/cblr/svc/op/ks/profile/%s" % (blended["http_server"], profile.name)
+
+ if distro.breed is None or distro.breed == "redhat":
+ append_line = "%s ks=%s" % (append_line, kickstart_path)
+ elif distro.breed == "suse":
+ append_line = "%s autoyast=%s" % (append_line, kickstart_path)
+ elif distro.breed == "debian":
+ append_line = "%s auto=true url=%s" % (append_line, kickstart_path)
+ append_line = append_line.replace("ksdevice","interface")
+
+ # store variables for templating
+ metadata["menu_label"] = ""
+ if not is_ia64 and system is None:
+ metadata["menu_label"] = "MENU LABEL %s" % profile.name
+ metadata["profile_name"] = profile.name
+ metadata["kernel_path"] = kernel_path
+ metadata["initrd_path"] = initrd_path
+ metadata["append_line"] = append_line
+
+ # get the template
+ template_fh = open(template)
+ template_data = template_fh.read()
+ template_fh.close()
+
+ # save file and/or return results, depending on how called.
+ buffer = self.templar.render(template_data, metadata, None)
+ if filename is not None:
+ fd = open(filename, "w")
+ fd.write(buffer)
+ fd.close()
+ return buffer
+
+
+
+
diff --git a/cobbler/remote.py b/cobbler/remote.py
index 5131323..1a414f3 100644
--- a/cobbler/remote.py
+++ b/cobbler/remote.py
@@ -24,6 +24,7 @@ import random
import base64
import string
import traceback
+import glob
import api as cobbler_api
import utils
@@ -64,6 +65,11 @@ class CobblerXMLRPCInterface:
def ping(self):
return True
+ def update(self,token=None):
+ # ensure the config is up to date as of /now/
+ self.api.deserialize()
+ return True
+
def get_user_from_token(self,token):
if not TOKEN_CACHE.has_key(token):
raise CX(_("invalid token: %s") % token)
@@ -158,9 +164,36 @@ class CobblerXMLRPCInterface:
return self._fix_none(data)
+ def get_kickstart_templates(self,token):
+ """
+ Returns all of the kickstarts that are in use by the system.
+ """
+ self.log("get_kickstart_templates",token=token)
+ self.check_access(token, "get_kickstart_templates")
+ return utils.get_kickstart_templates(self.api)
+
+ def is_kickstart_in_use(self,ks,token):
+ self.log("is_kickstart_in_use",token=token)
+ self.check_access(token, "is_kickstart_in_use")
+ for x in self.api.profiles():
+ if x.kickstart is not None and x.kickstart == ks:
+ return True
+ for x in self.api.systems():
+ if x.kickstart is not None and x.kickstart == ks:
+ return True
+ return False
+
+ def generate_kickstart(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None):
+ 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):
"""
- Return the contents of /var/lib/cobbler/settings, which is a hash.
+ Return the contents of /etc/cobbler/settings, which is a hash.
"""
self.log("get_settings",token=token)
return self.__get_all("settings")
@@ -184,7 +217,7 @@ class CobblerXMLRPCInterface:
self.api.add_system(system)
- def register_mac(self,mac,token=None):
+ def register_mac(self,mac,profile,token=None):
"""
If allow_cgi_register_mac is enabled in settings, this allows
kickstarts to add new system records for per-profile-provisioned
@@ -193,18 +226,35 @@ class CobblerXMLRPCInterface:
READ: https://fedorahosted.org/cobbler/wiki/AutoRegistration
"""
- if not self.api.settings().allow_cgi_mac_registration:
+ if mac is None:
+ # don't go further if not being called by anaconda
return 1
- system = self.api.find_system(mac_address=mac)
- if system is not None:
+ if not self.api.settings().register_new_installs:
+ # must be enabled in settings
return 2
- obj = server.new_system(token)
+ 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)
+ obj = self.api.new_system()
obj.set_profile(profile)
- obj.set_name(mac.replace(":","_"))
+ name = mac.replace(":","_")
+ obj.set_name(name)
obj.set_mac_address(mac, "intf0")
- systems.add(obj,save=True)
+ obj.set_netboot_enabled(False)
+ self.api.add_system(obj)
return 0
def disable_netboot(self,name,token=None):
@@ -230,26 +280,28 @@ class CobblerXMLRPCInterface:
systems.add(obj,save=True,with_triggers=False,with_sync=False,quick_pxe_update=True)
return True
- def run_post_install_triggers(self,name,token=None):
+ def run_install_triggers(self,mode,objtype,name,ip,token=None):
+
"""
- This is a feature used to run the post install trigger.
- It passes the system named "name" to the trigger. Disabled by default as
- this requires public API access and is technically a read-write operation.
+ This is a feature used to run the pre/post install triggers.
+ See CobblerTriggers on Wiki for details
"""
- self.log("run_post_install_triggers",token=token)
- # used by postinstalltrigger.cgi
- self.api.clear()
- self.api.deserialize()
- if not self.api.settings().run_post_install_trigger:
- # feature disabled!
+ self.log("run_install_triggers",token=token)
+
+ if mode != "pre" and mode != "post":
return False
- systems = self.api.systems()
- obj = systems.find(name=name)
- if obj == None:
- # system not found!
+ if objtype != "system" and objtype !="profile":
return False
- utils.run_triggers(obj, "/var/lib/cobbler/triggers/install/post/*")
+
+ # the trigger script is called with name,mac, and ip as arguments 1,2, and 3
+ # we do not do API lookups here because they are rather expensive at install
+ # 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])
+
+
return True
def _refresh(self):
@@ -418,25 +470,13 @@ class CobblerXMLRPCInterface:
def get_random_mac(self,token=None):
"""
- Generate a random MAC address.
- from xend/server/netif.py
- Generate a random MAC address.
- Uses OUI 00-16-3E, allocated to
- Xensource, Inc. Last 3 fields are random.
- return: MAC address string
+ Wrapper for utils.get_random_mac
+
+ Used in the webui
"""
self.log("get_random_mac",token=None)
self._refresh()
- mac = [ 0x00, 0x16, 0x3e,
- random.randint(0x00, 0x7f),
- random.randint(0x00, 0xff),
- random.randint(0x00, 0xff) ]
- mac = ':'.join(map(lambda x: "%02x" % x, mac))
- systems = self.api.systems()
- while ( systems.find(mac_address=mac) ):
- mac = self.get_random_mac()
-
- return mac
+ return utils.get_random_mac(self.api)
def _fix_none(self,data):
"""
@@ -561,10 +601,6 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
FIXME: currently looks for users in /etc/cobbler/auth.conf
Would be very nice to allow for PAM and/or just Kerberos.
"""
- if not self.auth_enabled and input_user == "<system>":
- return True
- if self.auth_enabled and input_user == "<system>":
- return False
return self.api.authenticate(input_user,input_password)
def __validate_token(self,token):
@@ -579,11 +615,12 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
self.__invalidate_expired_tokens()
self.__invalidate_expired_objects()
- if not self.auth_enabled:
- user = self.get_user_from_token(token)
- if user == "<system>":
- self.token_cache[token] = (time.time(), user) # update to prevent timeout
- return True
+ #if not self.auth_enabled:
+ # user = self.get_user_from_token(token)
+ # # old stuff, preserving for future usage
+ # # if user == "<system>":
+ # # self.token_cache[token] = (time.time(), user) # update to prevent timeout
+ # # return True
if self.token_cache.has_key(token):
user = self.get_user_from_token(token)
@@ -596,12 +633,54 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
self.log("invalid token",token=token)
raise CX(_("invalid token: %s" % token))
+ def __name_to_object(self,resource,name):
+ if resource.find("distro") != -1:
+ return self.api.find_distro(name)
+ if resource.find("profile") != -1:
+ return self.api.find_profile(name)
+ if resource.find("system") != -1:
+ return self.api.find_system(name)
+ if resource.find("repo") != -1:
+ return self.api.find_repo(name)
+ return None
+
+ def check_access_no_fail(self,token,resource,arg1=None,arg2=None):
+ """
+ This is called by the WUI to decide whether an element
+ is editable or not. It differs form check_access in that
+ it is supposed to /not/ log the access checks (TBA) and does
+ not raise exceptions.
+ """
+
+ need_remap = False
+ for x in [ "distro", "profile", "system", "repo" ]:
+ if arg1 is not None and resource.find(x) != -1:
+ need_remap = True
+ break
+
+ if need_remap:
+ # we're called with an object name, but need an object
+ arg1 = self.__name_to_object(resource,arg1)
+
+ try:
+ self.check_access(token,resource,arg1,arg2)
+ return True
+ except:
+ utils.log_exc(self.logger)
+ return False
+
def check_access(self,token,resource,arg1=None,arg2=None):
validated = self.__validate_token(token)
+ user = self.get_user_from_token(token)
if not self.auth_enabled:
+ # for public read-only XMLRPC, permit access
+ self.log("permitting read-only access")
return True
- return self.__authorize(token,resource,arg1,arg2)
-
+ rc = self.__authorize(token,resource,arg1,arg2)
+ self.log("authorization result: %s" % rc)
+ if not rc:
+ raise CX(_("authorization failure for user %s" % user))
+ return rc
def login(self,login_user,login_password):
"""
@@ -621,7 +700,11 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
def __authorize(self,token,resource,arg1=None,arg2=None):
user = self.get_user_from_token(token)
- if self.api.authorize(user,resource,arg1,arg2):
+ args = [ resource, arg1, arg2 ]
+ self.log("calling authorize for resource %s" % args, user=user)
+
+ rc = self.api.authorize(user,resource,arg1,arg2)
+ if rc:
return True
else:
raise CX(_("user does not have access to resource: %s") % resource)
@@ -664,6 +747,16 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
return self.object_cache[object_id][0]
raise CX(_("No such object for ID: %s") % object_id)
+ def sync(self,token):
+ """
+ Run sync code, which should complete before XMLRPC timeout. We can't
+ do reposync this way. Would be nice to send output over AJAX/other
+ later.
+ """
+ self.log("sync",token=token)
+ self.check_access(token,"sync")
+ return self.api.sync()
+
def new_distro(self,token):
"""
Creates a new (unconfigured) distro object. It works something like
@@ -767,41 +860,56 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
found = self.api.repos().find(name)
return self.__store_object(found)
- def save_distro(self,object_id,token):
+ def save_distro(self,object_id,token,editmode="bypass"):
"""
Saves a newly created or modified distro object to disk.
"""
self.log("save_distro",object_id=object_id,token=token)
- self.check_access(token,"save_distro")
obj = self.__get_object(object_id)
- return self.api.distros().add(obj,save=True)
+ self.check_access(token,"save_distro",obj)
+ if editmode == "new":
+ return self.api.distros().add(obj,save=True,check_for_duplicate_names=True)
+ else:
+ return self.api.distros().add(obj,save=True)
- def save_profile(self,object_id,token):
+ def save_profile(self,object_id,token,editmode="bypass"):
"""
Saves a newly created or modified profile object to disk.
"""
self.log("save_profile",token=token,object_id=object_id)
- self.check_access(token,"save_profile")
obj = self.__get_object(object_id)
- return self.api.profiles().add(obj,save=True)
+ self.check_access(token,"save_profile",obj)
+ if editmode == "new":
+ return self.api.profiles().add(obj,save=True,check_for_duplicate_names=True)
+ else:
+ return self.api.profiles().add(obj,save=True)
- def save_system(self,object_id,token):
+ def save_system(self,object_id,token,editmode="bypass"):
"""
Saves a newly created or modified system object to disk.
"""
self.log("save_system",token=token,object_id=object_id)
- self.check_access(token,"save_system")
obj = self.__get_object(object_id)
- return self.api.systems().add(obj,save=True)
+ self.check_access(token,"save_system",obj)
+ if editmode == "new":
+ return self.api.systems().add(obj,save=True,check_for_duplicate_names=True,check_for_duplicate_netinfo=True)
+ elif editmode == "edit":
+ return self.api.systems().add(obj,save=True,check_for_duplicate_netinfo=True)
+ else:
+ return self.api.systems().add(obj,save=True)
+
- def save_repo(self,object_id,token=None):
+ def save_repo(self,object_id,token=None,editmode="bypass"):
"""
Saves a newly created or modified repo object to disk.
"""
self.log("save_repo",object_id=object_id,token=token)
- self.check_access(token,"save_repo")
obj = self.__get_object(object_id)
- return self.api.repos().add(obj,save=True)
+ self.check_access(token,"save_repo",obj)
+ if editmode == "new":
+ return self.api.repos().add(obj,save=True,check_for_duplicate_names=True)
+ else:
+ return self.api.repos().add(obj,save=True)
def copy_distro(self,object_id,newname,token=None):
"""
@@ -874,8 +982,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Allows modification of certain attributes on newly created or
existing distro object handle.
"""
- self.check_access(token, "modify_distro", attribute, arg)
obj = self.__get_object(object_id)
+ self.check_access(token, "modify_distro", obj, attribute)
return self.__call_method(obj, attribute, arg)
def modify_profile(self,object_id,attribute,arg,token):
@@ -883,8 +991,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Allows modification of certain attributes on newly created or
existing profile object handle.
"""
- self.check_access(token, "modify_profile", attribute, arg)
obj = self.__get_object(object_id)
+ self.check_access(token, "modify_profile", obj, attribute)
return self.__call_method(obj, attribute, arg)
def modify_system(self,object_id,attribute,arg,token):
@@ -892,8 +1000,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Allows modification of certain attributes on newly created or
existing system object handle.
"""
- self.check_access(token, "modify_system", attribute, arg)
obj = self.__get_object(object_id)
+ self.check_access(token, "modify_system", obj, attribute)
return self.__call_method(obj, attribute, arg)
def modify_repo(self,object_id,attribute,arg,token):
@@ -901,8 +1009,8 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Allows modification of certain attributes on newly created or
existing repo object handle.
"""
- self.check_access(token, "modify_repo", attribute, arg)
obj = self.__get_object(object_id)
+ self.check_access(token, "modify_repo", obj, attribute)
return self.__call_method(obj, attribute, arg)
def remove_distro(self,name,token,recursive=1):
@@ -910,7 +1018,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
Deletes a distro from a collection. Note that this just requires the name
of the distro, not a handle.
"""
- self.log("remove_distro",name=name,token=token)
+ self.log("remove_distro (%s)" % recursive,name=name,token=token)
self.check_access(token, "remove_distro", name)
rc = self.api._config.distros().remove(name,recursive=True)
return rc
@@ -918,74 +1026,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
def remove_profile(self,name,token,recursive=1):
"""
Deletes a profile from a collection. Note that this just requires the name
- of the profile, not a handle.
"""
- self.log("remove_profile",name=name,token=token)
- self.check_access(token, "remove_profile", name)
- rc = self.api._config.profiles().remove(name,recursive=True)
- return rc
-
- def remove_system(self,name,token):
- """
- Deletes a system from a collection. Note that this just requires the name
- of the system, not a handle.
- """
- self.log("remove_system",name=name,token=token)
- self.check_access(token, "remove_system", name)
- rc = self.api._config.systems().remove(name)
- return rc
-
- def remove_repo(self,name,token):
- """
- Deletes a repo from a collection. Note that this just requires the name
- of the repo, not a handle.
- """
- self.log("remove_repo",name=name,token=token)
- self.check_access(token, "remove_repo", name)
- rc = self.api._config.repos().remove(name)
- return rc
-
- def sync(self,token):
- """
- Applies changes in Cobbler to the filesystem.
- Editing a leaf-node object (like a system) does not require
- this, but if updating a upper-level object or a kickstart file,
- running sync at the end of operations is a good idea. A typical
- cobbler sync may take anywhere between a few seconds and several
- minutes, so user interfaces should be programmed accordingly.
- Future versions of cobbler may understand how to do a cascade sync
- on object edits making explicit calls to sync redundant.
- """
- self.log("sync",token=token)
- self.check_access(token, "sync")
- return self.api.sync()
-
- def reposync(self,repos=[],token=None):
- """
- Updates one or more mirrored yum repositories.
- reposync is very slow and probably should not be used
- through the XMLRPC API, setting up reposync on nightly cron is better.
- """
- self.log("reposync",token=token,name=repos)
- self.check_access(token, "reposync", repos)
- return self.api.reposync(repos)
-
- def import_tree(self,mirror_url,mirror_name,network_root=None,token=None):
- """
- I'm exposing this in the XMLRPC API for consistancy but as this
- can be a very long running operation usage is /not/ recommended.
- It would be better to use the CLI. See documentation in api.py.
- This command may be removed from the API in a future release.
- """
- self.log("import_tree",name=mirror_name,token=token)
- self.check_access(token, "import_tree")
- return self.api.import_tree(mirror_url,mirror_name,network_root)
-
- def get_kickstart_templates(self,token):
- """
- Returns all of the kickstarts that are in use by the system.
- """
- self.log("get_kickstart_templates",token=token)
self.check_access(token, "get_kickstart_templates")
files = {}
for x in self.api.profiles():
@@ -998,7 +1039,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
def read_or_write_kickstart_template(self,kickstart_file,is_read,new_data,token):
- """
+ """
Allows the WebUI to be used as a kickstart file editor. For security
reasons we will only allow kickstart files to be edited if they reside in
/var/lib/cobbler/kickstarts/ or /etc/cobbler. This limits the damage
@@ -1028,9 +1069,16 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface):
fileh.close()
return data
else:
- fileh = open(kickstart_file,"w+")
- fileh.write(new_data)
- fileh.close()
+ if new_data == -1:
+ # delete requested
+ if not self.is_kickstart_in_use(kickstart_file,token):
+ os.remove(kickstart_file)
+ else:
+ raise CX(_("attempt to delete in-use file"))
+ else:
+ fileh = open(kickstart_file,"w+")
+ fileh.write(new_data)
+ fileh.close()
return True
diff --git a/cobbler/serializable.py b/cobbler/serializable.py
index ba8f015..ad9e64e 100644
--- a/cobbler/serializable.py
+++ b/cobbler/serializable.py
@@ -13,7 +13,7 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import exceptions
class Serializable:
diff --git a/cobbler/serializer.py b/cobbler/serializer.py
index ae9f18c..ac98412 100644
--- a/cobbler/serializer.py
+++ b/cobbler/serializer.py
@@ -15,7 +15,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import errno
import os
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
import fcntl
from cexceptions import *
@@ -59,10 +59,8 @@ def serialize_item(collection, item):
storage_module = __get_storage_module(collection.collection_type())
save_fn = getattr(storage_module, "serialize_item", None)
if save_fn is None:
- # print "DEBUG: WARNING: full serializer"
rc = storage_module.serialize(collection)
else:
- # print "DEBUG: partial serializer"
rc = save_fn(collection,item)
__release_lock()
return rc
@@ -75,10 +73,8 @@ def serialize_delete(collection, item):
storage_module = __get_storage_module(collection.collection_type())
delete_fn = getattr(storage_module, "serialize_delete", None)
if delete_fn is None:
- # print "DEBUG: full delete"
rc = storage_module.serialize(collection)
else:
- # print "DEBUG: partial delete"
rc = delete_fn(collection,item)
__release_lock()
return rc
diff --git a/cobbler/services.py b/cobbler/services.py
new file mode 100644
index 0000000..1cb3864
--- /dev/null
+++ b/cobbler/services.py
@@ -0,0 +1,96 @@
+# Mod Python service functions for Cobbler's public interface
+# (aka cool stuff that works with wget)
+#
+# Copyright 2007 Albert P. Tobey <tobert@gmail.com>
+# additions: Michael DeHaan <mdehaan@redhat.com>
+#
+# This software may be freely redistributed under the terms of the GNU
+# general public license.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+import exceptions
+import xmlrpclib
+import os
+import traceback
+import string
+import sys
+import time
+
+def log_exc(apache):
+ """
+ Log active traceback to logfile.
+ """
+ (t, v, tb) = sys.exc_info()
+ apache.log_error("Exception occured: %s" % t )
+ apache.log_error("Exception value: %s" % v)
+ apache.log_error("Exception Info:\n%s" % string.join(traceback.format_list(traceback.extract_tb(tb))))
+
+class CobblerSvc(object):
+ """
+ Interesting mod python functions are all keyed off the parameter
+ mode, which defaults to index. All options are passed
+ as parameters into the function.
+ """
+ def __init__(self, server=None, apache=None):
+ self.server = server
+ self.apache = apache
+ self.remote = None
+
+ def __xmlrpc_setup(self):
+ """
+ Sets up the connection to the Cobbler XMLRPC server.
+ This is the version that does not require logins.
+ """
+ self.remote = xmlrpclib.Server(self.server, allow_none=True)
+ self.remote.update()
+
+ def modes(self):
+ """
+ Returns a list of methods in this object that can be run as web
+ modes.
+ """
+ retval = list()
+ for m in dir(self):
+ func = getattr( self, m )
+ if hasattr(func, 'exposed') and getattr(func,'exposed'):
+ retval.append(m)
+ return retval
+
+ def index(self,**args):
+ return "no mode specified"
+
+ def ks(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None,**rest):
+ """
+ Generate kickstart files...
+ """
+ self.__xmlrpc_setup()
+ data = self.remote.generate_kickstart(profile,system,REMOTE_ADDR,REMOTE_MAC)
+ return u"%s" % data
+
+ def trig(self,mode="?",profile=None,system=None,REMOTE_ADDR=None,**rest):
+ """
+ Hook to call install triggers.
+ """
+ self.__xmlrpc_setup()
+ ip = REMOTE_ADDR
+ if profile:
+ rc = self.remote.run_install_triggers(mode,"profile",profile,ip)
+ else:
+ rc = self.remote.run_install_triggers(mode,"system",system,ip)
+ return str(rc)
+
+ def nopxe(self,system=None,**rest):
+ self.__xmlrpc_setup()
+ return str(self.remote.disable_netboot(system))
+
+ # =======================================================
+ # list of functions that are callable via mod_python:
+ modes.exposed = False
+ index.exposed = True
+ ks.exposed = True
+ trig.exposed = True
+
+
diff --git a/cobbler/settings.py b/cobbler/settings.py
index 8c8be03..4670ab0 100644
--- a/cobbler/settings.py
+++ b/cobbler/settings.py
@@ -14,7 +14,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
import serializable
import utils
-from rhpl.translate import _, N_, textdomain, utf8
+from utils import _
TESTMODE = False
@@ -22,24 +22,36 @@ TESTMODE = False
# we need.
DEFAULTS = {
- "allow_cgi_mac_registration" : 0,
- "allow_cgi_profile_change" : 0,
+ "allow_duplicate_macs" : 0,
+ "allow_duplicate_ips" : 0,
+ "bind_bin" : "/usr/sbin/named",
"bootloaders" : {
"standard" : "/usr/lib/syslinux/pxelinux.0",
"ia64" : "/var/lib/cobbler/elilo-3.6-ia64.efi"
},
+ "cobbler_master" : '',
"default_kickstart" : "/etc/cobbler/default.ks",
"default_virt_bridge" : "xenbr0",
"default_virt_type" : "auto",
"default_virt_file_size" : "5",
"default_virt_ram" : "512",
+ "default_ownership" : "admin",
"dhcpd_conf" : "/etc/dhcpd.conf",
"dhcpd_bin" : "/usr/sbin/dhcpd",
"dnsmasq_bin" : "/usr/sbin/dnsmasq",
"dnsmasq_conf" : "/etc/dnsmasq.conf",
"httpd_bin" : "/usr/sbin/httpd",
"http_port" : "80",
- "kerberos_realm" : "example.org",
+ "isc_set_host_name" : 0,
+ "ldap_server" : "grimlock.devel.redhat.com",
+ "ldap_base_dn" : "DC=devel,DC=redhat,DC=com",
+ "ldap_port" : 389,
+ "ldap_tls" : "on",
+ "ldap_anonymous_bind" : 1,
+ "ldap_search_bind_dn" : '',
+ "ldap_search_passwd" : '',
+ "ldap_search_prefix" : 'uid=',
+ "kerberos_realm" : "EXAMPLE.COM",
"kernel_options" : {
"lang" : " ",
"text" : None,
@@ -47,18 +59,25 @@ DEFAULTS = {
},
"manage_dhcp" : 0,
"manage_dhcp_mode" : "isc",
+ "manage_dns" : 0,
+ "manage_forward_zones" : [],
+ "manage_reverse_zones" : [],
+ "named_conf" : "/etc/named.conf",
"next_server" : "127.0.0.1",
+ "omapi_enabled" : 0,
+ "omapi_port" : 647,
+ "omshell_bin" : "/usr/bin/omshell",
"pxe_just_once" : 0,
- "run_post_install_trigger" : 0,
+ "register_new_installs" : 0,
+ "run_install_triggers" : 1,
"server" : "127.0.0.1",
"snippetsdir" : "/var/lib/cobbler/snippets",
"syslog_port" : 25150,
- "tftpboot" : "/tftpboot",
"tftpd_bin" : "/usr/sbin/in.tftpd",
"tftpd_conf" : "/etc/xinetd.d/tftp",
"webdir" : "/var/www/cobbler",
"xmlrpc_port" : 25151,
- "xmlrpc_rw_enabled" : 0,
+ "xmlrpc_rw_enabled" : 1,
"xmlrpc_rw_port" : 25152,
"yum_post_install_mirror" : 1,
"yumdownloader_flags" : "--resolve"
@@ -101,7 +120,9 @@ class Settings(serializable.Serializable):
if datastruct is None:
print _("warning: not loading empty structure for %s") % self.filename()
return
+
self._attributes = datastruct
+
return self
def __getattr__(self,name):
@@ -119,11 +140,3 @@ class Settings(serializable.Serializable):
else:
raise AttributeError, name
-if __name__ == "__main__":
- # used to save a settings file to /var/lib/cobbler/settings, for purposes of
- # including a new updated settings file in the RPM without remembering how
- # to format lots of YAML.
- import yaml
- print yaml.dump(DEFAULTS)
-
-
diff --git a/cobbler/templar.py b/cobbler/templar.py
new file mode 100644
index 0000000..fa401b8
--- /dev/null
+++ b/cobbler/templar.py
@@ -0,0 +1,189 @@
+"""
+Cobbler uses Cheetah templates for lots of stuff, but there's
+some additional magic around that to deal with snippets/etc.
+(And it's not spelled wrong!)
+
+Copyright 2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import os
+import os.path
+import glob
+import utils
+from cexceptions import *
+from Cheetah.Template import Template
+
+class Templar:
+
+ def __init__(self,config):
+ """
+ Constructor
+ """
+ self.config = config
+ self.api = config.api
+ self.settings = config.settings()
+ self.cache = {}
+
+ def render(self, data_input, search_table, out_path, subject=None):
+ """
+ Render data_input back into a file.
+ data_input is either a string or a filename
+ search_table is a hash of metadata keys and values
+ out_path if not-none writes the results to a file
+ (though results are always returned)
+ subject is a profile or system object, if available (for snippet eval)
+ """
+
+ if type(data_input) != str:
+ raw_data = data_input.read()
+ else:
+ raw_data = data_input
+
+ # backward support for Cobbler's legacy (and slightly more readable)
+ # template syntax.
+ raw_data = raw_data.replace("TEMPLATE::","$")
+
+ # replace snippets with the proper Cheetah include directives prior to processing.
+ # see Wiki for full details on how snippets operate.
+ snippet_results = ""
+ for line in raw_data.split("\n"):
+ line = self.replace_snippets(line,subject)
+ snippet_results = "\n".join((snippet_results, line))
+ raw_data = snippet_results
+
+ # HACK: the ksmeta field may contain nfs://server:/mount in which
+ # case this is likely WRONG for kickstart, which needs the NFS
+ # directive instead. Do this to make the templates work.
+ newdata = ""
+ if search_table.has_key("tree") and search_table["tree"].startswith("nfs://"):
+ for line in data.split("\n"):
+ if line.find("--url") != -1 and line.find("url ") != -1:
+ rest = search_table["tree"][6:] # strip off "nfs://" part
+ try:
+ (server, dir) = rest.split(":",2)
+ except:
+ raise CX(_("Invalid syntax for NFS path given during import: %s" % search_table["tree"]))
+ line = "nfs --server %s --dir %s" % (server,dir)
+ # but put the URL part back in so koan can still see
+ # what the original value was
+ line = line + "\n" + "#url --url=%s" % search_table["tree"]
+ newdata = newdata + line + "\n"
+ raw_data = newdata
+
+ # tell Cheetah not to blow up if it can't find a symbol for something
+ raw_data = "#errorCatcher Echo\n" + raw_data
+
+ # now do full templating scan, where we will also templatify the snippet insertions
+ t = Template(source=raw_data, searchList=[search_table])
+ try:
+ data_out = str(t)
+ except:
+ print _("There appears to be an formatting error in the template file.")
+ print _("For completeness, the traceback from Cheetah has been included below.")
+ raise
+
+ # now apply some magic post-filtering that is used by cobbler import and some
+ # other places, but doesn't use Cheetah. Forcing folks to double escape
+ # things would be very unwelcome.
+
+ for x in search_table:
+ if type(search_table[x]) == str:
+ data_out = data_out.replace("@@%s@@" % x, search_table[x])
+
+ # remove leading newlines which apparently breaks AutoYAST ?
+ if data_out.startswith("\n"):
+ data_out = data_out.strip()
+
+ if out_path is not None:
+ utils.mkdir(os.path.dirname(out_path))
+ fd = open(out_path, "w+")
+ fd.write(data_out)
+ fd.close()
+
+ return data_out
+
+ def replace_snippets(self,line,subject):
+ """
+ Replace all SNIPPET:: syntaxes on a line with the
+ results of evaluating the snippet, taking care not
+ to replace tabs with spaces or anything like that
+ """
+ tokens = line.split(None)
+ for t in tokens:
+ if t.startswith("SNIPPET::"):
+ snippet_name = t.replace("SNIPPET::","")
+ line = line.replace(t,self.eval_snippet(snippet_name,subject))
+ return line
+
+ def eval_snippet(self,name,subject):
+ """
+ Replace SNIPPET::foo with contents of files:
+ Use /var/lib/cobbler/snippets/per_system/$name/$sysname
+ /var/lib/cobbler/snippets/per_profile/$name/$proname
+ /var/lib/cobbler/snippets/$name
+ in order... (first one wins)
+ """
+
+ sd = self.settings.snippetsdir
+ default_path = "%s/%s" % (sd,name)
+
+ if subject is None:
+ if os.path.exists(default_path):
+ return self.slurp(default_path)
+ else:
+ return self.slurp(None)
+
+
+ if subject.COLLECTION_TYPE == "system":
+ profile = self.api.find_profile(name=subject.profile)
+ sys_path = "%s/per_system/%s/%s" % (sd,name,subject.name)
+ pro_path = "%s/per_profile/%s/%s" % (sd,name,profile.name)
+ if os.path.exists(sys_path):
+ return self.slurp(sys_path)
+ elif os.path.exists(pro_path):
+ return self.slurp(pro_path)
+ elif os.path.exists(default_path):
+ return self.slurp(default_path)
+ else:
+ return self.slurp(None)
+
+ if subject.COLLECTION_TYPE == "profile":
+ pro_path = "%s/per_profile/%s/%s" % (sd,name,subject.name)
+ if os.path.exists(pro_path):
+ return self.slurp(pro_path)
+ elif os.path.exists(default_path):
+ return self.slurp(default_path)
+ else:
+ return self.slurp(None)
+
+ return self.slurp(None)
+
+ def slurp(self,filename):
+ """
+ Get the contents of a filename but if none is specified
+ just include some generic error text for the rendered
+ template.
+ """
+
+ if filename is None:
+ return "# error: no snippet data found"
+
+ # potentially eliminate a ton of system calls if syncing
+ # thousands of systems that use the same template
+ if self.cache.has_key(filename):
+ return self.cache[filename]
+
+ fd = open(filename,"r")
+ data = fd.read()
+ self.cache[filename] = data
+ fd.close()
+
+ return data
diff --git a/cobbler/utils.py b/cobbler/utils.py
index 069d440..de00150 100644
--- a/cobbler/utils.py
+++ b/cobbler/utils.py
@@ -17,12 +17,19 @@ import os
import re
import socket
import glob
+import random
import sub_process
import shutil
import string
import traceback
+import errno
+import logging
from cexceptions import *
-from rhpl.translate import _, N_, textdomain, utf8
+
+#placeholder for translation
+def _(foo):
+ return foo
+
MODULE_CACHE = {}
@@ -31,6 +38,18 @@ MODULE_CACHE = {}
_re_kernel = re.compile(r'vmlinuz(.*)')
_re_initrd = re.compile(r'initrd(.*).img')
+def setup_logger(name):
+ logger = logging.getLogger(name)
+ logger.setLevel(logging.INFO)
+ try:
+ ch = logging.FileHandler("/var/log/cobbler/cobbler.log")
+ except:
+ raise CX(_("No write permissions on log file. Are you root?"))
+ ch.setLevel(logging.INFO)
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(message)s")
+ ch.setFormatter(formatter)
+ logger.addHandler(ch)
+ return logger
def log_exc(logger):
"""
@@ -106,6 +125,26 @@ def is_mac(strdata):
return True
return False
+def get_random_mac(api_handle):
+ """
+ Generate a random MAC address.
+ from xend/server/netif.py
+ Generate a random MAC address.
+ Uses OUI 00-16-3E, allocated to
+ Xensource, Inc. Last 3 fields are random.
+ return: MAC address string
+ """
+ mac = [ 0x00, 0x16, 0x3e,
+ random.randint(0x00, 0x7f),
+ random.randint(0x00, 0xff),
+ random.randint(0x00, 0xff) ]
+ mac = ':'.join(map(lambda x: "%02x" % x, mac))
+ systems = api_handle.systems()
+ while ( systems.find(mac_address=mac) ):
+ mac = get_random_mac(api_handle)
+
+ return mac
+
def resolve_ip(strdata):
"""
@@ -230,6 +269,22 @@ def find_kickstart(url):
return url
return None
+def input_string_or_list(options,delim=","):
+ """
+ Accepts a delimited list of stuff or a list, but always returns a list.
+ """
+ if options is None or options == "delete":
+ return []
+ elif type(options) == list:
+ return options
+ elif type(options) == str:
+ tokens = options.split(delim)
+ if delim == ",":
+ tokens = [t.lstrip().rstrip() for t in tokens]
+ return tokens
+ else:
+ raise CX(_("invalid input type"))
+
def input_string_or_hash(options,delim=","):
"""
Older cobbler files stored configurations in a flat way, such that all values for strings.
@@ -240,7 +295,7 @@ def input_string_or_hash(options,delim=","):
if options == "<<inherit>>":
options = {}
- if options is None:
+ if options is None or options == "delete":
return (True, {})
elif type(options) == list:
raise CX(_("No idea what to do with list: %s") % options)
@@ -261,7 +316,7 @@ def input_string_or_hash(options,delim=","):
options.pop('',None)
return (True, options)
else:
- raise CX(_("Foreign options type"))
+ raise CX(_("invalid input type"))
def grab_tree(api_handle, obj):
"""
@@ -276,19 +331,14 @@ def grab_tree(api_handle, obj):
results.append(settings)
return results
-def blender(api_handle,remove_hashes, root_obj, blend_cache=None):
+def blender(api_handle,remove_hashes, root_obj):
"""
Combine all of the data in an object tree from the perspective
of that point on the tree, and produce a merged hash containing
consolidated data.
"""
- cache_enabled = False # FIXME: disabled for now as there a few bugs in this impl.
-
blend_key = "%s/%s/%s" % (root_obj.TYPE_NAME, root_obj.name, remove_hashes)
- if cache_enabled and blend_cache is not None:
- if blend_cache.has_key(blend_key):
- return blend_cache[blend_key]
settings = api_handle.settings()
tree = grab_tree(api_handle, root_obj)
@@ -334,8 +384,18 @@ def blender(api_handle,remove_hashes, root_obj, blend_cache=None):
if remove_hashes:
results = flatten(results)
- if cache_enabled and blend_cache is not None:
- blend_cache[blend_key] = results
+ # add in some variables for easier templating
+ # as these variables change based on object type
+ if results.has_key("interfaces"):
+ results["system_name"] = results["name"]
+ results["profile_name"] = results["profile"]
+ results["distro_name"] = results["distro"]
+ elif results.has_key("distro"):
+ results["profile_name"] = results["name"]
+ results["distro_name"] = results["distro"]
+ elif results.has_key("kernel"):
+ results["distro_name"] = results["name"]
+
return results
def flatten(data):
@@ -399,6 +459,24 @@ def __consolidate(node,results):
else:
results[field] = data_item
+ # now if we have any "!foo" results in the list, delete corresponding
+ # key entry "foo", and also the entry "!foo", allowing for removal
+ # of kernel options set in a distro later in a profile, etc.
+
+ hash_removals(results,"kernel_options")
+ hash_removals(results,"ks_meta")
+
+def hash_removals(results,subkey):
+ if not results.has_key(subkey):
+ return
+ scan = results[subkey].keys()
+ for k in scan:
+ if k.startswith("!") and k != "!":
+ remove_me = k[1:]
+ if results[subkey].has_key(remove_me):
+ del results[subkey][remove_me]
+ del results[subkey][k]
+
def hash_to_string(hash):
"""
Convert a hash to a printable string.
@@ -417,7 +495,7 @@ def hash_to_string(hash):
buffer = buffer + str(key) + "=" + str(value) + " "
return buffer
-def run_triggers(ref,globber):
+def run_triggers(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
@@ -434,10 +512,12 @@ def run_triggers(ref,globber):
# skip .rpmnew files that may have been installed
# in the triggers directory
continue
+ arglist = [ file ]
if ref:
- rc = sub_process.call([file,ref.name], shell=False)
- else:
- rc = sub_process.call([file], shell=False)
+ arglist.append(ref.name)
+ for x in additional:
+ arglist.append(x)
+ rc = sub_process.call(arglist, shell=False)
except:
print _("Warning: failed to execute trigger: %s" % file)
continue
@@ -452,9 +532,6 @@ def fix_mod_python_select_submission(repos):
which doesn't seem to happen on all versions of python/mp.
"""
- if str(repos).find("Field(") == -1:
- return repos # no hack needed
-
# should be nice regex, but this is readable :)
repos = str(repos)
repos = repos.replace("'repos'","")
@@ -468,3 +545,306 @@ def fix_mod_python_select_submission(repos):
repos = repos.lstrip().rstrip()
return repos
+def check_dist():
+ """
+ Determines what distro we're running under.
+ """
+ if os.path.exists("/etc/debian_version"):
+ return "debian"
+ else:
+ # valid for Fedora and all Red Hat / Fedora derivatives
+ return "redhat"
+
+def os_release():
+
+ if check_dist() == "redhat":
+
+ if not os.path.exists("/bin/rpm"):
+ return ("unknown", 0)
+ args = ["/bin/rpm", "-q", "--whatprovides", "redhat-release"]
+ cmd = sub_process.Popen(args,shell=False,stdout=sub_process.PIPE)
+ data = cmd.communicate()[0]
+ data = data.rstrip().lower()
+ make = "other"
+ if data.find("redhat") != -1:
+ make = "redhat"
+ elif data.find("centos") != -1:
+ make = "centos"
+ elif data.find("fedora") != -1:
+ make = "fedora"
+ version = data.split("release-")[-1]
+ rest = 0
+ if version.find("-"):
+ parts = version.split("-")
+ version = parts[0]
+ rest = parts[1]
+ return (make, float(version), rest)
+ elif check_dist() == "debian":
+ fd = open("/etc/debian_version")
+ parts = fd.read().split(".")
+ version = parts[0]
+ rest = parts[1]
+ make = "debian"
+ return (make, float(version), rest)
+ else:
+ return ("unknown",0)
+
+def tftpboot_location():
+
+ # if possible, read from TFTP config file to get the location
+ if os.path.exists("/etc/xinetd.d/tftp"):
+ fd = open("/etc/xinetd.d/tftp")
+ lines = fd.read().split("\n")
+ for line in lines:
+ if line.find("server_args") != -1:
+ tokens = line.split(None)
+ mark = False
+ for t in tokens:
+ if t == "-s":
+ mark = True
+ elif mark:
+ return t
+
+ # otherwise, guess based on the distro
+ (make,version,rest) = os_release()
+ if make == "fedora" and version >= 9:
+ return "/var/lib/tftpboot"
+ return "/tftpboot"
+
+def linkfile(src, dst):
+ """
+ Attempt to create a link dst that points to src. Because file
+ systems suck we attempt several different methods or bail to
+ copyfile()
+ """
+
+ try:
+ return os.link(src, dst)
+ except (IOError, OSError):
+ pass
+
+ try:
+ return os.symlink(src, dst)
+ except (IOError, OSError):
+ pass
+
+ return copyfile(src, dst)
+
+def copyfile(src,dst):
+ try:
+ return shutil.copyfile(src,dst)
+ except:
+ if not os.access(src,os.R_OK):
+ raise CX(_("Cannot read: %s") % src)
+ if not os.path.samefile(src,dst):
+ # accomodate for the possibility that we already copied
+ # the file as a symlink/hardlink
+ raise CX(_("Error copying %(src)s to %(dst)s") % { "src" : src, "dst" : dst})
+
+def rmfile(path):
+ try:
+ os.unlink(path)
+ return True
+ except OSError, ioe:
+ if not ioe.errno == errno.ENOENT: # doesn't exist
+ traceback.print_exc()
+ raise CX(_("Error deleting %s") % path)
+ return True
+
+def rmtree_contents(path):
+ what_to_delete = glob.glob("%s/*" % path)
+ for x in what_to_delete:
+ rmtree(x)
+
+def rmtree(path):
+ try:
+ if os.path.isfile(path):
+ return rmfile(path)
+ else:
+ return shutil.rmtree(path,ignore_errors=True)
+ except OSError, ioe:
+ traceback.print_exc()
+ if not ioe.errno == errno.ENOENT: # doesn't exist
+ raise CX(_("Error deleting %s") % path)
+ return True
+
+def mkdir(path,mode=0777):
+ try:
+ return os.makedirs(path,mode)
+ except OSError, oe:
+ if not oe.errno == 17: # already exists (no constant for 17?)
+ traceback.print_exc()
+ print oe.errno
+ raise CX(_("Error creating") % path)
+
+def set_repos(self,repos,bypass_check=False):
+ # WARNING: hack
+ repos = fix_mod_python_select_submission(repos)
+
+ # allow the magic inherit string to persist
+ if repos == "<<inherit>>":
+ # FIXME: this is not inheritable in the WebUI presently ?
+ self.repos = "<<inherit>>"
+ return
+
+ # store as an array regardless of input type
+ if repos is None:
+ repolist = []
+ elif type(repos) != list:
+ # allow backwards compatibility support of string input
+ repolist = repos.split(None)
+ else:
+ repolist = repos
+
+ # make sure there are no empty strings
+ try:
+ repolist.remove('')
+ except:
+ pass
+
+ self.repos = []
+
+ # if any repos don't exist, fail the set operation
+ # unless called from the deserializer stage in which
+ # case we have a soft error that check can report
+ ok = True
+ for r in repolist:
+ if bypass_check:
+ self.repos.append(r)
+ else:
+ if self.config.repos().find(name=r) is not None:
+ self.repos.append(r)
+ else:
+ raise CX(_("repo %s is not defined") % r)
+
+ return True
+
+def set_virt_file_size(self,num):
+ """
+ For Virt only.
+ Specifies the size of the virt image in gigabytes.
+ Older versions of koan (x<0.6.3) interpret 0 as "don't care"
+ Newer versions (x>=0.6.4) interpret 0 as "no disks"
+ """
+ # num is a non-negative integer (0 means default)
+ # can also be a comma seperated list -- for usage with multiple disks
+
+ if num == "<<inherit>>":
+ self.virt_file_size = "<<inherit>>"
+ return True
+
+ if type(num) == str and num.find(",") != -1:
+ tokens = num.split(",")
+ for t in tokens:
+ # hack to run validation on each
+ self.set_virt_file_size(t)
+ # if no exceptions raised, good enough
+ self.virt_file_size = num
+ return True
+
+ try:
+ inum = int(num)
+ if inum != float(num):
+ return CX(_("invalid virt file size"))
+ if inum >= 0:
+ self.virt_file_size = inum
+ return True
+ raise CX(_("invalid virt file size"))
+ except:
+ raise CX(_("invalid virt file size"))
+ return True
+
+def set_virt_ram(self,num):
+ """
+ For Virt only.
+ Specifies the size of the Virt RAM in MB.
+ 0 tells Koan to just choose a reasonable default.
+ """
+
+ if num == "<<inherit>>":
+ self.virt_ram = "<<inherit>>"
+ return True
+
+ # num is a non-negative integer (0 means default)
+ try:
+ inum = int(num)
+ if inum != float(num):
+ return CX(_("invalid virt ram size"))
+ if inum >= 0:
+ self.virt_ram = inum
+ return True
+ return CX(_("invalid virt ram size"))
+ except:
+ return CX(_("invalid virt ram size"))
+ return True
+
+def set_virt_type(self,vtype):
+ """
+ Virtualization preference, can be overridden by koan.
+ """
+
+ if vtype == "<<inherit>>":
+ self.virt_type == "<<inherit>>"
+ return True
+
+ if vtype.lower() not in [ "qemu", "xenpv", "xenfv", "vmware", "auto" ]:
+ raise CX(_("invalid virt type"))
+ self.virt_type = vtype
+ return True
+
+def set_virt_bridge(self,vbridge):
+ """
+ The default bridge for all virtual interfaces under this profile.
+ """
+ self.virt_bridge = vbridge
+ return True
+
+def set_virt_path(self,path):
+ """
+ Virtual storage location suggestion, can be overriden by koan.
+ """
+ self.virt_path = path
+ return True
+
+def set_virt_cpus(self,num):
+ """
+ For Virt only. Set the number of virtual CPUs to give to the
+ virtual machine. This is fed to virtinst RAW, so cobbler
+ will not yelp if you try to feed it 9999 CPUs. No formatting
+ like 9,999 please :)
+ """
+ if num == "<<inherit>>":
+ self.virt_cpus = "<<inherit>>"
+ return True
+
+ try:
+ num = int(str(num))
+ except:
+ raise CX(_("invalid number of virtual CPUs"))
+
+ self.virt_cpus = num
+ return True
+
+def get_kickstart_templates(api):
+ files = {}
+ for x in api.profiles():
+ if x.kickstart is not None and x.kickstart != "" and x.kickstart != "<<inherit>>":
+ if os.path.exists(x.kickstart):
+ files[x.kickstart] = 1
+ for x in api.systems():
+ if x.kickstart is not None and x.kickstart != "" and x.kickstart != "<<inherit>>":
+ if os.path.exists(x.kickstart):
+ files[x.kickstart] = 1
+ for x in glob.glob("/var/lib/cobbler/kickstarts/*"):
+ files[x] = 1
+ for x in glob.glob("/etc/cobbler/*.ks"):
+ files[x] = 1
+
+ return files.keys()
+
+
+
+if __name__ == "__main__":
+ # print redhat_release()
+ print tftpboot_location()
+
diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py
index 9a0cf90..d272281 100644
--- a/cobbler/webui/CobblerWeb.py
+++ b/cobbler/webui/CobblerWeb.py
@@ -60,6 +60,9 @@ class CobblerWeb(object):
# validate that our token is still good
try:
self.remote.token_check(self.token)
+ self.username = self.remote.get_user_from_token(self.token)
+ # ensure config is up2date
+ self.remote.update(self.token)
return True
except Exception, e:
if str(e).find("invalid token") != -1:
@@ -78,6 +81,8 @@ class CobblerWeb(object):
log_exc(self.apache)
return False
self.password = None # don't need it anymore, get rid of it
+ # ensure configuration is up2date
+ self.remote.update(self.token)
return True
# login failed
@@ -162,15 +167,27 @@ class CobblerWeb(object):
input_distro = None
if name is not None:
input_distro = self.remote.get_distro(name, True)
+ can_edit = self.remote.check_access_no_fail(self.token,"modify_distro",name)
+ else:
+ can_edit = self.remote.check_access_no_fail(self.token,"new_distro",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( 'distro_edit.tmpl', {
+ 'user' : self.username,
'edit' : True,
+ 'editable' : can_edit,
'distro': input_distro,
} )
def distro_save(self,name=None,oldname=None,new_or_edit=None,editmode='edit',kernel=None,
- initrd=None,kopts=None,ksmeta=None,arch=None,breed=None,
- delete1=None,delete2=None,**args):
+ initrd=None,kopts=None,ksmeta=None,owners=None,arch=None,breed=None,
+ delete1=None,delete2=None,recursive=False,**args):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -182,8 +199,12 @@ class CobblerWeb(object):
# handle deletes as a special case
if new_or_edit == 'edit' and delete1 and delete2:
- try:
- self.remote.remove_distro(name,self.token,1) # recursive
+ try:
+ if recursive is None:
+ self.remote.remove_distro(name,self.token,False)
+ else:
+ self.remote.remove_distro(name,self.token,True)
+
except Exception, e:
return self.error_page("could not delete %s, %s" % (name,str(e)))
return self.distro_list()
@@ -220,11 +241,14 @@ class CobblerWeb(object):
self.remote.modify_distro(distro, 'kopts', kopts, self.token)
if ksmeta:
self.remote.modify_distro(distro, 'ksmeta', ksmeta, self.token)
+ if owners:
+ self.remote.modify_distro(distro, 'owners', owners, self.token)
if arch:
self.remote.modify_distro(distro, 'arch', arch, self.token)
if breed:
self.remote.modify_distro(distro, 'breed', breed, self.token)
- self.remote.save_distro(distro, self.token)
+ # now time to save, do we want to run duplication checks?
+ self.remote.save_distro(distro, self.token, editmode)
except Exception, e:
log_exc(self.apache)
return self.error_page("Error while saving distro: %s" % str(e))
@@ -288,8 +312,9 @@ class CobblerWeb(object):
def system_save(self,name=None,oldname=None,editmode="edit",profile=None,
new_or_edit=None,
- kopts=None, ksmeta=None, server_override=None, netboot='n',
- delete1=None, delete2=None, **args):
+ kopts=None, ksmeta=None, owners=None, server_override=None, netboot='n',
+ virtpath=None,virtram=None,virttype=None,virtcpus=None,virtfilesize=None,delete1=None, delete2=None, **args):
+
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -332,11 +357,26 @@ class CobblerWeb(object):
self.remote.modify_system(system, 'kopts', kopts, self.token)
if ksmeta:
self.remote.modify_system(system, 'ksmeta', ksmeta, self.token)
+ if owners:
+ self.remote.modify_system(system, 'owners', owners, self.token)
if netboot:
self.remote.modify_system(system, 'netboot-enabled', netboot, self.token)
if server_override:
self.remote.modify_system(system, 'server', server_override, self.token)
+ if virtfilesize:
+ self.remote.modify_system(system, 'virt-file-size', virtfilesize, self.token)
+ if virtcpus:
+ self.remote.modify_system(system, 'virt-cpus', virtcpus, self.token)
+ if virtram:
+ self.remote.modify_system(system, 'virt-ram', virtram, self.token)
+ if virttype:
+ self.remote.modify_system(system, 'virt-type', virttype, self.token)
+
+ if virtpath:
+ self.remote.modify_system(system, 'virt-path', virtpath, self.token)
+
+
for x in range(0,7):
interface = "intf%s" % x
macaddress = args.get("macaddress-%s" % interface, "")
@@ -364,8 +404,7 @@ class CobblerWeb(object):
mods["gateway-%s" % interface] = gateway
self.remote.modify_system(system,'modify-interface', mods, self.token)
- # now commit the edits
- self.remote.save_system( system, self.token)
+ self.remote.save_system(system, self.token, editmode)
except Exception, e:
log_exc(self.apache)
@@ -390,9 +429,20 @@ class CobblerWeb(object):
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()
} )
@@ -427,10 +477,21 @@ class CobblerWeb(object):
input_profile = None
if name is not None:
- input_profile = self.remote.get_profile(name,True)
+ input_profile = self.remote.get_profile(name,True)
+ can_edit = self.remote.check_access_no_fail(self.token,"modify_profile",name)
+ else:
+ can_edit = self.remote.check_access_no_fail(self.token,"new_profile",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( 'profile_edit.tmpl', {
+ 'user' : self.username,
'edit' : True,
+ 'editable' : can_edit,
'profile': input_profile,
'distros': self.remote.get_distros(),
'profiles': self.remote.get_profiles(),
@@ -441,9 +502,9 @@ class CobblerWeb(object):
def profile_save(self,new_or_edit=None,editmode='edit',name=None,oldname=None,
distro=None,kickstart=None,kopts=None,
- ksmeta=None,virtfilesize=None,virtram=None,virttype=None,
+ ksmeta=None,owners=None,virtfilesize=None,virtram=None,virttype=None,
virtpath=None,repos=None,dhcptag=None,delete1=None,delete2=None,
- parent=None,virtcpus=None,virtbridge=None,subprofile=None,server_override=None,**args):
+ parent=None,virtcpus=None,virtbridge=None,subprofile=None,server_override=None,recursive=False,**args):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
@@ -463,7 +524,11 @@ class CobblerWeb(object):
# handle deletes as a special case
if new_or_edit == 'edit' and delete1 and delete2:
try:
- self.remote.remove_profile(name,self.token,1)
+ if recursive:
+ self.remote.remove_profile(name,self.token,True)
+ else:
+ self.remote.remove_profile(name,self.token,False)
+
except Exception, e:
return self.error_page("could not delete %s, %s" % (name,str(e)))
return self.profile_list()
@@ -495,6 +560,8 @@ class CobblerWeb(object):
self.remote.modify_profile(profile, 'kickstart', kickstart, self.token)
if kopts:
self.remote.modify_profile(profile, 'kopts', kopts, self.token)
+ if owners:
+ self.remote.modify_profile(profile, 'owners', owners, self.token)
if ksmeta:
self.remote.modify_profile(profile, 'ksmeta', ksmeta, self.token)
if virtfilesize:
@@ -523,7 +590,7 @@ class CobblerWeb(object):
if dhcptag:
self.remote.modify_profile(profile, 'dhcp-tag', dhcptag, self.token)
- self.remote.save_profile(profile,self.token)
+ self.remote.save_profile(profile,self.token, editmode)
except Exception, e:
log_exc(self.apache)
return self.error_page("Error while saving profile: %s" % str(e))
@@ -565,13 +632,24 @@ class CobblerWeb(object):
input_repo = None
if name is not None:
input_repo = self.remote.get_repo(name, True)
+ can_edit = self.remote.check_access_no_fail(self.token,"modify_repo",name)
+ else:
+ can_edit = self.remote.check_access_no_fail(self.token,"new_repo",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( 'repo_edit.tmpl', {
+ 'user' : self.username,
'repo': input_repo,
+ 'editable' : can_edit
} )
def repo_save(self,name=None,oldname=None,new_or_edit=None,editmode="edit",
- mirror=None,keep_updated=None,priority=99,
+ mirror=None,owners=None,keep_updated=None,mirror_locally=0,priority=99,
rpm_list=None,createrepo_flags=None,arch=None,yumopts=None,
delete1=None,delete2=None,**args):
if not self.__xmlrpc_setup():
@@ -615,6 +693,7 @@ class CobblerWeb(object):
self.remote.modify_repo(repo, 'mirror', mirror, self.token)
self.remote.modify_repo(repo, 'keep-updated', keep_updated, self.token)
self.remote.modify_repo(repo, 'priority', priority, self.token)
+ self.remote.modify_repo(repo, 'mirror-locally', mirror_locally, self.token)
if rpm_list:
self.remote.modify_repo(repo, 'rpm-list', rpm_list, self.token)
@@ -624,8 +703,10 @@ class CobblerWeb(object):
self.remote.modify_repo(repo, 'arch', arch, self.token)
if yumopts:
self.remote.modify_repo(repo, 'yumopts', yumopts, self.token)
+ if owners:
+ self.remote.modify_repo(repo, 'owners', owners, self.token)
- self.remote.save_repo(repo, self.token)
+ self.remote.save_repo(repo, self.token, editmode)
except Exception, e:
log_exc(self.apache)
@@ -650,22 +731,49 @@ class CobblerWeb(object):
'ksfiles': self.remote.get_kickstart_templates(self.token)
} )
+ def ksfile_new(self, name=None,**spam):
+
+
+ if not self.__xmlrpc_setup():
+ return self.xmlrpc_auth_failure()
+
+ can_edit = self.remote.check_access_no_fail(self.token,"add_kickstart",name)
+ return self.__render( 'ksfile_new.tmpl', {
+ 'editable' : can_edit,
+ 'ksdata': ''
+ } )
+
+
+
def ksfile_edit(self, name=None,**spam):
+
+
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
+
+ can_edit = self.remote.check_access_no_fail(self.token,"modify_kickstart",name)
return self.__render( 'ksfile_edit.tmpl', {
'name': name,
+ 'deleteable' : not self.remote.is_kickstart_in_use(name,self.token),
+ 'editable' : can_edit,
'ksdata': self.remote.read_or_write_kickstart_template(name,True,"",self.token)
} )
- def ksfile_save(self, name=None, ksdata=None, **args):
+ def ksfile_save(self, name=None, ksdata=None, delete1=None, delete2=None, isnew=None, **args):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
+
+
try:
- self.remote.read_or_write_kickstart_template(name,False,ksdata,self.token)
+ if delete1 and delete2:
+ self.remote.read_or_write_kickstart_template(name,False,-1,self.token)
+ if isnew is not None:
+ name = "/var/lib/cobbler/kickstarts/" + name
+ if not delete1 and not delete2:
+ self.remote.read_or_write_kickstart_template(name,False,ksdata,self.token)
except Exception, e:
return self.error_page("An error occurred while trying to save kickstart file %s:<br/><br/>%s" % (name,str(e)))
- return self.ksfile_edit(name=name)
+ return self.ksfile_list()
# ------------------------------------------------------------------------ #
# Miscellaneous
@@ -675,6 +783,13 @@ class CobblerWeb(object):
if not self.__xmlrpc_setup():
return self.xmlrpc_auth_failure()
+ can_edit = self.remote.check_access_no_fail(self.token,"sync",None)
+ if not can_edit:
+ return self.__render('message.tmpl', {
+ 'message1' : "Access denied.",
+ 'message2' : "You do not have permission to create new objects."
+ })
+
try:
rc = self.remote.sync(self.token)
if not rc:
@@ -739,6 +854,7 @@ class CobblerWeb(object):
settings_view.exposed = True
ksfile_edit.exposed = True
+ ksfile_new.exposed = True
ksfile_save.exposed = True
ksfile_list.exposed = True
diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py
deleted file mode 100644
index 7ef1b0a..0000000
--- a/cobbler/webui/master.py
+++ /dev/null
@@ -1,262 +0,0 @@
-#!/usr/bin/env python
-
-
-
-
-##################################################
-## DEPENDENCIES
-import sys
-import os
-import os.path
-from os.path import getmtime, exists
-import time
-import types
-import __builtin__
-from Cheetah.Version import MinCompatibleVersion as RequiredCheetahVersion
-from Cheetah.Version import MinCompatibleVersionTuple as RequiredCheetahVersionTuple
-from Cheetah.Template import Template
-from Cheetah.DummyTransaction import DummyTransaction
-from Cheetah.NameMapper import NotFound, valueForName, valueFromSearchList, valueFromFrameOrSearchList
-from Cheetah.CacheRegion import CacheRegion
-import Cheetah.Filters as Filters
-import Cheetah.ErrorCatchers as ErrorCatchers
-
-##################################################
-## MODULE CONSTANTS
-try:
- True, False
-except NameError:
- True, False = (1==1), (1==0)
-VFFSL=valueFromFrameOrSearchList
-VFSL=valueFromSearchList
-VFN=valueForName
-currentTime=time.time
-__CHEETAH_version__ = '2.0.1'
-__CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0)
-__CHEETAH_genTime__ = 1207686338.5978019
-__CHEETAH_genTimestamp__ = 'Tue Apr 8 16:25:38 2008'
-__CHEETAH_src__ = 'webui_templates/master.tmpl'
-__CHEETAH_srcLastModified__ = 'Fri Feb 15 14:47:43 2008'
-__CHEETAH_docstring__ = 'Autogenerated by CHEETAH: The Python-Powered Template Engine'
-
-if __CHEETAH_versionTuple__ < RequiredCheetahVersionTuple:
- raise AssertionError(
- 'This template was compiled with Cheetah version'
- ' %s. Templates compiled before version %s must be recompiled.'%(
- __CHEETAH_version__, RequiredCheetahVersion))
-
-##################################################
-## CLASSES
-
-class master(Template):
-
- ##################################################
- ## CHEETAH GENERATED METHODS
-
-
- def __init__(self, *args, **KWs):
-
- Template.__init__(self, *args, **KWs)
- if not self._CHEETAH__instanceInitialized:
- cheetahKWArgs = {}
- allowedKWs = 'searchList namespaces filter filtersLib errorCatcher'.split()
- for k,v in KWs.items():
- if k in allowedKWs: cheetahKWArgs[k] = v
- self._initCheetahInstance(**cheetahKWArgs)
-
-
- def body(self, **KWS):
-
-
-
- ## CHEETAH: generated from #block body at line 53, col 1.
- trans = KWS.get("trans")
- if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)):
- trans = self.transaction # is None unless self.awake() was called
- if not trans:
- trans = DummyTransaction()
- _dummyTrans = True
- else: _dummyTrans = False
- write = trans.response().write
- SL = self._CHEETAH__searchList
- _filter = self._CHEETAH__currentFilter
-
- ########################################
- ## START - generated method body
-
- write('''
- <h1 style="color: red;">Template Failure</h1>
-
-''')
-
- ########################################
- ## END - generated method body
-
- return _dummyTrans and trans.response().getvalue() or ""
-
-
- def respond(self, trans=None):
-
-
-
- ## CHEETAH: main method generated for this template
- if (not trans and not self._CHEETAH__isBuffering and not callable(self.transaction)):
- trans = self.transaction # is None unless self.awake() was called
- if not trans:
- trans = DummyTransaction()
- _dummyTrans = True
- else: _dummyTrans = False
- write = trans.response().write
- SL = self._CHEETAH__searchList
- _filter = self._CHEETAH__currentFilter
-
- ########################################
- ## START - generated method body
-
- write('''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xml:lang="en" lang="en" xmlns="http://www.w3.org/1999/xhtml">
-<head>
- <title>''')
- _v = VFFSL(SL,"title",True) # '$title' on line 5, col 12
- if _v is not None: write(_filter(_v, rawExpr='$title')) # from line 5, col 12.
- write('''</title>
- <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
-
- <link rel="stylesheet" type="text/css" media="all" href="/cobbler/webui/style.css" />
- <link rel="stylesheet" type="text/css" media="all" href="/cobbler/webui/cobblerweb.css" />
-
-<script language="Javascript" src="/cobbler/webui/cobbler.js" ></script>
-
-</head>
-
-
-<body onload="global_onload();">
-
-<div id="wrap">
- <h1 id="masthead">
- <a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 20, col 18
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 20, col 18.
- write('''/index">
- <img alt="Cobbler Logo"
- src="/cobbler/webui/logo-cobbler.png"/>
- </a>
- </h1>
-</div>
-
-<div id="main">
-
-<div id="sidebar">
- <ul id="nav">
- <li><a href="/cobbler/webui/wui.html" class="menu">Docs</a></li>
- <li><a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 32, col 22
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 32, col 22.
- write('''?mode=settings_view" class="menu">Settings</a></li>
- <li><hr/></li>
- <li>LIST</li>
- <li><a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 35, col 22
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 35, col 22.
- write('''?mode=distro_list" class="menu">Distros</a></li>
- <li><a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 36, col 22
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 36, col 22.
- write('''?mode=profile_list" class="menu">Profiles</a></li>
- <li><a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 37, col 22
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 37, col 22.
- write('''?mode=system_list" class="menu">Systems</a></li>
- <li><a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 38, col 22
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 38, col 22.
- write('''?mode=ksfile_list" class="menu">Kickstarts</a></li>
- <li><a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 39, col 22
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 39, col 22.
- write('''?mode=repo_list" class="menu">Repos</a></li>
- <li><hr/></li>
- <li>ADD</li>
- <li><a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 42, col 22
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 42, col 22.
- write('''?mode=distro_edit" class="menu">Distro</a></li>
- <li><a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 43, col 22
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 43, col 22.
- write('''?mode=profile_edit" class="menu">Profile</a></li>
- <li><a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 44, col 22
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 44, col 22.
- write('''?mode=subprofile_edit" class="menu">Subprofile</a></li>
- <li><a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 45, col 22
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 45, col 22.
- write('''?mode=system_edit" class="menu">System</a></li>
- <li><a href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 46, col 22
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 46, col 22.
- write('''?mode=repo_edit" class="menu">Repo</a></li>
- <li><hr/><br/></li>
- <li><a class="button sync" href="''')
- _v = VFFSL(SL,"base_url",True) # '$base_url' on line 48, col 42
- if _v is not None: write(_filter(_v, rawExpr='$base_url')) # from line 48, col 42.
- write('''?mode=sync">Sync</a></li>
- </ul>
-</div>
-
-<div id="content">
-''')
- self.body(trans=trans)
- write('''</div><!-- content -->
-</div><!-- main -->
-
-</body>
-</html>
-''')
-
- ########################################
- ## END - generated method body
-
- return _dummyTrans and trans.response().getvalue() or ""
-
- ##################################################
- ## CHEETAH GENERATED ATTRIBUTES
-
-
- _CHEETAH__instanceInitialized = False
-
- _CHEETAH_version = __CHEETAH_version__
-
- _CHEETAH_versionTuple = __CHEETAH_versionTuple__
-
- _CHEETAH_genTime = __CHEETAH_genTime__
-
- _CHEETAH_genTimestamp = __CHEETAH_genTimestamp__
-
- _CHEETAH_src = __CHEETAH_src__
-
- _CHEETAH_srcLastModified = __CHEETAH_srcLastModified__
-
- title = "Cobbler Web Interface"
-
- _mainCheetahMethod_for_master= 'respond'
-
-## END CLASS DEFINITION
-
-if not hasattr(master, '_initCheetahAttributes'):
- templateAPIClass = getattr(master, '_CHEETAH_templateClass', Template)
- templateAPIClass._addCheetahPlumbingCodeToClass(master)
-
-
-# CHEETAH was developed by Tavis Rudd and Mike Orr
-# with code, advice and input from many other volunteers.
-# For more information visit http://www.CheetahTemplate.org/
-
-##################################################
-## if run from command line:
-if __name__ == '__main__':
- from Cheetah.TemplateCmdLineIface import CmdLineIface
- CmdLineIface(templateObj=master()).run()
-
-
diff --git a/cobbler/yaml/__init__.py b/cobbler/yaml/__init__.py
index 419d1f3..bd21b40 100644
--- a/cobbler/yaml/__init__.py
+++ b/cobbler/yaml/__init__.py
@@ -1,3 +1,9 @@
+"""
+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
diff --git a/cobbler/yaml/dump.py b/cobbler/yaml/dump.py
index b8e9d79..eb34955 100644
--- a/cobbler/yaml/dump.py
+++ b/cobbler/yaml/dump.py
@@ -1,3 +1,10 @@
+"""
+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
diff --git a/cobbler/yaml/implicit.py b/cobbler/yaml/implicit.py
index 6172564..49d65e0 100644
--- a/cobbler/yaml/implicit.py
+++ b/cobbler/yaml/implicit.py
@@ -1,3 +1,9 @@
+"""
+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
diff --git a/cobbler/yaml/inline.py b/cobbler/yaml/inline.py
index 8e647de..d4f6439 100644
--- a/cobbler/yaml/inline.py
+++ b/cobbler/yaml/inline.py
@@ -1,3 +1,9 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
import re
import string
diff --git a/cobbler/yaml/klass.py b/cobbler/yaml/klass.py
index edcf5a8..c182fcf 100644
--- a/cobbler/yaml/klass.py
+++ b/cobbler/yaml/klass.py
@@ -1,3 +1,9 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
import new
import re
diff --git a/cobbler/yaml/load.py b/cobbler/yaml/load.py
index 259178d..54931d6 100644
--- a/cobbler/yaml/load.py
+++ b/cobbler/yaml/load.py
@@ -1,3 +1,9 @@
+"""
+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
diff --git a/cobbler/yaml/ordered_dict.py b/cobbler/yaml/ordered_dict.py
index b3788b7..5bc2e3e 100644
--- a/cobbler/yaml/ordered_dict.py
+++ b/cobbler/yaml/ordered_dict.py
@@ -1,3 +1,10 @@
+"""
+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
diff --git a/cobbler/yaml/redump.py b/cobbler/yaml/redump.py
index 56ea958..eefd68e 100644
--- a/cobbler/yaml/redump.py
+++ b/cobbler/yaml/redump.py
@@ -1,3 +1,9 @@
+"""
+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
diff --git a/cobbler/yaml/stream.py b/cobbler/yaml/stream.py
index cc78c4b..dcd65c3 100644
--- a/cobbler/yaml/stream.py
+++ b/cobbler/yaml/stream.py
@@ -1,3 +1,9 @@
+"""
+pyyaml legacy
+Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
+(see open source license information in docs/ directory)
+"""
+
import re
import string
diff --git a/cobbler/yaml/timestamp.py b/cobbler/yaml/timestamp.py
index abcb2e6..5c522f6 100644
--- a/cobbler/yaml/timestamp.py
+++ b/cobbler/yaml/timestamp.py
@@ -1,3 +1,10 @@
+"""
+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
diff --git a/cobbler/yaml/ypath.py b/cobbler/yaml/ypath.py
index 51d9d2f..b183a23 100644
--- a/cobbler/yaml/ypath.py
+++ b/cobbler/yaml/ypath.py
@@ -1,3 +1,10 @@
+"""
+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
diff --git a/cobbler/yumgen.py b/cobbler/yumgen.py
new file mode 100644
index 0000000..8c10be3
--- /dev/null
+++ b/cobbler/yumgen.py
@@ -0,0 +1,110 @@
+"""
+Builds out filesystem trees/data based on the object tree.
+This is the code behind 'cobbler sync'.
+
+Copyright 2006-2008, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import os
+import os.path
+import shutil
+import time
+import sub_process
+import sys
+import glob
+import traceback
+import errno
+
+import utils
+from cexceptions import *
+import templar
+
+import item_distro
+import item_profile
+import item_repo
+import item_system
+
+from utils import _
+
+
+class YumGen:
+
+ def __init__(self,config):
+ """
+ Constructor
+ """
+ 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)
+
+ def retemplate_all_yum_repos(self):
+ for p in self.profiles:
+ self.retemplate_yum_repos(p,True)
+ for system in self.systems:
+ self.retemplate_yum_repos(system,False)
+
+ def retemplate_yum_repos(self,obj,is_profile):
+ """
+ Yum repository management files are in self.settings.webdir/repo_mirror/$name/config.repo
+ and also potentially in listed in the source_repos structure of the distro object, however
+ these files have server URLs in them that must be templated out. This function does this.
+ """
+ blended = utils.blender(self.api, False, obj)
+
+ if is_profile:
+ outseg = "repos_profile"
+ else:
+ outseg = "repos_system"
+
+ input_files = []
+
+ # chance old versions from upgrade do not have a source_repos
+ # workaround for user bug
+ if not blended.has_key("source_repos"):
+ blended["source_repos"] = []
+
+ # tack on all the install source repos IF there is more than one.
+ # this is basically to support things like RHEL5 split trees
+ # if there is only one, then there is no need to do this.
+
+ for r in blended["source_repos"]:
+ filename = self.settings.webdir + "/" + "/".join(r[0].split("/")[4:])
+ input_files.append(filename)
+
+ for repo in blended["repos"]:
+ input_files.append(os.path.join(self.settings.webdir, "repo_mirror", repo, "config.repo"))
+
+ for infile in input_files:
+ if infile.find("ks_mirror") == -1:
+ dispname = infile.split("/")[-2]
+ else:
+ dispname = infile.split("/")[-1].replace(".repo","")
+ confdir = os.path.join(self.settings.webdir, outseg)
+ outdir = os.path.join(confdir, blended["name"])
+ utils.mkdir(outdir)
+ try:
+ infile_h = open(infile)
+ except:
+ # file does not exist and the user needs to run reposync
+ # before we will use this, cobbler check will mention
+ # this problem
+ continue
+ infile_data = infile_h.read()
+ infile_h.close()
+ outfile = os.path.join(outdir, "%s.repo" % (dispname))
+ self.templar.render(infile_data, blended, outfile, None)
+
+