diff options
-rw-r--r-- | CHANGELOG | 4 | ||||
-rw-r--r-- | MANIFEST.in | 7 | ||||
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | cobbler.spec | 5 | ||||
-rw-r--r-- | cobbler/action_litesync.py | 13 | ||||
-rw-r--r-- | cobbler/action_sync.py | 365 | ||||
-rw-r--r-- | cobbler/api.py | 11 | ||||
-rw-r--r-- | cobbler/kickgen.py | 272 | ||||
-rw-r--r-- | cobbler/remote.py | 8 | ||||
-rw-r--r-- | cobbler/services.py | 94 | ||||
-rw-r--r-- | cobbler/settings.py | 5 | ||||
-rw-r--r-- | cobbler/webui/master.py | 4 | ||||
-rw-r--r-- | config/cobbler_svc.conf | 12 | ||||
-rw-r--r-- | config/settings | 5 | ||||
-rwxr-xr-x | legacy/change_profile.cgi (renamed from scripts/change_profile.cgi) | 0 | ||||
-rwxr-xr-x | legacy/findks.cgi (renamed from scripts/findks.cgi) | 0 | ||||
-rw-r--r-- | legacy/install_trigger.cgi (renamed from scripts/install_trigger.cgi) | 0 | ||||
-rwxr-xr-x | legacy/nopxe.cgi (renamed from scripts/nopxe.cgi) | 0 | ||||
-rwxr-xr-x | legacy/register_mac.cgi (renamed from scripts/register_mac.cgi) | 2 | ||||
-rwxr-xr-x | legacy/watcher.py (renamed from scripts/watcher.py) | 0 | ||||
-rwxr-xr-x | scripts/services.py | 67 | ||||
-rw-r--r-- | setup.py | 9 |
22 files changed, 494 insertions, 390 deletions
@@ -26,6 +26,10 @@ Cobbler CHANGELOG - change default authentication to deny_all, xmlrpc_rw_enabled now on by default - additional fix for mod_python select box submissions - set repo arch if found in the URL and no --arch is specified +- CGI scripts have been moved under mod_python for speed/consolidation +- kickstart templates are now evaluated dynamically +- optional MAC registration is now built-in to requesting kickstarts +- legacy static file generation from /var/www/cobbler removed - ??? - 0.8.3 - Make createrepo get run for local cobbler reposync invocations as needed diff --git a/MANIFEST.in b/MANIFEST.in index 12f9f9e..644c0c2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -17,12 +17,7 @@ include docs/cobbler.1.gz include docs/cobbler.html include docs/wui.html include COPYING AUTHORS README CHANGELOG -include scripts/watcher.py -include scripts/index.py -include scripts/cobblerd -include scripts/findks.cgi -include scripts/nopxe.cgi -include scripts/install_trigger.cgi +include scripts/*.py include snippets/* recursive-include po *.pot recursive-include po *.po @@ -51,6 +51,7 @@ devinstall: chown -R apache /var/www/cobbler chown -R apache /var/www/cgi-bin/cobbler chmod -R +x /var/www/cobbler/web + chmod -R +x /var/www/cobbler/svc webtest: devinstall /sbin/service cobblerd restart diff --git a/cobbler.spec b/cobbler.spec index 5e63f48..7b4e525 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -83,8 +83,8 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %defattr(755,apache,apache) %dir /var/www/cobbler/web/ /var/www/cobbler/web/*.py* -%dir /var/www/cgi-bin/cobbler/ -/var/www/cgi-bin/cobbler/*.cgi +%dir /var/www/cobbler/svc/ +/var/www/cobbler/svc/*.py* %defattr(755,apache,apache) %dir /usr/share/cobbler/webui_templates @@ -195,6 +195,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT * Tue Apr 08 2008 Michael DeHaan <mdehaan@redhat.com> - 0.9.0-1 - Upstream changes (see CHANGELOG) - packaged /etc/cobbler/users.conf +- remaining CGI replaced with mod_python * Tue Apr 08 2008 Michael DeHaan <mdehaan@redhat.com> - 0.8.3-2 - Upstream changes (see CHANGELOG) diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index bc7ffb2..3a2de8e 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -82,11 +82,7 @@ class BootLiteSync: 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) # cascade sync @@ -98,8 +94,6 @@ 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 utils.rmfile(os.path.join(self.settings.webdir, "profiles", name)) # delete contents on kickstarts/$name directory in webdir @@ -109,7 +103,7 @@ class BootLiteSync: 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.write_all_system_files(system) def add_single_system(self, name): # get the system object: @@ -119,19 +113,14 @@ class BootLiteSync: # 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) # 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) 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 utils.rmfile(os.path.join(self.settings.webdir, "systems", name)) # delete contents of kickstarts_sys/$name in webdir diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 5b7b546..aa53d12 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -27,7 +27,7 @@ import errno import utils from cexceptions import * import templar - +import kickgen import item_distro import item_profile @@ -57,6 +57,7 @@ class BootSync: self.settings = config.settings() self.repos = config.repos() self.templar = templar.Templar(config) + self.kickgen = kickgen.KickGen(config) self.bootloc = utils.tftpboot_location() def run(self): @@ -82,9 +83,9 @@ class BootSync: self.clean_trees() self.copy_bootloaders() self.copy_distros() + for x in self.systems: + self.write_all_system_files(x) self.retemplate_all_yum_repos() - self.validate_kickstarts() - self.build_trees() if self.settings.manage_dhcp: # these functions DRT for ISC or dnsmasq self.write_dhcp_file() @@ -278,7 +279,7 @@ class BootSync: if not x.endswith(".py"): 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 utils.rmtree(path) if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles","repo_profile","repo_system"]: @@ -324,294 +325,6 @@ class BootSync: else: utils.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) - 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 - ) - utils.mkdir(copy_path) - dest = os.path.join(copy_path, "ks.cfg") - 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) - self.templar.render(kfile, meta, dest, g) - 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, 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 - """ - - # FIXME: watcher is more of a request than a packaged file - # we should eventually package something and let it do something important" - - nopxe = "\nwget \"http://%s/cgi-bin/cobbler/nopxe.cgi?system=%s\"" - saveks = "\nwget \"http://%s/cobbler/%s/%s/ks.cfg\" -O /root/cobbler.ks" - runpost = "\nwget \"http://%s/cgi-bin/cobbler/install_trigger.cgi?mode=post&%s=%s\"" - runpre = "\nwget \"http://%s/cgi-bin/cobbler/install_trigger.cgi?mode=pre&%s=%s\"" - - 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, "kickstarts_sys", system.name) - if self.settings.run_install_trigger: - buf = buf + runpost % (srv, what, system.name) - else: - if self.settings.run_install_trigger: - buf = buf + runpre % (srv, what, system.name) - - else: - if not is_pre: - if kickstart and os.path.exists(kickstart): - buf = buf + saveks % (srv, "kickstarts", profile.name) - if self.settings.run_install_trigger: - buf = buf + runpost % (srv, what, profile.name) - else: - if self.settings.run_install_trigger: - buf = buf + runpre % (srv, what, 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) - configs = self.get_repo_filenames(obj,is_profile) - repos = self.repos - - 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': - 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) - 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) - 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) - 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 - ) - utils.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(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) - self.templar.render(kfile, meta, dest, s) - kfile.close() - except: - traceback.print_exc() - raise CX(_("Error templating file %(src)s to %(dest)s") % { "src" : meta["kickstart"], "dest" : dest }) - - def 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) @@ -619,8 +332,6 @@ class BootSync: 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 @@ -670,7 +381,7 @@ class BootSync: self.templar.render(infile_data, blended, outfile, None) - def write_all_system_files(self,system,just_edit_pxe=False): + def write_all_system_files(self,system): profile = system.get_conceptual_parent() if profile is None: @@ -713,11 +424,6 @@ class BootSync: # ensure the file doesn't exist utils.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 @@ -849,9 +555,9 @@ class BootSync: 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) + 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/kickstarts/%s/ks.cfg" % (blended["http_server"], profile.name) + 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) @@ -884,60 +590,5 @@ class BootSync: 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) - 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) - 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) - filename = os.path.join(self.settings.webdir,"systems",system.name) - fd = open(filename, "w+") - fd.write(yaml.dump(blended)) - fd.close() - diff --git a/cobbler/api.py b/cobbler/api.py index 6f7d1b1..ebe987e 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -25,6 +25,7 @@ import action_validate from cexceptions import * import sub_process import module_loader +import kickgen import logging import os @@ -79,6 +80,7 @@ class BootAPI: "module", "authz_allowall" ) + self.kickgen = kickgen.KickGen(self._config) self.logger.debug("API handle initialized") def __setup_logger(self,name): @@ -284,7 +286,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 diff --git a/cobbler/kickgen.py b/cobbler/kickgen.py new file mode 100644 index 0000000..dce33f3 --- /dev/null +++ b/cobbler/kickgen.py @@ -0,0 +1,272 @@ +""" +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: + traceback.print_exc() # leave this in, for now... + msg = "err_kickstart2" + raise CX(_("Error while rendering kickstart file")) + + 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/srv/?op=trig&?mode=post&%s=%s\" -O /dev/null" + runpre = "\nwget \"http://%s/cblr/srv/?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 + + 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': + 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) + 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/remote.py b/cobbler/remote.py index 0cbaf22..a87355b 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -158,6 +158,14 @@ class CobblerXMLRPCInterface: return self._fix_none(data) + def generate_kickstart(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None,reg=None): + self.log("generate_kickstart") + + if reg is not None and 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. diff --git a/cobbler/services.py b/cobbler/services.py new file mode 100644 index 0000000..9ced0c6 --- /dev/null +++ b/cobbler/services.py @@ -0,0 +1,94 @@ +# 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) + + 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,reg=None,**rest): + """ + Generate kickstart files... + """ + self.__xmlrpc_setup() + return self.remote.generate_kickstart(profile,system,REMOTE_ADDR,REMOTE_MAC) + + 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 1695cbb..40ed571 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -22,8 +22,6 @@ TESTMODE = False # we need. DEFAULTS = { - "allow_cgi_mac_registration" : 0, - "allow_cgi_profile_change" : 0, "allow_duplicate_macs" : 0, "allow_duplicate_ips" : 0, "bootloaders" : { @@ -61,7 +59,8 @@ DEFAULTS = { "manage_dhcp_mode" : "isc", "next_server" : "127.0.0.1", "pxe_just_once" : 0, - "run_install_trigger" : 1, + "register_new_installs" : 0, + "run_install_triggers" : 1, "server" : "127.0.0.1", "snippetsdir" : "/var/lib/cobbler/snippets", "syslog_port" : 25150, diff --git a/cobbler/webui/master.py b/cobbler/webui/master.py index 1e6d30d..0eec178 100644 --- a/cobbler/webui/master.py +++ b/cobbler/webui/master.py @@ -33,8 +33,8 @@ VFN=valueForName currentTime=time.time __CHEETAH_version__ = '2.0.1' __CHEETAH_versionTuple__ = (2, 0, 1, 'final', 0) -__CHEETAH_genTime__ = 1208298360.4598751 -__CHEETAH_genTimestamp__ = 'Tue Apr 15 18:26:00 2008' +__CHEETAH_genTime__ = 1208545841.2024181 +__CHEETAH_genTimestamp__ = 'Fri Apr 18 15:10:41 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' diff --git a/config/cobbler_svc.conf b/config/cobbler_svc.conf new file mode 100644 index 0000000..f0c86de --- /dev/null +++ b/config/cobbler_svc.conf @@ -0,0 +1,12 @@ +# This configuration file allows cobbler data +# to be accessed over HTTP. + +# mod_python WebUI/services + +<Directory "/var/www/cobbler/svc/"> + SetHandler mod_python + PythonHandler services + PythonDebug on +</Directory> + + diff --git a/config/settings b/config/settings index fc6739a..10e06e2 100644 --- a/config/settings +++ b/config/settings @@ -1,6 +1,4 @@ --- -allow_cgi_mac_registration: 0 -allow_cgi_profile_change: 0 allow_duplicate_macs: 0 allow_duplicate_ips: 0 bootloaders: @@ -35,7 +33,8 @@ manage_dhcp: 0 manage_dhcp_mode: isc next_server: '127.0.0.1' pxe_just_once: 0 -run_install_trigger: 1 +register_new_installs: 0 +run_install_triggers: 1 server: '127.0.0.1' snippetsdir: /var/lib/cobbler/snippets syslog_port: 25150 diff --git a/scripts/change_profile.cgi b/legacy/change_profile.cgi index f7330f1..f7330f1 100755 --- a/scripts/change_profile.cgi +++ b/legacy/change_profile.cgi diff --git a/scripts/findks.cgi b/legacy/findks.cgi index 39adbcf..39adbcf 100755 --- a/scripts/findks.cgi +++ b/legacy/findks.cgi diff --git a/scripts/install_trigger.cgi b/legacy/install_trigger.cgi index b83ff57..b83ff57 100644 --- a/scripts/install_trigger.cgi +++ b/legacy/install_trigger.cgi diff --git a/scripts/nopxe.cgi b/legacy/nopxe.cgi index a2eae88..a2eae88 100755 --- a/scripts/nopxe.cgi +++ b/legacy/nopxe.cgi diff --git a/scripts/register_mac.cgi b/legacy/register_mac.cgi index 5507525..3f251c4 100755 --- a/scripts/register_mac.cgi +++ b/legacy/register_mac.cgi @@ -13,7 +13,7 @@ # what is this? This is a # script to auto add systems who make a wget into cobbler. # right now it requires "kssendmac" in kernel options and takes only 1 arg -# ex: wget http://cobbler.example.org/cgi-bin/regsister_mac?profile=foo +# ex: wget http://cobbler.example.org/cgi-bin/register_mac?profile=foo # suitable to be called from kickstart,etc import cgi diff --git a/scripts/watcher.py b/legacy/watcher.py index dfa8dc3..dfa8dc3 100755 --- a/scripts/watcher.py +++ b/legacy/watcher.py diff --git a/scripts/services.py b/scripts/services.py new file mode 100755 index 0000000..07243ae --- /dev/null +++ b/scripts/services.py @@ -0,0 +1,67 @@ +""" +mod_python gateway to cgi-like cobbler web functions + +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. +""" + +from mod_python import apache +from mod_python import Session +from mod_python import util + +import xmlrpclib +import cgi +import os +from cobbler.services import CobblerSvc + +#======================================= + +def handler(req): + + """ + Right now, index serves everything. + + Hitting this URL means we've already cleared authn/authz + but we still need to use the token for all remote requests. + """ + + my_uri = req.uri + + # apache.log_error("cannot load /var/lib/cobbler/web.ss") + req.add_common_vars() + + # process form and qs data, if any + fs = util.FieldStorage(req) + form = {} + for x in fs.keys(): + form[x] = str(fs.get(x,'default')) + + form["REMOTE_ADDR"] = req.subprocess_env.get("REMOTE_ADDR",None) + form["REMOTE_MAC"] = req.subprocess_env.get("HTTP_X_RHN_PROVISIONING_MAC_0",None) + + # instantiate a CobblerWeb object + cw = CobblerSvc( + apache = apache, + server = "http://127.0.0.1/cobbler_api" + ) + + # check for a valid path/mode + # handle invalid paths gracefully + mode = form.get('op','index') + + func = getattr( cw, mode ) + content = func( **form ) + + # apache.log_error("%s:%s ... %s" % (my_user, my_uri, str(form))) + req.content_type = "text/plain" + req.write(content) + + return apache.OK + @@ -44,8 +44,9 @@ if __name__ == "__main__": tftp_cfg = "/tftpboot/pxelinux.cfg" tftp_images = "/tftpboot/images" rotpath = "/etc/logrotate.d" - cgipath = "/var/www/cgi-bin/cobbler" + # cgipath = "/var/www/cgi-bin/cobbler" modpython = "/var/www/cobbler/web" + modpythonsvc = "/var/www/cobbler/svc" setup( name="cobbler", version = VERSION, @@ -63,13 +64,15 @@ if __name__ == "__main__": scripts = ["scripts/cobbler", "scripts/cobblerd"], data_files = [ (modpython, ['scripts/index.py']), + (modpythonsvc, ['scripts/services.py']), # cgi files - (cgipath, ['scripts/findks.cgi', 'scripts/nopxe.cgi']), - (cgipath, ['scripts/install_trigger.cgi']), + # (cgipath, ['scripts/nopxe.cgi']), + # (cgipath, ['scripts/install_trigger.cgi']), # miscellaneous config files (rotpath, ['config/cobblerd_rotate']), (wwwconf, ['config/cobbler.conf']), + (wwwconf, ['config/cobbler_svc.conf']), (cobpath, ['config/cobbler_hosts']), (etcpath, ['config/modules.conf']), (etcpath, ['config/users.digest']), |