diff options
-rw-r--r-- | MANIFEST.in | 1 | ||||
-rw-r--r-- | cobbler.spec | 1 | ||||
-rw-r--r-- | cobbler/action_import.py | 8 | ||||
-rw-r--r-- | cobbler/action_reposync.py | 86 | ||||
-rw-r--r-- | cobbler/action_sync.py | 23 | ||||
-rw-r--r-- | cobbler/api.py | 24 | ||||
-rwxr-xr-x | cobbler/cobbler.py | 58 | ||||
-rw-r--r-- | cobbler/cobbler_msg.py | 4 | ||||
-rw-r--r-- | cobbler/collection_repos.py | 54 | ||||
-rw-r--r-- | cobbler/config.py | 16 | ||||
-rw-r--r-- | cobbler/item_profile.py | 20 | ||||
-rw-r--r-- | cobbler/item_repo.py | 104 | ||||
-rw-r--r-- | kickstart_fc6.ks | 41 |
13 files changed, 425 insertions, 15 deletions
diff --git a/MANIFEST.in b/MANIFEST.in index 26a4769..d447ab8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include COPYING_ELILO include elilo-3.6-ia64.efi include dhcp.template include kickstart_fc5.ks +include kickstart_fc6.ks include default.ks include default.pxe include cobbler.1.gz diff --git a/cobbler.spec b/cobbler.spec index cf84b52..cd0b673 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -10,6 +10,7 @@ Group: Applications/System Requires: python >= 2.3 Requires: httpd Requires: tftp-server +Requires: python-devel BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot BuildArch: noarch ExcludeArch: ppc diff --git a/cobbler/action_import.py b/cobbler/action_import.py index a2d76f9..11ec3b0 100644 --- a/cobbler/action_import.py +++ b/cobbler/action_import.py @@ -37,7 +37,7 @@ MATCH_LIST = ( ( "4/" , "/etc/cobbler/kickstart_fc5.ks" ), ( "5/" , "/etc/cobbler/kickstart_fc5.ks" ), ( "FC-5/" , "/etc/cobbler/kickstart_fc5.ks" ), - ( "FC-6/" , "/etc/cobbler/kickstart_fc5.ks" ), + ( "FC-6/" , "/etc/cobbler/kickstart_fc6.ks" ), ( "RHEL-4/" , "/etc/cobbler/kickstart_fc5.ks" ), ( "6/" , "/etc/cobbler/kickstart_fc5.ks" ), ( "5/" , "/etc/cobbler/kickstart_fc5.ks" ), @@ -88,7 +88,7 @@ class Importer: if self.mirror_name is None: raise cexceptions.CobblerException("import_failed","must specify --mirror-name") print "This will take a while..." - self.path = "/var/www/cobbler/localmirror/%s" % self.mirror_name + self.path = "/var/www/cobbler/ks_mirror/%s" % self.mirror_name try: os.makedirs(self.path) except: @@ -97,7 +97,7 @@ class Importer: spacer = "" if not self.mirror.startswith("rsync://"): spacer = ' -e "ssh" ' - cmd = "rsync -a %s %s /var/www/cobbler/localmirror/%s --exclude-from=/etc/cobbler/rsync.exclude --delete --delete-excluded --progress" % (spacer, self.mirror, self.mirror_name) + cmd = "rsync -a %s %s /var/www/cobbler/ks_mirror/%s --exclude-from=/etc/cobbler/rsync.exclude --delete --delete-excluded --progress" % (spacer, self.mirror, self.mirror_name) sub_process.call(cmd,shell=True) update_file = open(os.path.join(self.path,"update.sh"),"w+") update_file.write("#!/bin/sh") @@ -157,7 +157,7 @@ class Importer: for profile in self.profiles: distro = self.distros.find(profile.name) kpath = distro.kernel - if not kpath.startswith("/var/www/cobbler/localmirror/"): + if not kpath.startswith("/var/www/cobbler/ks_mirror/"): continue for entry in MATCH_LIST: (part, kickstart) = entry diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py new file mode 100644 index 0000000..8089900 --- /dev/null +++ b/cobbler/action_reposync.py @@ -0,0 +1,86 @@ +""" +Builds out and synchronizes yum repo mirrors. +Initial support for rsync, perhaps reposync coming later. + +Copyright 2006, 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 yaml # Howell-Clark version +import sub_process +import sys + +import utils +import cobbler_msg +import cexceptions +import traceback +import errno + + + +class RepoSync: + """ + Handles conversion of internal state to the tftpboot tree layout + """ + + def __init__(self,config): + """ + Constructor + """ + self.verbose = True + self.config = config + self.distros = config.distros() + self.profiles = config.profiles() + self.systems = config.systems() + self.settings = config.settings() + self.repos = config.repos() + + def run(self,dryrun=False,verbose=True): + """ + Syncs the current repo configuration file with the filesystem. + """ + + self.verbose = verbose + self.dryrun = dryrun + for repo in self.repos: + print "considering: %s" % repo + repo_path = os.path.join(repo.root, repo.name) + mirror = repo.mirror + if not os.path.isdir(repo_path): + try: + os.makedirs(repo_path) + except OSError, oe: + if not oe.errno == 17: # already exists, constant for this? + raise cexceptions.CobblerException("no_create", repo_path) + if mirror.startswith("rsync://") or mirror.startswith("ssh://"): + self.do_rsync_repo(repo) + else: + raise cexceptions.CobblerException("no_mirror") + + return True + + def do_rsync_repo(self,repo): + if not repo.keep_updated: + print "- %s is set to not be updated" + return True + print "imagine an rsync happened here, and that it was amazing..." + dest_path = os.path.join(self.settings.webdir, "repo_mirror", repo.name) + spacer = "" + if repo.mirror.find("ssh://") != -1: + spacer = "-e ssh" + cmd = "rsync -av %s --exclude=debug/ %s %s" % (spacer, repo.mirror, dest_path) + print "executing: %s" % cmd + rc = sub_process.call(cmd, shell=True) + + diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 31784f8..93f428a 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -44,7 +44,7 @@ class BootSync: self.profiles = config.profiles() self.systems = config.systems() self.settings = config.settings() - + self.repos = config.repos() def run(self,dryrun=False,verbose=True): """ @@ -191,7 +191,10 @@ class BootSync: if os.path.isfile(path): self.rmfile(path) if os.path.isdir(path): - if not x == "localmirror": + if not x in ["localmirror","repo_mirror","ks_mirror"] : + # new versions of cobbler use repo_mirror for repos and ks_mirror for + # basic kickstart tree core data. older versions just used "local_mirror" so we + # do have to leave the "localmirror" in there to avoid breaking users on upgrades self.rmtree(path) self.rmtree(os.path.join(self.settings.tftpboot, "pxelinux.cfg")) self.rmtree(os.path.join(self.settings.tftpboot, "images")) @@ -278,12 +281,27 @@ class BootSync: distro.ks_meta, g.ks_meta, )) + meta["yum_repo_stanza"] = self.generate_repo_stanza(g) self.apply_template(kickstart_path, meta, dest) except: traceback.print_exc() # leave this in, for now... msg = "err_kickstart2" raise cexceptions.CobblerException(msg,kickstart_path,dest) + def generate_repo_stanza(self, profile): + # returns the line of repo additions (Anaconda supports in FC-6 and later) that adds + # the list of repos to things that Anaconda can install from. This corresponds + # will replace "TEMPLATE::yum_repo_stanza" in a cobbler kickstart file. + buf = "" + repos = profile.repos.split(" ") + for r in repos: + repo = self.repos.find(r) + if repo is None: + raise cexceptions.CobblerException("no_repo",r) + http_url = "http://%s/repo_mirror/%s" % (self.settings.server, repo.name) + buf = buf + "repo --name=%s --baseurl=%s\n" % (repo.name, http_url) + return buf + def validate_kickstarts_per_system(self): """ PXE provisioning needs kickstarts evaluated per system. @@ -312,6 +330,7 @@ class BootSync: profile.ks_meta, s.ks_meta )) + meta["yum_repo_stanza"] = self.generate_repo_stanza(profile) self.apply_template(kickstart_path, meta, dest) except: msg = "err_kickstart2" diff --git a/cobbler/api.py b/cobbler/api.py index 720bcbc..2ebb2e6 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -20,6 +20,7 @@ import action_sync import action_check import action_enchant import action_import +import action_reposync import cexceptions class BootAPI: @@ -51,20 +52,24 @@ class BootAPI: """ return self._config.profiles() - def distros(self): """ Return the current list of distributions """ return self._config.distros() + def repos(self): + """ + Return the current list of repos + """ + return self._config.repos() + def settings(self): """ Return the application configuration """ return self._config.settings() - def new_system(self): """ Return a blank, unconfigured system, unattached to a collection @@ -85,6 +90,12 @@ class BootAPI: """ return self._config.new_profile() + def new_repo(self): + """ + Create a blank, unconfigured repo, unattached to a collection + """ + return self._config.new_repo() + def check(self): """ See if all preqs for network booting are valid. This returns @@ -97,7 +108,6 @@ class BootAPI: check = action_check.BootCheck(self._config) return check.run() - def sync(self,dryrun=True): """ Take the values currently written to the configuration files in @@ -108,6 +118,14 @@ class BootAPI: sync = action_sync.BootSync(self._config) return sync.run(dryrun=dryrun) + def reposync(self,dryrun=True): + """ + Take the contents of /var/lib/cobbler/repos and update them -- + or create the initial copy if no contents exist yet. + """ + sync = action_reposync.RepoSync(self._config) + return sync.run(dryrun=dryrun) + def enchant(self,address,profile,systemdef): """ Re-kickstart a running system. diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index 90f148d..6e41b38 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -53,6 +53,12 @@ class BootCLI: 'delete' : self.system_remove, 'remove' : self.system_remove, } + self.commands['repo'] = { + 'add' : self.repo_edit, + 'edit' : self.repo_edit, + 'delete' : self.repo_remove, + 'remove' : self.repo_remove + } self.commands['toplevel'] = { 'check' : self.check, 'list' : self.list, @@ -62,7 +68,10 @@ class BootCLI: 'profile' : self.profile, 'systems' : self.system, 'system' : self.system, + 'repos' : self.repo, + 'repo' : self.repo, 'sync' : self.sync, + 'reposync' : self.reposync, 'import' : self.import_tree, 'enchant' : self.enchant, 'transmogrify' : self.enchant, @@ -96,11 +105,13 @@ class BootCLI: all.append(self.api.distros()) elif a == '--settings': all.append(self.api.settings()) + elif a == '--repos': + all.append(self.api.repos()) else: terms.extend(a) if len(all) == 0: all = [ self.api.settings(), self.api.distros(), - self.api.profiles(), self.api.systems() ] + self.api.profiles(), self.api.systems(), self.api.repos() ] for item in all: print item.printable() @@ -138,6 +149,16 @@ class BootCLI: on_ok = lambda: True return self.apply_args(args,commands,on_ok) + def repo_remove(self,args): + """ + Delete a repo: 'cobbler repo remove --name='foo' + """ + commands = { + '--name' : lambda(a): self.api.repos().remove(a), + '--repo' : lambda(a): self.api.repos().remove(a) + } + on_ok = lambda: True + return self.apply_args(args,commands,on_ok) def enchant(self,args): """ @@ -230,11 +251,25 @@ class BootCLI: '--virt-file-size' : lambda(a) : profile.set_virt_file_size(a), '--xen-ram' : lambda(a) : profile.set_virt_ram(a), '--virt-ram' : lambda(a) : profile.set_virt_ram(a), - '--ksmeta' : lambda(a) : profile.set_ksmeta(a) + '--ksmeta' : lambda(a) : profile.set_ksmeta(a), + '--repos' : lambda(a) : profile.set_repos(a) } on_ok = lambda: self.api.profiles().add(profile) return self.apply_args(args,commands,on_ok) + def repo_edit(self,args): + """ + Create/edit a repo: 'cobbler repo add --name='foo' ... + """ + repo = self.api.new_repo() + commands = { + '--name' : lambda(a): repo.set_name(a), + '--mirror' : lambda(a): repo.set_mirror(a), + '--keep-updated' : lambda(a): repo.set_keep_updated(a), + '--root' : lambda(a): repo.set_root(a) + } + on_ok = lambda: self.api.repos().add(repo) + return self.apply_args(args,commands,on_ok) def distro_edit(self,args): """ @@ -291,7 +326,6 @@ class BootCLI: raise cexceptions.CobblerException("unknown_cmd", args[0]) return True - def sync(self, args): """ Sync the config file with the system config: 'cobbler sync [--dryrun]' @@ -303,6 +337,17 @@ class BootCLI: status = self.api.sync(dryrun=False) return status + def reposync(self, args): + """ + Sync the repo-specific portions of the config with the filesystem. + 'cobbler reposync'. Intended to be run on cron. + """ + status = None + if args is not None and ("--dryrun" in args or "-n" in args): + status = self.api.reposync(dryrun=True) + else: + status = self.api.reposync(dryrun=False) + return status def check(self,args): """ @@ -318,7 +363,6 @@ class BootCLI: print "#%d: %s" % (i,x) return False - def distro(self,args): """ Handles any of the 'cobbler distro' subcommands @@ -337,6 +381,12 @@ class BootCLI: """ return self.curry_args(args, self.commands['system']) + def repo(self,args): + """ + Handles any of the 'cobbler repo' subcommands + """ + return self.curry_args(args, self.commands['repo']) + def main(): """ CLI entry point diff --git a/cobbler/cobbler_msg.py b/cobbler/cobbler_msg.py index 0181439..10d16a1 100644 --- a/cobbler/cobbler_msg.py +++ b/cobbler/cobbler_msg.py @@ -73,8 +73,10 @@ _msg_table = { "no_bootloader" : "missing 1 or more bootloader files listed in /var/lib/cobbler/settings", "no_tftpd" : "cobbler couldn't find tftpd, try 'yum install tftpd'", "no_dir" : "cobbler couldn't find %s, please create it", + "no_mirror" : "mirror URL is not valid", "chg_attrib" : "need to change field '%s' value to '%s' in file '%s'", "no_exist" : "file %s does not exist", + "no_exist2" : "path %s does not exist", "no_line" : "file '%s' should have a line '%s' somewhere", "no_next_server" : "file '%s' should have a next-server line", "no_dir2" : "can't find %s for %s as referenced in /var/lib/cobbler/settings", @@ -89,6 +91,8 @@ _msg_table = { "orphan_system" : "Removing this profile would break system '%s'", "delete_nothing" : "can't delete something that doesn't exist", "no_distro" : "distro does not exist", + "no_repo" : "repository %s referenced in profile cannot be found", + "no_repos" : "one of the listed repositories is not defined in cobbler", "no_profile" : "profile does not exist", "no_kickstart" : "kickstart must be an absolute path, or an http://, ftp:// or nfs:// URL", "no_kernel" : "cannot find kernel file", diff --git a/cobbler/collection_repos.py b/cobbler/collection_repos.py new file mode 100644 index 0000000..0ae9db3 --- /dev/null +++ b/cobbler/collection_repos.py @@ -0,0 +1,54 @@ +""" +Repositories in cobbler are way to create a local mirror of a yum repository. +When used in conjunction with a mirrored kickstart tree (see "cobbler import") +outside bandwidth needs can be reduced and/or eliminated. + +Copyright 2006, 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 item_repo as repo +import utils +import collection +import cexceptions + +TESTMODE = False + +#-------------------------------------------- + +class Repos(collection.Collection): + + def collection_type(self): + return "repository" + + def factory_produce(self,config,seed_data): + """ + Return a system forged from seed_data + """ + return repo.Repo(config).from_datastruct(seed_data) + + def filename(self): + """ + Return a filename for System serialization + """ + if TESTMODE: + return "/var/lib/cobbler/test/repos" + else: + return "/var/lib/cobbler/repos" + + def remove(self,name): + """ + Remove element named 'name' from the collection + """ + if self.find(name): + del self.listing[name] + return True + raise cexceptions.CobblerException("delete_nothing") + diff --git a/cobbler/config.py b/cobbler/config.py index 4bcc31e..44a7ab1 100644 --- a/cobbler/config.py +++ b/cobbler/config.py @@ -18,10 +18,12 @@ import weakref import item_distro as distro import item_profile as profile import item_system as system +import item_repo as repo import collection_distros as distros import collection_profiles as profiles import collection_systems as systems +import collection_repos as repos import settings import serializer @@ -37,11 +39,13 @@ class Config: self._profiles = profiles.Profiles(weakref.proxy(self)) self._systems = systems.Systems(weakref.proxy(self)) self._settings = settings.Settings() # not a true collection + self._repos = repos.Repos(weakref.proxy(self)) self._classes = [ self._distros, self._profiles, self._systems, self._settings, + self._repos ] self.file_check() @@ -69,6 +73,12 @@ class Config: """ return self._settings + def repos(self): + """ + Return the definitive copy of the Repos collection + """ + return self._repos + def new_distro(self): """ Create a new distro object with a backreference to this object @@ -87,6 +97,12 @@ class Config: """ return profile.Profile(weakref.proxy(self)) + def new_repo(self): + """ + Create a new mirror to keep track of... + """ + return repo.Repo(weakref.proxy(self)) + def clear(self): """ Forget about all loaded configuration data diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py index 311b54f..d293c04 100644 --- a/cobbler/item_profile.py +++ b/cobbler/item_profile.py @@ -39,6 +39,7 @@ class Profile(item.Item): self.virt_file_size = 5 # GB. 5 = Decent _minimum_ default for FC5. self.virt_ram = 512 # MB. Install with 256 not likely to pass self.virt_paravirt = True # hvm support is *NOT* in Koan (now) + self.repos = [] # names of cobbler repo definitions def from_datastruct(self,seed_data): """ @@ -49,7 +50,8 @@ class Profile(item.Item): self.kickstart = self.load_item(seed_data,'kickstart') self.kernel_options = self.load_item(seed_data,'kernel_options') self.ks_meta = self.load_item(seed_data,'ks_meta') - + self.repos = self.load_item(seed_data,'repos', "") + # virt specific self.virt_name = self.load_item(seed_data,'virt_name') self.virt_ram = self.load_item(seed_data,'virt_ram') @@ -77,6 +79,18 @@ class Profile(item.Item): return True raise cexceptions.CobblerException("no_distro") + def set_repos(self,repos): + repolist = repos.split(" ") + ok = True + for r in repolist: + if not self.config.repos().find(r): + ok = False + break + if ok: + self.repos = repolist + else: + raise cexceptions.CobblerException("no_repos") + def set_kickstart(self,kickstart): """ Sets the kickstart. This must be a NFS, HTTP, or FTP URL. @@ -187,7 +201,8 @@ class Profile(item.Item): 'virt_file_size' : self.virt_file_size, 'virt_ram' : self.virt_ram, 'virt_paravirt' : self.virt_paravirt, - 'ks_meta' : self.ks_meta + 'ks_meta' : self.ks_meta, + 'repos' : " ".join(self.repos) } def printable(self,id): @@ -203,5 +218,6 @@ class Profile(item.Item): buf = buf + "virt file size : %s\n" % self.virt_file_size buf = buf + "virt ram : %s\n" % self.virt_ram buf = buf + "virt paravirt : %s\n" % self.virt_paravirt + buf = buf + "repos : %s\n" % " ".join(self.repos) return buf diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py new file mode 100644 index 0000000..e911c86 --- /dev/null +++ b/cobbler/item_repo.py @@ -0,0 +1,104 @@ +""" +A Cobbler repesentation of a yum repo. + +Copyright 2006, 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 utils +import item +import cexceptions + +# TODO: if distribution is detected FC6 or greater, auto-add the mirror stanza +# to the kickstart. + +class Repo(item.Item): + + def __init__(self,config): + self.config = config + self.clear() + + def clear(self): + self.name = None # is required + self.mirror = None # is required + self.keep_updated = 1 # has reasonable defaults + self.root = "/var/www/cobbler/repo_mirror" # has reasonable defaults + + def from_datastruct(self,seed_data): + self.name = self.load_item(seed_data,'name') + self.mirror = self.load_item(seed_data,'mirror') + self.keep_updated = self.load_item(seed_data, 'keep_updated') + self.root = self.load_item(seed_data, 'root') + return self + + def set_name(self,name): + """ + A name can be anything. It's a string, though best values are something like "fc6extras" + or "myrhel4stuff" + """ + self.name = name # we check it add time, but store the original value. + return True + + def set_mirror(self,mirror): + """ + A repo is (initially, as in right now) is something that can be rsynced. + reposync/repotrack integration over HTTP might come later. + """ + if mirror.startswith("rsync://") or mirror.startswith("ssh://"): + self.mirror = mirror + return True + else: + raise cexceptions.CobblerException("no_mirror") + + def set_keep_updated(self,keep_updated): + """ + This allows the user to disable updates to a particular repo for whatever reason. + """ + if not keep_updated.lower() in ["yes","y","yup","yeah","1"]: + self.keep_updated = False + else: + self.keep_updated = True + return True + + def set_root(self,root): + """ + Sets the directory to mirror in. Directory will include the name of the repo off of the + given root. By default, uses /var/www/cobbler/repomirror/. + """ + if os.path.isdir(root): + self.root = root + return True + raise cexceptions.CobblerException("no_exist2",root) + + def is_valid(self): + """ + A repo is valid if it has a name and a mirror URL + """ + if self.name is None: + return False + if self.mirror is None: + return False + return True + + def to_datastruct(self): + return { + 'name' : self.name, + 'mirror' : self.mirror, + 'keep_updated' : self.keep_updated, + 'root' : self.root + } + + def printable(self,id): + buf = "repo %-4s : %s\n" % (id, self.name) + buf = buf + "mirror : %s\n" % self.mirror + buf = buf + "keep updated : %s\n" % self.keep_updated + buf = buf + "root : %s\n" % self.root + return buf + diff --git a/kickstart_fc6.ks b/kickstart_fc6.ks new file mode 100644 index 0000000..13e5cf0 --- /dev/null +++ b/kickstart_fc6.ks @@ -0,0 +1,41 @@ +#platform=x86, AMD64, or Intel EM64T +# System authorization information +auth --useshadow --enablemd5 +# System bootloader configuration +bootloader --location=mbr +# Partition clearing information +clearpart --all --initlabel +# Use text mode install +text +# Firewall configuration +firewall --enabled +# Run the Setup Agent on first boot +firstboot --disable +# System keyboard +keyboard us +# System language +lang en_US +# Use network installation +url --url=TEMPLATE::tree +# If any cobbler repo definitions were referenced in the kickstart profile, include them here. +TEMPLATE::yum_repo_stanza +# Network information +network --bootproto=dhcp --device=eth0 --onboot=on +# Reboot after installation +reboot + +#Root password +rootpw --iscrypted $1$mF86/UHC$WvcIcX2t6crBz2onWxyac. +# SELinux configuration +selinux --disabled +# Do not configure the X Window System +skipx +# System timezone +timezone America/New_York +# Install OS instead of upgrade +install +# Clear the Master Boot Record +zerombr + +%packages + |