diff options
-rw-r--r-- | CHANGELOG | 3 | ||||
-rw-r--r-- | cobbler.spec | 4 | ||||
-rw-r--r-- | cobbler/action_hardlink.py | 56 | ||||
-rw-r--r-- | cobbler/action_sync.py | 3 | ||||
-rw-r--r-- | cobbler/api.py | 5 | ||||
-rw-r--r-- | cobbler/collection.py | 1 | ||||
-rw-r--r-- | cobbler/collection_distros.py | 2 | ||||
-rw-r--r-- | cobbler/collection_images.py | 2 | ||||
-rw-r--r-- | cobbler/collection_profiles.py | 2 | ||||
-rw-r--r-- | cobbler/collection_repos.py | 4 | ||||
-rw-r--r-- | cobbler/collection_systems.py | 2 | ||||
-rw-r--r-- | cobbler/modules/cli_misc.py | 17 | ||||
-rw-r--r-- | cobbler/modules/scm_track.py | 62 | ||||
-rw-r--r-- | cobbler/remote.py | 11 | ||||
-rw-r--r-- | cobbler/settings.py | 2 | ||||
-rw-r--r-- | installer_templates/settings.template | 9 | ||||
-rw-r--r-- | kickstarts/legacy.ks | 2 | ||||
-rw-r--r-- | kickstarts/sample.ks | 3 | ||||
-rw-r--r-- | kickstarts/sample_end.ks | 2 | ||||
-rw-r--r-- | setup.py | 8 | ||||
-rw-r--r-- | snippets/keep_ssh_host_keys | 37 | ||||
-rw-r--r-- | snippets/log_ks_post | 2 | ||||
-rw-r--r-- | snippets/log_ks_pre | 12 | ||||
-rw-r--r-- | webui_templates/paginate.tmpl | 2 |
24 files changed, 246 insertions, 7 deletions
@@ -20,6 +20,9 @@ Cobbler CHANGELOG - (BUGF) sleep 5 seconds between power cycling systems - (FEAT) support for /usr/bin/cobbler-register - (FEAT) web UI search feature +- (FEAT) very simple "cobbler hardlink" command to optimize space in /var/www/cobbler +- (FEAT) new "change" trigger that runs on all adds/edits/removes/syncs +- (FEAT) an SCM trigger that knows about /var/lib/cobbler kickstarts, snippets, and config directories, and audits them for changes - Tue Mar 3 2009 - 1.4.3 - (BUGF) fix OMAPI support's (note: deprecated) usage of subprocess diff --git a/cobbler.spec b/cobbler.spec index 73a39f14..cb2f0cbe 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -220,6 +220,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %dir /var/lib/cobbler/kickstarts/ %dir /var/lib/cobbler/backup/ %dir /var/lib/cobbler/triggers +%dir /var/lib/cobbler/triggers/change %dir /var/lib/cobbler/triggers/add %dir /var/lib/cobbler/triggers/add/distro %dir /var/lib/cobbler/triggers/add/distro/pre @@ -273,6 +274,9 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %config(noreplace) /var/lib/cobbler/snippets/post_s390_reboot %config(noreplace) /var/lib/cobbler/snippets/redhat_register %config(noreplace) /var/lib/cobbler/snippets/cobbler_register +%config(noreplace) /var/lib/cobbler/snippets/keep_ssh_host_keys +%config(noreplace) /var/lib/cobbler/snippets/log_ks_pre +%config(noreplace) /var/lib/cobbler/snippets/log_ks_post /var/lib/cobbler/elilo-3.8-ia64.efi /var/lib/cobbler/menu.c32 /var/lib/cobbler/yaboot-1.3.14 diff --git a/cobbler/action_hardlink.py b/cobbler/action_hardlink.py new file mode 100644 index 00000000..6e17e357 --- /dev/null +++ b/cobbler/action_hardlink.py @@ -0,0 +1,56 @@ +""" +Hard links cobbler content together to save space. + +Copyright 2009, Red Hat, Inc +Michael DeHaan <mdehaan@redhat.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +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., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA +""" + +import os +import utils +from cexceptions import * + +class HardLinker: + + def __init__(self,config): + """ + Constructor + """ + #self.config = config + #self.api = config.api + #self.settings = config.settings() + pass + + def run(self): + """ + Simply hardlinks directories that are cobbler managed. + This is a /very/ simple command but may grow more complex + and intelligent over time. + """ + + # FIXME: if these directories become configurable some + # changes will be required here. + + if not os.path.exists("/usr/sbin/hardlink"): + raise CX("please install 'hardlink' (/usr/sbin/hardlink) to use this feature") + + print "now hardlinking to save space, this may take some time." + + rc = os.system("/usr/sbin/hardlink -c -v /var/www/cobbler/ks_mirror /var/www/cobbler/repo_mirror") + + return rc + diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index 78f3c3d5..76211418 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -141,7 +141,10 @@ class BootSync: # run post-triggers if self.verbose: print "- running post-sync triggers" + utils.run_triggers(self.api, None, "/var/lib/cobbler/triggers/sync/post/*") + utils.run_triggers(self.api, None, "/var/lib/cobbler/triggers/change/*") + return True def clean_trees(self): diff --git a/cobbler/api.py b/cobbler/api.py index ffc62644..c36d442a 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -36,6 +36,7 @@ import action_replicate import action_acl import action_report import action_power +import action_hardlink from cexceptions import * import sub_process import module_loader @@ -681,6 +682,10 @@ class BootAPI: iso=iso, profiles=profiles, systems=systems, tempdir=tempdir, distro=distro, standalone=standalone, source=source ) + def hardlink(self): + linker = action_hardlink.HardLinker(self._config) + return linker.run() + def replicate(self, cobbler_master = None, sync_all=False, sync_kickstarts=False, sync_trees=False, sync_repos=False, sync_triggers=False, systems=False): """ Pull down metadata from a remote cobbler server that is a master to this server. diff --git a/cobbler/collection.py b/cobbler/collection.py index 3bc15cc7..50c07094 100644 --- a/cobbler/collection.py +++ b/cobbler/collection.py @@ -284,6 +284,7 @@ class Collection(serializable.Serializable): # save the tree, so if neccessary, scripts can examine it. if with_triggers: + self._run_triggers(self.api, ref, "/var/lib/cobbler/triggers/change/*") self._run_triggers(self.api, ref,"/var/lib/cobbler/triggers/add/%s/post/*" % self.collection_type()) diff --git a/cobbler/collection_distros.py b/cobbler/collection_distros.py index ae75f407..bcb23a07 100644 --- a/cobbler/collection_distros.py +++ b/cobbler/collection_distros.py @@ -76,6 +76,8 @@ class Distros(collection.Collection): self.log_func("deleted distro %s" % name) if with_triggers: self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/distro/post/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/change/*") + # look through all mirrored directories and find if any directory is holding # this particular distribution's kernel and initrd diff --git a/cobbler/collection_images.py b/cobbler/collection_images.py index 2dfeeac1..1bf52069 100644 --- a/cobbler/collection_images.py +++ b/cobbler/collection_images.py @@ -69,6 +69,8 @@ class Images(collection.Collection): self.log_func("deleted repo %s" % name) if with_triggers: self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/image/post/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/change/*") + return True if with_delete and not self.api.is_cobblerd: diff --git a/cobbler/collection_profiles.py b/cobbler/collection_profiles.py index bb27dea4..5ca9d4be 100644 --- a/cobbler/collection_profiles.py +++ b/cobbler/collection_profiles.py @@ -74,6 +74,8 @@ class Profiles(collection.Collection): self.log_func("deleted profile %s" % name) if with_triggers: self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/profile/post/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/change/*") + if with_delete and not self.api.is_cobblerd: self.api._internal_cache_update("profile", name, remove=True) diff --git a/cobbler/collection_repos.py b/cobbler/collection_repos.py index 3b2c1c20..eef82b8c 100644 --- a/cobbler/collection_repos.py +++ b/cobbler/collection_repos.py @@ -65,7 +65,9 @@ class Repos(collection.Collection): self.log_func("deleted repo %s" % name) if with_triggers: self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/repo/post/*") - + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/change/*") + + path = "/var/www/cobbler/repo_mirror/%s" % obj.name if os.path.exists(path): utils.rmtree(path) diff --git a/cobbler/collection_systems.py b/cobbler/collection_systems.py index 5a628248..16576802 100644 --- a/cobbler/collection_systems.py +++ b/cobbler/collection_systems.py @@ -62,7 +62,9 @@ class Systems(collection.Collection): self.log_func("deleted system %s" % name) if with_triggers: self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/system/post/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/change/*") + if with_delete and not self.api.is_cobblerd: self.api._internal_cache_update("system", name, remove=True) diff --git a/cobbler/modules/cli_misc.py b/cobbler/modules/cli_misc.py index f83b5c8c..a4fc4708 100644 --- a/cobbler/modules/cli_misc.py +++ b/cobbler/modules/cli_misc.py @@ -39,6 +39,21 @@ HELP_FORMAT = commands.HELP_FORMAT ######################################################## +class HardLinkFunction(commands.CobblerFunction): + def help_me(self): + return HELP_FORMAT % ("cobbler hardlink","") + + def command_name(self): + return "hardlink" + + def add_options(self, p, args): + pass + + def run(self): + self.api.hardlink() + +######################################################## + class CheckFunction(commands.CobblerFunction): def help_me(self): @@ -359,7 +374,7 @@ def cli_functions(api): ListFunction(api), StatusFunction(api), SyncFunction(api), RepoSyncFunction(api), ValidateKsFunction(api), ReplicateFunction(api), AclFunction(api), - VersionFunction(api) + VersionFunction(api), HardLinkFunction(api) ] return [] diff --git a/cobbler/modules/scm_track.py b/cobbler/modules/scm_track.py new file mode 100644 index 00000000..dd9ef687 --- /dev/null +++ b/cobbler/modules/scm_track.py @@ -0,0 +1,62 @@ +import distutils.sysconfig +import sys +import os +import traceback +from cobbler.cexceptions import * +import os +import sub_process +import sys +#import xmlrpclib +import cobbler.module_loader as module_loader + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + +def register(): + # this pure python trigger acts as if it were a legacy shell-trigger, but is much faster. + # the return of this method indicates the trigger type + return "/var/lib/cobbler/triggers/change/*" + +def scall(args): + op = sub_process.Popen(args, shell=False, stdout=sub_process.PIPE, stderr=sub_process.PIPE) + op.communicate() + + +def run(api,args): + + + settings = api.settings() + + scm_track_enabled = str(settings.scm_track_enabled).lower() + mode = str(settings.scm_track_mode).lower() + + if scm_track_enabled not in [ "y", "yes", "1", "true" ]: + # feature disabled + return 0 + + if mode == "git": + + old_dir = os.getcwd() + os.chdir("/var/lib/cobbler") + if os.getcwd() != "/var/lib/cobbler": + raise "danger will robinson" + + if not os.path.exists("/var/lib/cobbler/.git"): + scall(["git","init"]) + + # FIXME: if we know the remote user of an XMLRPC call + # use them as the author + + scall(["git","add","config"]) + scall(["git","add","kickstarts"]) + scall(["git","add","snippets"]) + + scall(["git","commit","-m",'API update',"--author","'cobbler <root@localhost.localdomain>'"]) + + os.chdir(old_dir) + + return 0 + + else: + raise CX("currently unsupported SCM type: %s" % mode) diff --git a/cobbler/remote.py b/cobbler/remote.py index 6fdb96fe..eaf3d41d 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -1138,6 +1138,17 @@ class CobblerXMLRPCInterface: self.check_access(token,"sync") return self.api.sync() + def hardlink(self,token): + """ + Hardlink trees and repos to save disk space. Caution: long + running op. Until we have a task engine, this may lock other + folks out of the web app, so use wisely. It may also be timeout + prone. + """ + self._log("hardlink",token=token) + self.check_access(token,"hardlink") + return self.api.hardlink() + def new_distro(self,token): """ Creates a new (unconfigured) distro object. It works something like diff --git a/cobbler/settings.py b/cobbler/settings.py index bf94d24d..2422341a 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -105,6 +105,8 @@ DEFAULTS = { "ris_linuxd_dir" : "/tftpboot/drivers", "ris_linuxd_flags" : "", "run_install_triggers" : 1, + "scm_track_enabled" : 0, + "scm_track_mode" : "git", "server" : "127.0.0.1", "snippetsdir" : "/var/lib/cobbler/snippets", "tftpd_bin" : "/usr/sbin/in.tftpd", diff --git a/installer_templates/settings.template b/installer_templates/settings.template index d97f32c8..51457a64 100644 --- a/installer_templates/settings.template +++ b/installer_templates/settings.template @@ -311,6 +311,15 @@ register_new_installs: 0 # uses a logging trigger to audit install progress. run_install_triggers: 1 +# enables a trigger which version controls all changes to /var/lib/cobbler +# when add, edit, or sync events are performed. This can be used +# to revert to previous database versions, generate RSS feeds, or for +# other auditing or backup purposes. git is the recommend SCM +# for use with this feature. + +scm_track_enabled: 0 +scm_track_mode: "git" + # this is the address of the cobbler server -- as it is used # by systems during the install process, it must be the address # or hostname of the system as those systems can see the server. diff --git a/kickstarts/legacy.ks b/kickstarts/legacy.ks index 70918985..86e31d8a 100644 --- a/kickstarts/legacy.ks +++ b/kickstarts/legacy.ks @@ -38,6 +38,7 @@ zerombr autopart %pre +$SNIPPET('log_ks_pre') $kickstart_start $SNIPPET('pre_install_network_config') $SNIPPET('pre_anamon') @@ -45,6 +46,7 @@ $SNIPPET('pre_anamon') %packages %post +$SNIPPET('log_ks_post') # Begin yum configuration $yum_config_stanza # End yum configuration diff --git a/kickstarts/sample.ks b/kickstarts/sample.ks index f41470d9..15a0b329 100644 --- a/kickstarts/sample.ks +++ b/kickstarts/sample.ks @@ -41,6 +41,7 @@ autopart %pre +$SNIPPET('log_ks_pre') $kickstart_start $SNIPPET('pre_install_network_config') # Enable installation monitoring @@ -50,7 +51,7 @@ $SNIPPET('pre_anamon') $SNIPPET('func_install_if_enabled') %post - +$SNIPPET('log_ks_post') # Start yum configuration $yum_config_stanza # End yum configuration diff --git a/kickstarts/sample_end.ks b/kickstarts/sample_end.ks index 21148366..0112fc0e 100644 --- a/kickstarts/sample_end.ks +++ b/kickstarts/sample_end.ks @@ -44,6 +44,7 @@ zerombr autopart %pre +$SNIPPET('log_ks_pre') $kickstart_start $SNIPPET('pre_install_network_config') # Enable installation monitoring @@ -55,6 +56,7 @@ $SNIPPET('func_install_if_enabled') %end %post +$SNIPPET('log_ks_post') # Start yum configuration $yum_config_stanza # End yum configuration @@ -164,7 +164,7 @@ if __name__ == "__main__": # for --version support across distros (libpath, ['config/version']), - + # bootloaders and syslinux support files (libpath, ['loaders/elilo-3.8-ia64.efi']), (libpath, ['loaders/menu.c32']), @@ -251,6 +251,9 @@ if __name__ == "__main__": (snippetpath, ['snippets/post_s390_reboot']), (snippetpath, ['snippets/redhat_register']), (snippetpath, ['snippets/cobbler_register']), + (snippetpath, ['snippets/keep_ssh_host_keys']), + (snippetpath, ['snippets/log_ks_pre']), + (snippetpath, ['snippets/log_ks_post']), # documentation (manpath, ['docs/cobbler.1.gz']), @@ -356,7 +359,8 @@ if __name__ == "__main__": ("%s/install/pre" % trigpath, []), ("%s/install/post" % trigpath, []), ("%s/sync/pre" % trigpath, []), - ("%s/sync/post" % trigpath, []) + ("%s/sync/post" % trigpath, []), + ("%s/change" % trigpath, []) ], description = SHORT_DESC, long_description = LONG_DESC diff --git a/snippets/keep_ssh_host_keys b/snippets/keep_ssh_host_keys index 93a6fadb..527c9926 100644 --- a/snippets/keep_ssh_host_keys +++ b/snippets/keep_ssh_host_keys @@ -8,14 +8,17 @@ keys_found=no insmod /lib/jbd.o insmod /lib/ext3.o +mkdir -p /tmp/ssh + drives=$(list-harddrives | awk '{print $1}') for disk in $drives; do DISKS="$DISKS $(fdisk -l /dev/$disk | awk '/^\/dev/{print $1}')" done +# Try to find the keys on ordinary partitions for disk in $DISKS; do name=$(basename $disk) - mkdir -p /tmp/$name /tmp/ssh + mkdir -p /tmp/$name mount $disk /tmp/$name [ $? -eq 0 ] || continue # Skip to the next partition if the mount fails @@ -30,6 +33,38 @@ for disk in $DISKS; do rm -r /tmp/$name done +# Try LVM if that didn't work +if [ "$keys_found" = "no" ]; then + lvm lvmdiskscan + vgs=$(lvm vgs | tail -n +2 | awk '{ print $1 }') + for vg in $vgs; do + # Activate any VG we found + lvm vgchange -ay $vg + done + + lvs=$(lvm lvs | tail -n +2 | awk '{ print "/dev/" $2 "/" $1 }') + for lv in $lvs; do + tmpdir=$(mktemp -d findkeys.XXXXXX) + mkdir -p /tmp/${tmpdir} + mount $lv /tmp/${tmpdir} || continue # Skip to next volume if this fails + + # Let's see if the keys are in there + if [ -d /tmp/${tmpdir}/etc/ssh ]; then + cp -a /tmp/${tmpdir}/etc/ssh/ssh_host* /tmp/ssh/ + keys_found="yes" + umount /tmp/${tmpdir} + break # We're done! + fi + umount /tmp/${tmpdir} + rm -r /tmp/${tmpdir} + done + + # And clean up.. + for vg in $vgs; do + lvm vgchange -an $vg + done +fi + # Loop until the ssh rpm is installed if [ "$keys_found" = "yes" ]; then while : ; do diff --git a/snippets/log_ks_post b/snippets/log_ks_post new file mode 100644 index 00000000..d1df0265 --- /dev/null +++ b/snippets/log_ks_post @@ -0,0 +1,2 @@ +set -x -v +exec 1>/root/ks-post.log 2>&1 diff --git a/snippets/log_ks_pre b/snippets/log_ks_pre new file mode 100644 index 00000000..fe71c592 --- /dev/null +++ b/snippets/log_ks_pre @@ -0,0 +1,12 @@ +set -x -v +exec 1>/tmp/ks-pre.log 2>&1 + +# Once root's homedir is there, copy over the log. +while : ; do + sleep 10 + if [ -d /mnt/sysimage/root ]; then + cp /tmp/ks-pre.log /mnt/sysimage/root/ + logger "Copied %pre section log to system" + break + fi +done & diff --git a/webui_templates/paginate.tmpl b/webui_templates/paginate.tmpl index 66bf6345..9f5803d3 100644 --- a/webui_templates/paginate.tmpl +++ b/webui_templates/paginate.tmpl @@ -1,4 +1,4 @@ -#if $page > 0 +#if $pages > 0 ## USAGE: # set global what="system" ## # include "/path/to/this/file" |