diff options
author | James Cammarata <jimi@sngx.net> | 2009-03-07 21:44:00 -0600 |
---|---|---|
committer | James Cammarata <jimi@sngx.net> | 2009-03-07 21:44:00 -0600 |
commit | d3fdc6d4965b02ce18346f067c14080115943a38 (patch) | |
tree | d2fa630d3f0e941acb7f5115c31ea91aabb4ae11 | |
parent | 54e462f3dca472d8bb8dc8a2b0a69c60ed9fe2ec (diff) | |
parent | 61db7baa541acc32b305ea6977a14ee8f5b3f470 (diff) | |
download | cobbler-d3fdc6d4965b02ce18346f067c14080115943a38.tar.gz cobbler-d3fdc6d4965b02ce18346f067c14080115943a38.tar.xz cobbler-d3fdc6d4965b02ce18346f067c14080115943a38.zip |
Merge branch 'devel' of git://git.fedorahosted.org/cobbler into ris-devel
Conflicts:
cobbler/utils.py
199 files changed, 10314 insertions, 3916 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4a347069 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +*.pyc +*.swp +*.tmp +dist +rpm-build +build +MANIFEST +TAGS + +# docs - ignore pod output +docs/*.gz +docs/*.html + +# Build output +cobbler/webui/master.py +config/modules.conf +config/settings +config/version @@ -8,6 +8,7 @@ Cobbler is written & maintained by: Patches and other contributions from: Partha Aji <paji@redhat.com> + Anton Arapov <aarapov@redhat.com> Joseph Boyer Jr. <jboyer@liquidnet.com> Andrew Brown <ambrown@ncsu.edu> David Brown <dmlb2000@gmail.com> @@ -20,7 +21,6 @@ Patches and other contributions from: MáirÃn Duffy <duffy@redhat.com> John Eckersberg <jeckersb@redhat.com> Dan Guernsey <danpg102@gmail.com> - Henry Kemp <henrykemp@gmail.com> Marcel Haerry <haerry+et@puzzle.ch> Dave Hatton <dave@davehatton.it> Scott Henson <shenson@redhat.com> @@ -30,7 +30,9 @@ Patches and other contributions from: Shuichi Ihara <ihara@sun.com> Pablo Iranzo Gómez <pablo.iranzo@redhat.com> Brian Kearney <bkearney@redhat.com> + Henry Kemp <henrykemp@gmail.com> Ruben Kerkhof <ruben@rubenkerkhof.com> + Don Khan <khan_don@emc.com> Vito Laurenza <vitolaurenza@gmail.com> Adrian Likins <alikins@redhat.com> David Lutterkort <dlutter@redhat.com> @@ -49,16 +51,19 @@ Patches and other contributions from: Ben Riggs <rigg0022@umn.edu> Jeremy Rosengren <jeremy@rosengren.org> Adam Rosenwald <thestrider@gmail.com> + Satoru Satoh <ssato@redhat.com> + Jeff Schroeder <jeffschroeder@computer.org> Scott Seago <sseago@redhat.com> Justin Sherill <jsherril@redhat.com> Anderson Silva <ansilva@redhat.com> + Dylan Swift <dylan.swift@gmail.com> Al Tobey <tobert@gmail.com> Thomas Uhde <thomas.uhde@KabelDeutschland.de> Ronald van den Blink <ronald@a61.nl> Tim Verhoeven <tim.verhoeven.be@gmail.com> John L. Villalovos <john@sodarock.com> Peter Vreman <peter.vreman@acision.com> - + Simon Woolsgrove <simon@woolsgrove.com> [...send patches to get your name here...] @@ -1,7 +1,79 @@ Cobbler CHANGELOG -(all entries mdehaan@redhat.com unless noted otherwise) -- Fri Nov 14 2008 - 1.3 +- Tue Mar 3 2009 - 1.5.0 +- (FEAT) Improved anaconda monitoring code +- (FEAT) preliminary linux-ris support for windows installs (WIP) +- (FEAT) download comps.xml always +- (FEAT) performance upgrades for cobblerd service to avoid reloads +- (FEAT) more code for s390 imports (WIP) +- (BUGF) import works better with rawhide +- (FEAT) snippet to preserve SSH host keys across reinstalls +- (FEAT) email trigger to notify when builds finish +- (FEAT) triggers now are written in Python, old system still exists. +- (FEAT) s390x zpxe support +- (BUGF) retry power 5 times before giving up +- (BUGF) fix for RHEL3 ppc imports +- (FEAT) use nameserver variable in network config in Anaconda when set +- (BUGF) sleep 5 seconds between power cycling systems +- (FEAT) support for /usr/bin/cobbler-register + +- Tue Mar 3 2009 - 1.4.3 +- (BUGF) fix OMAPI support's (note: deprecated) usage of subprocess +- (BUGF) don't traceback on invalid cmd ("cobbler distro list --name=foo") +- (BUGF) fix import usage with --kickstart option (no more traceback) +- (BUGF) fix removal of images with child system objects +- (BUGF) make --rpmlist on repo use same parsing routes as the rest of cobbler +- (BUGF) default value for server override should be <<inherit>> not <inherit> +- (BUGF) ensure if virt bridge is set to "" it will apply the value from settings +- (BUGF) cobbler check should say path to config file is /etc/cobbler, not /var/lib +- (FEAT) enable cobblerweb username/pass logins when authing with spacewalk +- (BUGF) allow --kopts to take parameters that are shell quoted. +- (BUGF) allow kernel options to start with '-' +- (BUGF) Use shlex for parsing --kopts to allow a wider variety of kernel options +- (BUGF) prevent potential traceback when --template-file data isn't a hash +- (BUGF) anaconda doesn't honor nameserver always, set manually +- (BUGF) various post install network snippet fixes +- (BUGF) fix OMAPI support's (note: deprecated) usage of subprocess +- (SPEC) fix build for rawhide now that yaboot is included +- (BUGF) allow src and noarch arches for repo objects +- (BUGF) move to PyYAML for YAML implementation since it is better maintained +- (BUGF) fixed web-interface editing of ownership +- (BUGF) fixed web-interface editing of management classes +- (BUGF) don't run full sync in import, sync makes the install system unavailable for very short periods of time +- (FEAT) XMLRPC functions for searching objects, just like the Python API has +- (BUGF) make "find repo" CLI command search repos, not systems + +- XXX - 1.4.2 +- (BUGF) fix WTI power templates +- (FEAT) add WTI power type +- (BUGF) remove Python 2.6/3000 warnings +- (BUGF) fix typo in network template (DNS enumeration) +- (BUGF) make buildiso work for systems that are using bonding +- (BUGF) blending fix for --template-files +- (BUGF) cobbler check ignores comments in TFTP config +- (BUGF) fix image type field editing in the webapp +- (BUGF) allow deletion of eth0 if other interfaces are present +- (BUGF) fix server-override setting in CLI for profile objects +- (BUGF) systems and profiles are now sorted for "cobbler buildiso" +- (BUGF) ensure that directories exist when installing a template file +- (BUGF) fix for typo in network config snippet +- (BUGF) fix for func integration script (should overwrite config, not append) +- (BUGF) append nameservers and gateway, if set, for buildiso + +- Fri Jan 09 2009 - 1.4.1 +- (BUGF) Cobbler check looks for right httpd on SUSE +- (BUGF) post_install_network_config snippet had bash errors +- (BUGF) don't run restorecon programatically +- (FEAT) have cobbler check mention when semanage rules should be applied +- (BUGF) fix an obscure xmlrpclib corner case where a string that looks like an int can't be served because xmlrpclib thinks it's an int. Applies to XMLRPC consumers only, not CobblerWeb or the cobbler CLI. +- (BUGF) fix external kickstart URLs (not templated) and per-system overrides. +- (BUGF) fix 'cobbler check' code for SuSE services +- (FEAT) batch editing on the system page. + +- Fri Dec 19 2008 - 1.4 +- (----) Stable release of 1.3 development branch + +- Fri Dec 19 2008 - 1.3 - (FEAT) ACLs to extend authz (see Wiki) - (FEAT) puppet integration with --mgmt-classes and external nodes URL - (FEAT) added puppet external nodes script, cobbler-ext-nodes @@ -70,12 +142,28 @@ Cobbler CHANGELOG - (----) no SELinux support on EL 4 - (FEAT) yum_post_install_mirror now on by default +- ??? - 1.2.9 +- (BUGF) do not allow unsafe Cheetah imports + +- Wed Oct 15 2008 - 1.2.8 +- (BUGF) make cobbler read /etc/cobbler/settings.py (introduced in 1.2.6) + +- Tue Oct 14 2008 - 1.2.7 +- (BUGF) go easier on restrictings when importing subprofiles to prevent load errors +- (FEAT) added debuginator script to scripts/ (not shipped with RPM) + +- Fri Oct 10 2008 - 1.2.6 +- (BUGF) fix image vs system parentage problem affecting cobbler replicate +- (BUGF) fix restart-services trigger to not restart dns unneccessarily +- (BUGF) add missing variable newname to signature for remote copy_ functions +- (BUGF) fix for ownership ownership module when editing kickstarts + - Fri Sep 26 2008 - 1.2.5 - (BUGF) expose --arch for "cobbler image add" - (BUGF) unbreak dnsmasq DHCP management, similar to ISC bug - (BUGF) fix --arch for cobbler distro add/edit - (BUGF) fix merge error with remote.py's remove_profile function (fix webapp) -- (BUGF) makekeep_updated and mirror_locally checkboxes in WebUI display correctly +- (BUGF) make keep_updated and mirror_locally checkboxes in WebUI display correctly - Mon Sep 08 2008 - 1.2.4 - (BUGF) simple rebuild to remove cli_report.py, which is not in git diff --git a/MANIFEST.in b/MANIFEST.in index a3672670..22e55a70 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,7 @@ include loaders/COPYING_ELILO include loaders/elilo-3.8-ia64.efi include loaders/menu.c32 include loaders/yaboot-1.3.14 +include loaders/zpxe.rexx include config/acls.conf include config/cobbler.conf include config/cobbler_svc.conf @@ -18,6 +19,8 @@ include config/users.conf include config/completions include config/cobbler_bash include config/cheetah_macros +include aux/anamon.py +include aux/anamon.init recursive-include templates *.template recursive-include installer_templates *.template defaults recursive-include kickstarts *.ks @@ -3,32 +3,26 @@ prefix=devinstall statepath=/tmp/cobbler_settings/$(prefix) -all: clean rpms +all: clean build clean: - -rm -f pod2htm*.tmp -rm -f cobbler*.gz cobbler*.rpm MANIFEST - -rm -rf cobbler-* dist build - -rm -rf *~ - -rm -rf rpm-build/ - #-rm -f docs/cobbler.1.gz - #-rm -f docs/cobbler.html - #-rm -f po/messages.pot* - -rm -f cobbler/*.pyc - -rm -f cobbler/yaml/*.pyc - -rm -f cobbler/webui/master.py - -rm -f config/modules.conf config/settings config/version - -rm -f docs/cobbler.1.gz docs/cobbler.html + -rm -rf cobbler-* dist build rpm-build + -rm -f *~ + -rm -f cobbler/*.pyc cobbler/yaml/*.pyc + -rm -f cobbler/webui/master.py config/modules.conf config/settings config/version + -rm -f docs/cobbler.1.gz docs/cobbler.html pod2htm*.tmp manpage: pod2man --center="cobbler" --release="" ./docs/cobbler.pod | gzip -c > ./docs/cobbler.1.gz pod2html ./docs/cobbler.pod > ./docs/cobbler.html - -test: + +test: make savestate prefix=test make rpms make install make eraseconfig + /sbin/service cobblerd restart -(make nosetests) make restorestate prefix=test @@ -42,9 +36,12 @@ build: manpage updatewui install: manpage updatewui python setup.py install -f -devinstall: - make savestate - make install +debinstall: manpage updatewui + python setup.py install -f --root $(DESTDIR) + +devinstall: + make savestate + make install make restorestate savestate: @@ -67,7 +64,7 @@ restorestate: cp $(statepath)/users.digest /etc/cobbler/users.digest cp $(statepath)/http.conf /etc/httpd/conf.d/cobbler.conf find /var/lib/cobbler/triggers | xargs chmod +x - chown -R apache /var/www/cobbler + chown -R apache /var/www/cobbler chmod -R +x /var/www/cobbler/web chmod -R +x /var/www/cobbler/svc rm -rf $(statepath) @@ -75,10 +72,10 @@ restorestate: completion: python mkbash.py -webtest: - make clean - make updatewui - make devinstall +webtest: updatewui devinstall + make clean + make updatewui + make devinstall make restartservices restartservices: @@ -125,3 +122,5 @@ eraseconfig: graphviz: dot -Tpdf docs/cobbler.dot -o cobbler.pdf +tags: + find . -type f -name '*.py' | xargs etags -c TAGS diff --git a/aux/anamon.init b/aux/anamon.init new file mode 100644 index 00000000..b2fd9828 --- /dev/null +++ b/aux/anamon.init @@ -0,0 +1,101 @@ +#!/bin/bash +## BEGIN INIT INFO +# Provides: anamon +# Default-Start: 3 5 +# Default-Stop: 0 1 2 4 6 +# Required-Start: +# Should-Start: $network +# Short-Description: Starts the cobbler anamon boot notification program +# Description: anamon runs the first time a machine is booted after +# installation. +## END INIT INFO + +# +# anamon: Starts the cobbler post-install boot notification program +# +# chkconfig: 35 99 95 +# +# description: anamon runs the first time a machine is booted after +# installation. +# + +LOCKFILE="/var/lock/subsys/anamon" +CFGFILE="/etc/sysconfig/anamon" + +# Source function library. +. /etc/init.d/functions + +# Source anamon config +. $CFGFILE + +LOGFILES=${LOGFILES:-/var/log/boot.log} + +# FIXME - can we rely on the koan snippet to update /etc/profile.d/cobbler.sh? +if [ -z "$COBBLER_SERVER" ]; then + echo "No COBBLER_SERVER defined in $CFGFILE" + exit 1 +fi + +if [ -z "$COBBLER_NAME" ]; then + echo "No COBBLER_NAME defined in $CFGFILE" + exit 1 +fi + +if [ -z "$LOGFILES" ]; then + echo "No LOGFILES defined in $CFGFILE" + exit 1 +fi + +start() { + echo -n $"Starting anamon: " + daemon /usr/local/sbin/anamon.py --watchfile \"$LOGFILES\" --name $COBBLER_NAME --server $COBBLER_SERVER --port ${COBBLER_PORT:-80} --exit + RETVAL=$? + [ $RETVAL -eq 0 ] && touch $LOCKFILE + echo + + # Disable service start + chkconfig anamon off + + return $RETVAL +} + +stop () { + echo -n $"Shutting down anamon: " + killproc /usr/local/sbin/anamon.py + RETVAL=$? + [ $RETVAL -eq 0 ] && rm -f $LOCKFILE + echo + return $RETVAL +} + +restart() { + stop + start +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + condrestart) + if [ -f $LOCKFILE ]; then + restart + fi + ;; + status) + status anamon.py + RETVAL=$? + ;; + *) + echo $"Usage: $0 {start|stop|status|restart|condrestart}" + RETVAL=2 + ;; +esac + +exit $RETVAL diff --git a/aux/anamon.py b/aux/anamon.py new file mode 100644 index 00000000..fe4d0eb6 --- /dev/null +++ b/aux/anamon.py @@ -0,0 +1,263 @@ +#!/usr/bin/python + +""" +This is a script used to automatically log details from an Anaconda +install back to a cobbler server. + +Copyright 2008, Red Hat, Inc +various@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 sys +import string +import time +import re +import base64 +import shlex + +# on older installers (EL 2) we might not have xmlrpclib +# and can't do logging, however this is more widely +# supported than remote syslog and also provides more +# detail. +try: + import xmlrpclib +except ImportError, e: + print "xmlrpclib not available, exiting" + sys.exit(0) + +# shlex.split support arrived in python-2.3, the following will provide some +# accomodation for older distros (e.g. RHEL3) +if not hasattr(shlex, "split"): + shlex.split = lambda s: s.split(" ") + +class WatchedFile: + def __init__(self, fn, alias): + self.fn = fn + self.alias = alias + self.reset() + + def reset(self): + self.where = 0 + self.last_size = 0 + self.lfrag='' + self.re_list={} + self.seen_line={} + + def exists(self): + return os.access(self.fn, os.F_OK) + + def lookfor(self,pattern): + self.re_list[pattern] = re.compile(pattern,re.MULTILINE) + self.seen_line[pattern] = 0 + + def seen(self,pattern): + if self.seen_line.has_key(pattern): + return self.seen_line[pattern] + else: + return 0 + + def changed(self): + if not self.exists(): + return 0 + size = os.stat(self.fn)[6] + if size > self.last_size: + self.last_size = size + return 1 + else: + return 0 + + def uploadWrapper(self, blocksize = 262144): + """upload a file in chunks using the uploadFile call""" + retries = 3 + fo = file(self.fn, "r") + totalsize = os.path.getsize(self.fn) + ofs = 0 + while True: + lap = time.time() + contents = fo.read(blocksize) + size = len(contents) + data = base64.encodestring(contents) + if size == 0: + offset = -1 + sz = ofs + else: + offset = ofs + sz = size + del contents + tries = 0 + while tries <= retries: + debug("upload_log_data('%s', '%s', %s, %s, ...)\n" % (name, self.alias, sz, offset)) + if session.upload_log_data(name, self.alias, sz, offset, data): + break + else: + tries = tries + 1 + if size == 0: + break + ofs += size + fo.close() + + def update(self): + if not self.exists(): + return + if not self.changed(): + return + try: + self.uploadWrapper() + except: + raise + +class MountWatcher: + + def __init__(self,mp): + self.mountpoint = mp + self.zero() + + def zero(self): + self.line='' + self.time = time.time() + + def update(self): + fd = open('/proc/mounts') + found = 0 + while 1: + line = fd.readline() + if not line: + break + parts = string.split(line) + mp = parts[1] + if mp == self.mountpoint: + found = 1 + if line != self.line: + self.line = line + self.time = time.time() + if not found: + self.zero() + fd.close() + + def stable(self): + self.update() + if self.line and (time.time() - self.time > 60): + return 1 + else: + return 0 + +def anamon_loop(): + alog = WatchedFile("/tmp/anaconda.log", "anaconda.log") + alog.lookfor("step installpackages$") + + slog = WatchedFile("/tmp/syslog", "sys.log") + llog = WatchedFile("/tmp/lvmout", "lvmout.log") + kcfg = WatchedFile("/tmp/ks.cfg", "ks.cfg") + scrlog = WatchedFile("/tmp/ks-script.log", "ks-script.log") + dump = WatchedFile("/tmp/anacdump.txt", "anacdump.txt") + mod = WatchedFile("/tmp/modprobe.conf", "modprobe.conf") + ilog = WatchedFile("/mnt/sysimage/root/install.log", "install.log") + ilog2 = WatchedFile("/mnt/sysimage/tmp/install.log", "tmp+install.log") + ulog = WatchedFile("/mnt/sysimage/root/upgrade.log", "upgrade.log") + ulog2 = WatchedFile("/mnt/sysimage/tmp/upgrade.log", "tmp+upgrade.log") + sysimage = MountWatcher("/mnt/sysimage") + + # Were we asked to watch specific files? + if watchfiles: + watchlist = [] + waitlist = [] + + # Create WatchedFile objects for each requested file + for watchfile in watchfiles: + if os.path.exists(watchfile): + watchfilebase = os.path.basename(watchfile) + watchlog = WatchedFile(watchfile, watchfilebase) + watchlist.append(watchlog) + + # Use the default watchlist and waitlist + else: + watchlist = [alog, slog, dump, scrlog, mod, llog, kcfg] + waitlist = [ilog, ilog2, ulog, ulog2] + + # Monitor loop + while 1: + time.sleep(5) + + # Not all log files are available at the start, we'll loop through the + # waitlist to determine when each file can be added to the watchlist + for watch in waitlist: + if alog.seen("step installpackages$") or (sysimage.stable() and watch.exists()): + debug("Adding %s to watch list\n" % watch.alias) + watchlist.append(watch) + waitlist.remove(watch) + + # Send any updates + for wf in watchlist: + wf.update() + + # If asked to run_once, exit now + if exit: + break + +# Establish some defaults +name = "" +server = "" +port = "80" +daemon = 1 +debug = lambda x,**y: None +watchfiles = [] +exit = False + +# Process command-line args +n = 0 +while n < len(sys.argv): + arg = sys.argv[n] + if arg == '--name': + n = n+1 + name = sys.argv[n] + elif arg == '--watchfile': + n = n+1 + watchfiles.extend(shlex.split(sys.argv[n])) + elif arg == '--exit': + exit = True + elif arg == '--server': + n = n+1 + server = sys.argv[n] + elif arg == '--port': + n = n+1 + port = sys.argv[n] + elif arg == '--debug': + debug = lambda x,**y: sys.stderr.write(x % y) + elif arg == '--fg': + daemon = 0 + n = n+1 + +# Create an xmlrpc session handle +session = xmlrpclib.Server("http://%s:%s/cobbler_api" % (server, port)) + +# Fork and loop +if daemon: + if not os.fork(): + # Redirect the standard I/O file descriptors to the specified file. + DEVNULL = getattr(os, "devnull", "/dev/null") + os.open(DEVNULL, os.O_RDWR) # standard input (0) + os.dup2(0, 1) # Duplicate standard input to standard output (1) + os.dup2(0, 2) # Duplicate standard input to standard error (2) + + anamon_loop() + sys.exit(1) + sys.exit(0) +else: + anamon_loop() + diff --git a/cobbler.spec b/cobbler.spec index e5d258a9..33033e93 100644 --- a/cobbler.spec +++ b/cobbler.spec @@ -1,8 +1,9 @@ %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} +%define _binaries_in_noarch_packages_terminate_build 0 Summary: Boot server configurator Name: cobbler AutoReq: no -Version: 1.3.4 +Version: 1.7.0 Release: 1%{?dist} Source0: %{name}-%{version}.tar.gz License: GPLv2+ @@ -21,6 +22,10 @@ Requires: python-devel Requires: createrepo Requires: python-cheetah Requires: rsync +Requires: python-netaddr +Requires: PyYAML +BuildRequires: PyYAML +Requires: libyaml %if 0%{?fedora} >= 11 || 0%{?rhel} >= 6 Requires: genisoimage %else @@ -30,8 +35,8 @@ Requires(post): /sbin/chkconfig Requires(preun): /sbin/chkconfig Requires(preun): /sbin/service %if 0%{?fedora} >= 11 || 0%{?rhel} >= 6 -%{!?pyver: %define pyver %(%{__python} -c "import sys ; print sys.version[:3]")} -Requires: python(abi)=%{pyver} +%{!?pyver: %define pyver %(%{__python} -c "import sys ; print sys.version[:3]" || echo 0)} +Requires: python(abi) = %{pyver} %endif %if 0%{?suse_version} < 0 BuildRequires: redhat-rpm-config @@ -51,16 +56,16 @@ Url: http://cobbler.et.redhat.com %description -Cobbler is a network boot and update server. Cobbler -supports PXE, provisioning virtualized images, and +Cobbler is a network install server. Cobbler +supports PXE, virtualized installs, and reinstalling existing Linux machines. The last two -modes require a helper tool called 'koan' that +modes use a helper tool, 'koan', that integrates with cobbler. Cobbler's advanced features include importing distributions from DVDs and rsync mirrors, kickstart templating, integrated yum mirroring, and built-in DHCP/DNS Management. Cobbler has a Python and XMLRPC API for integration with other -applications. +applications. There is also a web interface. %prep %setup -q @@ -76,6 +81,7 @@ PREFIX="--prefix=/usr" %{__python} setup.py install --optimize=1 --root=$RPM_BUILD_ROOT $PREFIX %post + # backup config if [ -e /var/lib/cobbler/distros ]; then cp /var/lib/cobbler/distros* /var/lib/cobbler/backup 2>/dev/null @@ -121,6 +127,7 @@ if [ "$1" -ge "1" ]; then /sbin/service httpd condrestart >/dev/null 2>&1 || : fi + %clean test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT @@ -155,8 +162,13 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %dir /var/www/cobbler/links %defattr(755,apache,apache) %dir /var/www/cobbler/webui +%dir /var/www/cobbler/aux %defattr(444,apache,apache) /var/www/cobbler/webui/* +%ghost /var/www/cobbler/aux/anamon.pyc +%ghost /var/www/cobbler/aux/anamon.pyo +/var/www/cobbler/aux/*.py +/var/www/cobbler/aux/*.init %defattr(755,root,root) %{_bindir}/cobbler @@ -165,10 +177,14 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %defattr(-,root,root) %dir /etc/cobbler +%dir /etc/cobbler/pxe +%dir /etc/cobbler/reporting +%dir /etc/cobbler/power %config(noreplace) /var/lib/cobbler/kickstarts/*.ks %config(noreplace) /var/lib/cobbler/kickstarts/*.seed %config(noreplace) /etc/cobbler/*.template %config(noreplace) /etc/cobbler/pxe/*.template +%config(noreplace) /etc/cobbler/reporting/*.template %config(noreplace) /etc/cobbler/power/*.template %config(noreplace) /etc/cobbler/rsync.exclude %config(noreplace) /etc/logrotate.d/cobblerd_rotate @@ -177,11 +193,9 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %config(noreplace) /etc/cobbler/acls.conf %config(noreplace) /etc/cobbler/cheetah_macros %dir %{python_sitelib}/cobbler -%dir %{python_sitelib}/cobbler/yaml %dir %{python_sitelib}/cobbler/modules %dir %{python_sitelib}/cobbler/webui %{python_sitelib}/cobbler/*.py* -%{python_sitelib}/cobbler/yaml/*.py* %{python_sitelib}/cobbler/server/*.py* %{python_sitelib}/cobbler/modules/*.py* %{python_sitelib}/cobbler/webui/*.py* @@ -195,6 +209,7 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %config(noreplace) /etc/httpd/conf.d/cobbler_svc.conf %endif %dir /var/log/cobbler/syslog +%dir /var/log/cobbler/anamon %defattr(755,root,root) %dir /var/lib/cobbler @@ -241,11 +256,6 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %dir /var/lib/cobbler/triggers/install/post %dir /var/lib/cobbler/snippets/ -%defattr(744,root,root) -%config(noreplace) /var/lib/cobbler/triggers/sync/post/restart-services.trigger -%config(noreplace) /var/lib/cobbler/triggers/install/pre/status_pre.trigger -%config(noreplace) /var/lib/cobbler/triggers/install/post/status_post.trigger - %defattr(664,root,root) %config(noreplace) /etc/cobbler/settings /var/lib/cobbler/version @@ -260,10 +270,15 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %config(noreplace) /var/lib/cobbler/snippets/func_register_if_enabled %config(noreplace) /var/lib/cobbler/snippets/download_config_files %config(noreplace) /var/lib/cobbler/snippets/koan_environment +%config(noreplace) /var/lib/cobbler/snippets/pre_anamon +%config(noreplace) /var/lib/cobbler/snippets/post_anamon +%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 /var/lib/cobbler/elilo-3.8-ia64.efi /var/lib/cobbler/menu.c32 /var/lib/cobbler/yaboot-1.3.14 +/var/lib/cobbler/zpxe.rexx %defattr(660,root,root) %config(noreplace) /etc/cobbler/users.digest @@ -279,178 +294,6 @@ test "x$RPM_BUILD_ROOT" != "x" && rm -rf $RPM_BUILD_ROOT %changelog -* Wed Dec 10 2008 Michael DeHaan <mdehaan@redhat.com> - 1.3.4-1 -- Updated test release - -- Upstream changes (see CHANGELOG) -- Added specfile changes for python 2.6 -* Mon Dec 08 2008 Michael DeHaan <mdehaan@redhat.com> - 1.3.3-1 -- Upstream changes (see CHANGELOG) -- Added specfile changes for python 2.6 - -* Tue Nov 18 2008 Michael DeHaan <mdehaan@redhat.com> - 1.3.2-1 -- Upstream changes (see CHANGELOG) -- placeholder for future test release -- packaged /var/lib/cobbler/version - -* Fri Nov 14 2008 Michael DeHaan <mdehaan@redhat.com> - 1.3.1-1 -- Upstream changes (see CHANGELOG) - -* Fri Sep 26 2008 Michael DeHaan <mdehaan@redhat.com> - 1.3.0-1 -- Upstream changes (see CHANGELOG) -- added sample.seed file -- added /usr/bin/cobbler-ext-nodes - -* Fri Sep 26 2008 Michael DeHaan <mdehaan@redhat.com> - 1.2.5-1 -- Upstream changes (see CHANGELOG) - -* Mon Sep 08 2008 Michael DeHaan <mdehaan@redhat.com> - 1.2.4-1 -- Rebuild - -* Sun Sep 07 2008 Michael DeHaan <mdehaan@redhat.com> - 1.2.3-1 -- Upstream changes (see CHANGELOG) - -* Fri Sep 05 2008 Michael DeHaan <mdehaan@redhat.com> - 1.2.2-1 -- Upstream changes (see CHANGELOG) - -* Tue Sep 02 2008 Michael DeHaan <mdehaan@redhat.com> - 1.2.1-1 -- Upstream changes (see CHANGELOG) -- Package unowned directories - -* Fri Aug 29 2008 Michael DeHaan <mdehaan@redhat.com> - 1.2.0-1 -- Upstream changes (see CHANGELOG) - -* Tue Jun 10 2008 Michael DeHaan <mdehaan@redhat.com> - 1.0.3-1 -- Upstream changes (see CHANGELOG) - -* Mon Jun 09 2008 Michael DeHaan <mdehaan@redhat.com> - 1.0.2-1 -- Upstream changes (see CHANGELOG) - -* Tue Jun 03 2008 Michael DeHaan <mdehaan@redhat.com> - 1.0.1-1 -- Upstream changes (see CHANGELOG) -- stop owning files in tftpboot -- condrestart for Apache - -* Wed May 27 2008 Michael DeHaan <mdehaan@redhat.com> - 1.0.0-2 -- Upstream changes (see CHANGELOG) - -* Fri May 16 2008 Michael DeHaan <mdehaan@redhat.com> - 0.9.2-2 -- Upstream changes (see CHANGELOG) -- moved /var/lib/cobbler/settings to /etc/cobbler/settings - -* Fri May 09 2008 Michael DeHaan <mdehaan@redhat.com> - 0.9.1-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) - -* Fri Mar 07 2008 Michael DeHaan <mdehaan@redhat.com> - 0.8.2-1 -- Upstream changes (see CHANGELOG) - -* Wed Feb 20 2008 Michael DeHaan <mdehaan@redhat.com> - 0.8.1-1 -- Upstream changes (see CHANGELOG) - -* Fri Feb 15 2008 Michael DeHaan <mdehaan@redhat.com> - 0.8.0-2 -- Fix egg packaging - -* Fri Feb 15 2008 Michael DeHaan <mdehaan@redhat.com> - 0.8.0-1 -- Upstream changes (see CHANGELOG) - -* Mon Jan 21 2008 Michael DeHaan <mdehaan@redhat.com> - 0.7.2-1 -- Upstream changes (see CHANGELOG) -- prune changelog, see git for full - -* Mon Jan 07 2008 Michael DeHaan <mdehaan@redhat.com> - 0.7.1-1 -- Upstream changes (see CHANGELOG) -- Generalize what files are included in RPM -- Add new python module directory -- Fixes for builds on F9 and later - -* Thu Dec 14 2007 Michael DeHaan <mdehaan@redhat.com> - 0.7.0-1 -- Upstream changes (see CHANGELOG), testing branch -- Don't require syslinux -- Added requires on rsync -- Disable autoreq to avoid slurping in perl modules - -* Wed Nov 14 2007 Michael DeHaan <mdehaan@redhat.com> - 0.6.4-2 -- Upstream changes (see CHANGELOG) -- Permissions changes - -* Wed Nov 07 2007 Michael DeHaan <mdehaan@redhat.com> - 0.6.3-2 -- Upstream changes (see CHANGELOG) -- now packaging javascript file(s) seperately for WUI -- backup state files on upgrade -- cobbler sync now has pre/post triggers, so package those dirs/files -- WebUI now has .htaccess file -- removed yum-utils as a requirement - -* Fri Sep 28 2007 Michael DeHaan <mdehaan@redhat.com> - 0.6.2-2 -- Upstream changes (see CHANGELOG) -- removed syslinux as a requirement (cobbler check will detect absense) -- packaged /var/lib/cobbler/settings as a config file -- added BuildRequires of redhat-rpm-config to help src RPM rebuilds on other platforms -- permissions cleanup -- make license field conform to rpmlint -- relocate cgi-bin files to cobbler subdirectory -- include the WUI! - -* Thu Aug 30 2007 Michael DeHaan <mdehaan@redhat.com> - 0.6.1-2 -- Upstream changes (see CHANGELOG) - -* Thu Aug 09 2007 Michael DeHaan <mdehaan@redhat.com> - 0.6.0-1 -- Upstream changes (see CHANGELOG) - -* Thu Jul 26 2007 Michael DeHaan <mdehaan@redhat.com> - 0.5.2-1 -- Upstream changes (see CHANGELOG) -- Tweaked description - -* Fri Jul 20 2007 Michael DeHaan <mdehaan@redhat.com> - 0.5.1-1 -- Upstream changes (see CHANGELOG) -- Modified description -- Added logrotate script -- Added findks.cgi - -* Wed Jun 27 2007 Michael DeHaan <mdehaan@redhat.com> - 0.5.0-1 -- Upstream changes (see CHANGELOG) -- Added dnsmasq.template - -* Fri Apr 27 2007 Michael DeHaan <mdehaan@redhat.com> - 0.4.9-1 -- Upstream changes (see CHANGELOG) - -* Thu Apr 26 2007 Michael DeHaan <mdehaan@redhat.com> - 0.4.8-1 -- Upstream changes (see CHANGELOG) -- Fix defattr in spec file - -* Fri Apr 20 2007 Michael DeHaan <mdehaan@redhat.com> - 0.4.7-5 -- Upstream changes (see CHANGELOG) -- Added triggers to /var/lib/cobbler/triggers - -* Thu Apr 05 2007 Michael DeHaan <mdehaan@redhat.com> - 0.4.6-0 -- Upstream changes (see CHANGELOG) -- Packaged 'config' directory under ks_mirror - -* Fri Mar 23 2007 Michael DeHaan <mdehaan@redhat.com> - 0.4.5-3 -- Upstream changes (see CHANGELOG) -- Fix sticky bit on /var/www/cobbler files - -* Fri Mar 23 2007 Michael DeHaan <mdehaan@redhat.com> - 0.4.4-0 -- Upstream changes (see CHANGELOG) - -* Wed Feb 28 2007 Michael DeHaan <mdehaan@redhat.com> - 0.4.3-0 -- Upstream changes (see CHANGELOG) -- Description cleanup - -* Mon Feb 19 2007 Michael DeHaan <mdehaan@redhat.com> - 0.4.2-0 -- Upstream changes (see CHANGELOG) - -* Mon Feb 19 2007 Michael DeHaan <mdehaan@redhat.com> - 0.4.1-0 -- Bundles menu.c32 (syslinux) for those distros that don't provide it. -- Unbundles Cheetah since it's available at http://www.python.org/pyvault/centos-4-i386/ -- Upstream changes (see CHANGELOG) +* Fri Mar 06 2009 Michael DeHaan <mdehaan@redhat.com> - 1.7.0-1 +- Development release 1.7 start -* Mon Feb 19 2007 Michael DeHaan <mdehaan@redhat.com> - 0.4.0-1 -- Upstream changes (see CHANGELOG) -- Cobbler RPM now owns various directories it uses versus creating them using commands. -- Bundling a copy of Cheetah for older distros diff --git a/cobbler/acls.py b/cobbler/acls.py index 8e7ecea9..e797f2f7 100644 --- a/cobbler/acls.py +++ b/cobbler/acls.py @@ -40,7 +40,7 @@ class AclEngine: yfh = open("/etc/cobbler/acls.conf") data = yfh.read() yfh.close() - self.data = yaml.load(data).next() + self.data = yaml.load(data) self.verbose = verbose def __match(self, needle, haystack): diff --git a/cobbler/action_buildiso.py b/cobbler/action_buildiso.py index de95afb9..2eebb668 100644 --- a/cobbler/action_buildiso.py +++ b/cobbler/action_buildiso.py @@ -29,6 +29,7 @@ import sys import traceback import shutil import sub_process +import re import utils from cexceptions import * @@ -68,8 +69,10 @@ class BuildIso: self.distros = config.distros() self.profiles = config.profiles() self.systems = config.systems() + self.distros = config.distros() self.distmap = {} self.distctr = 0 + self.source = "" def make_shorter(self,distname): if self.distmap.has_key(distname): @@ -79,44 +82,8 @@ class BuildIso: self.distmap[distname] = str(self.distctr) return str(self.distctr) - def run(self,iso=None,tempdir=None,profiles=None,systems=None): - - # if iso is none, create it in . as "kickstart.iso" - if iso is None: - iso = "kickstart.iso" - - if tempdir is None: - tempdir = os.path.join(os.getcwd(), "buildiso") - print _("- using/creating tempdir: %s") % tempdir - if not os.path.exists(tempdir): - os.makedirs(tempdir) - else: - shutil.rmtree(tempdir) - os.makedirs(tempdir) - - # if base of tempdir does not exist, fail - # create all profiles unless filtered by "profiles" - - imagesdir = os.path.join(tempdir, "images") - isolinuxdir = os.path.join(tempdir, "isolinux") - - print _("- building tree for isolinux") - if not os.path.exists(imagesdir): - os.makedirs(imagesdir) - if not os.path.exists(isolinuxdir): - os.makedirs(isolinuxdir) - - print _("- copying miscellaneous files") - isolinuxbin = "/usr/lib/syslinux/isolinux.bin" - menu = "/var/lib/cobbler/menu.c32" - chain = "/usr/lib/syslinux/chain.c32" - files = [ isolinuxbin, menu, chain ] - for f in files: - if not os.path.exists(f): - raise CX(_("Required file not found: %s") % f) - else: - utils.copyfile(f, os.path.join(isolinuxdir, os.path.basename(f))) - + + def generate_netboot_iso(self,imagesdir,isolinuxdir,profiles=None,systems=None): print _("- copying kernels and initrds - for profiles") # copy all images in included profiles to images dir for profile in self.api.profiles(): @@ -134,8 +101,14 @@ class BuildIso: distname = self.make_shorter(dist.name) # tempdir/isolinux/$distro/vmlinuz, initrd.img # FIXME: this will likely crash on non-Linux breeds - shutil.copyfile(dist.kernel, os.path.join(isolinuxdir, "%s.krn" % distname)) - shutil.copyfile(dist.initrd, os.path.join(isolinuxdir, "%s.img" % distname)) + f1 = os.path.join(isolinuxdir, "%s.krn" % distname) + f2 = os.path.join(isolinuxdir, "%s.img" % distname) + if not os.path.exists(dist.kernel): + raise CX("path does not exist: %s" % dist.kernel) + if not os.path.exists(dist.initrd): + raise CX("path does not exist: %s" % dist.initrd) + shutil.copyfile(dist.kernel, f1) + shutil.copyfile(dist.initrd, f2) if systems is not None: print _("- copying kernels and initrds - for systems") @@ -156,9 +129,15 @@ class BuildIso: isolinuxcfg = os.path.join(isolinuxdir, "isolinux.cfg") cfg = open(isolinuxcfg, "w+") cfg.write(HEADER) # fixme, use template - + print _("- generating profile list...") - for profile in self.api.profiles(): + #sort the profiles + profile_list = [profile for profile in self.profiles] + def sort_name(a,b): + return cmp(a.name,b.name) + profile_list.sort(sort_name) + + for profile in profile_list: use_this = True if profiles is not None: which_profiles = profiles.split(",") @@ -189,16 +168,22 @@ class BuildIso: length=len(append_line) if length>254: - print _("WARNING - append line length is greater than 254 chars: (%s chars)") % length - + print _("WARNING - append line length is greater than 254 chars: (%s chars)") % length + cfg.write(append_line) - + if systems is not None: print _("- generating system list...") cfg.write("\nMENU SEPARATOR\n") - for system in self.api.systems(): + #sort the systems + system_list = [system for system in self.systems] + def sort_name(a,b): + return cmp(a.name,b.name) + system_list.sort(sort_name) + + for system in system_list: use_this = False if systems is not None: which_systems = systems.split(",") @@ -230,35 +215,195 @@ class BuildIso: # add network info to avoid DHCP only if it is available - if data.has_key("ip_address_eth0") and data["ip_address_eth0"] != "": - append_line = append_line + " ip=%s" % data["ip_address_eth0"] - if data.has_key("subnet_eth0") and data["subnet_eth0"] != "": - append_line = append_line + " netmask=%s" % data["subnet_eth0"] - if data.has_key("gateway_eth0") and data["gateway_eth0"] != "": - append_line = append_line + " gateway=%s\n" % data["gateway_eth0"] + if data.has_key("bonding_master_eth0") and data["bonding_master_eth0"] != "": + primary_interface = data["bonding_master_eth0"] + else: + primary_interface = "eth0" + + if data.has_key("ip_address_" + primary_interface) and data["ip_address_" + primary_interface] != "": + append_line = append_line + " ip=%s" % data["ip_address_" + primary_interface] + + if data.has_key("subnet_" + primary_interface) and data["subnet_" + primary_interface] != "": + append_line = append_line + " netmask=%s" % data["subnet_" + primary_interface] + + if data.has_key("gateway") and data["gateway"] != "": + append_line = append_line + " gateway=%s" % data["gateway"] + + if data.has_key("name_servers") and data["name_servers"]: + append_line = append_line + " dns=%s\n" % ",".join(data["name_servers"]) length=len(append_line) if length > 254: - print _("WARNING - append line length is greater than 254 chars: (%s chars)") % length - + print _("WARNING - append line length is greater than 254 chars: (%s chars)") % length + cfg.write(append_line) - print _("- done writing config") + print _("- done writing config") cfg.write("\n") cfg.write("MENU END\n") cfg.close() - - cmd = "mkisofs -o %s -r -b isolinux/isolinux.bin -c isolinux/boot.cat" % iso - cmd = cmd + " -no-emul-boot -boot-load-size 4 " - cmd = cmd + " -boot-info-table -V Cobbler\ Install -R -J -T %s" % tempdir + + + def generate_standalone_iso(self,imagesdir,isolinuxdir,distname,filesource): + + # Get the distro object for the requested distro + # and then get all of its descendants (profiles/sub-profiles/systems) + distro = self.api.find_distro(distname) + if distro is None: + raise CX("distro %s was not found, aborting" % distname) + descendants = distro.get_descendants() + + if filesource is None: + # Try to determine the source from the distro kernel path + print _("- trying to locate source for distro") + found_source = False + (source_head, source_tail) = os.path.split(distro.kernel) + while source_tail != '': + if source_head == os.path.join(self.api.settings().webdir, "ks_mirror"): + filesource = os.path.join(source_head, source_tail) + found_source = True + print _(" found source in %s" % filesource) + break + (source_head, source_tail) = os.path.split(source_head) + # Can't find the source, raise an error + if not found_source: + raise CX(_(" Error, no installation source found. When building a standalone ISO, you must specify a --source if the distro install tree is not hosted locally")) + + print _("- copying kernels and initrds - for standalone distro") + # tempdir/isolinux/$distro/vmlinuz, initrd.img + # FIXME: this will likely crash on non-Linux breeds + f1 = os.path.join(isolinuxdir, "vmlinuz") + f2 = os.path.join(isolinuxdir, "initrd.img") + if not os.path.exists(distro.kernel): + raise CX("path does not exist: %s" % distro.kernel) + if not os.path.exists(distro.initrd): + raise CX("path does not exist: %s" % distro.initrd) + shutil.copyfile(distro.kernel, f1) + shutil.copyfile(distro.initrd, f2) + + cmd = "rsync -rlptgu --exclude=boot.cat --exclude=TRANS.TBL --exclude=isolinux/ %s/ %s/../" % (filesource, isolinuxdir) + print _("- copying distro %s files (%s)" % (distname,cmd)) + rc = sub_process.call(cmd, shell=True, close_fds=True) + if rc: + raise CX(_("rsync of files failed")) + + print _("- generating a isolinux.cfg") + isolinuxcfg = os.path.join(isolinuxdir, "isolinux.cfg") + cfg = open(isolinuxcfg, "w+") + cfg.write(HEADER) # fixme, use template + + for descendant in descendants: + data = utils.blender(self.api, True, descendant) + + cfg.write("\n") + cfg.write("LABEL %s\n" % descendant.name) + cfg.write(" MENU LABEL %s\n" % descendant.name) + cfg.write(" kernel vmlinuz\n") + + data["kickstart"] = "cdrom:/isolinux/ks-%s.cfg" % descendant.name + + append_line = " append initrd=initrd.img" + append_line = append_line + " ks=%s " % data["kickstart"] + append_line = append_line + " %s\n" % data["kernel_options"] + + cfg.write(append_line) + + if descendant.COLLECTION_TYPE == 'profile': + kickstart_data = self.api.kickgen.generate_kickstart_for_profile(descendant.name) + elif descendant.COLLECTION_TYPE == 'system': + kickstart_data = self.api.kickgen.generate_kickstart_for_system(descendant.name) + + cdregex = re.compile("url .*\n", re.IGNORECASE) + kickstart_data = cdregex.sub("cdrom\n", kickstart_data) + + ks_name = os.path.join(isolinuxdir, "ks-%s.cfg" % descendant.name) + ks_file = open(ks_name, "w+") + ks_file.write(kickstart_data) + ks_file.close() + + print _("- done writing config") + cfg.write("\n") + cfg.write("MENU END\n") + cfg.close() + + return + + + def run(self,iso=None,tempdir=None,profiles=None,systems=None,distro=None,standalone=None,source=None): + + # the distro option is for stand-alone builds only + if not standalone and distro is not None: + raise CX(_("The --distro option should only be used when creating a standalone ISO")) + # if building standalone, we only want --distro, + # profiles/systems are disallowed + if standalone: + if profiles is not None or systems is not None: + raise CX(_("When building a standalone ISO, use --distro only instead of --profiles/--systems")) + elif distro is None: + raise CX(_("When building a standalone ISO, you must specify a --distro")) + if source != None and not os.path.exists(source): + raise CX(_("The source specified (%s) does not exist" % source)) + + # if iso is none, create it in . as "kickstart.iso" + if iso is None: + iso = "kickstart.iso" + + if tempdir is None: + tempdir = os.path.join(os.getcwd(), "buildiso") + else: + if not os.path.isdir(tempdir): + raise CX(_("The --tempdir specified is not a directory")) + + (tempdir_head,tempdir_tail) = os.path.split(os.path.normpath(tempdir)) + if tempdir_tail != "buildiso": + tempdir = os.path.join(tempdir, "buildiso") + + print _("- using/creating tempdir: %s") % tempdir + if not os.path.exists(tempdir): + os.makedirs(tempdir) + else: + shutil.rmtree(tempdir) + os.makedirs(tempdir) + + # if base of tempdir does not exist, fail + # create all profiles unless filtered by "profiles" + + imagesdir = os.path.join(tempdir, "images") + isolinuxdir = os.path.join(tempdir, "isolinux") + + print _("- building tree for isolinux") + if not os.path.exists(imagesdir): + os.makedirs(imagesdir) + if not os.path.exists(isolinuxdir): + os.makedirs(isolinuxdir) + + print _("- copying miscellaneous files") + isolinuxbin = "/usr/lib/syslinux/isolinux.bin" + menu = "/var/lib/cobbler/menu.c32" + chain = "/usr/lib/syslinux/chain.c32" + files = [ isolinuxbin, menu, chain ] + for f in files: + if not os.path.exists(f): + raise CX(_("Required file not found: %s") % f) + else: + utils.copyfile(f, os.path.join(isolinuxdir, os.path.basename(f)), self.api) + + if standalone: + self.generate_standalone_iso(imagesdir,isolinuxdir,distro,source) + else: + self.generate_netboot_iso(imagesdir,isolinuxdir,profiles,systems) + + cmd = "mkisofs -quiet -o %s -r -b isolinux/isolinux.bin -c isolinux/boot.cat" % iso + cmd = cmd + " -no-emul-boot -boot-load-size 4" + cmd = cmd + " -boot-info-table -V Cobbler\ Install -R -J -T %s" % tempdir print _("- running: %s") % cmd rc = sub_process.call(cmd, shell=True, close_fds=True) if rc: raise CX(_("mkisofs failed")) - + print _("ISO build complete") print _("You may wish to delete: %s") % tempdir - print _("The output file is: %s") % iso - - + print _("The output file is: %s") % iso + + diff --git a/cobbler/action_check.py b/cobbler/action_check.py index 99fffbb0..a2885a28 100644 --- a/cobbler/action_check.py +++ b/cobbler/action_check.py @@ -44,6 +44,7 @@ class BootCheck: (The CLI usage is "cobbler check" before "cobbler sync") """ status = [] + self.checked_dist = utils.check_dist() self.check_name(status) self.check_selinux(status) if self.settings.manage_dhcp: @@ -95,13 +96,13 @@ class BootCheck: if notes != "": notes = " (NOTE: %s)" % notes rc = 0 - if utils.check_dist() == "redhat": + if self.checked_dist == "redhat" or self.checked_dist == "suse": if os.path.exists("/etc/rc.d/init.d/%s" % which): - rc = sub_process.call("/sbin/service %s status >/dev/null 2>/dev/null" % which, shell=True, close_fds=True) + rc = sub_process.call("/sbin/service %s status > /dev/null 2>/dev/null" % which, shell=True, close_fds=True) if rc != 0: status.append(_("service %s is not running%s") % (which,notes)) return False - elif utils.check_dist() == "debian": + elif self.checked_dist == "debian": if os.path.exists("/etc/init.d/%s" % which): rc = sub_process.call("/etc/init.d/%s status /dev/null 2>/dev/null" % which, shell=True, close_fds=True) if rc != 0: @@ -116,7 +117,7 @@ class BootCheck: if os.path.exists("/etc/rc.d/init.d/iptables"): rc = sub_process.call("/sbin/service iptables status >/dev/null 2>/dev/null", shell=True, close_fds=True) if rc == 0: - status.append(_("since iptables may be running, ensure 69, 80, %(syslog)s, and %(xmlrpc)s are unblocked") % { "syslog" : self.settings.syslog_port, "xmlrpc" : self.settings.xmlrpc_port }) + status.append(_("since iptables may be running, ensure 69, 80, and %(xmlrpc)s are unblocked") % { "xmlrpc" : self.settings.xmlrpc_port }) def check_yum(self,status): if not os.path.exists("/usr/bin/createrepo"): @@ -125,6 +126,11 @@ class BootCheck: status.append(_("reposync is not installed, need for cobbler reposync, install/upgrade yum-utils?")) if not os.path.exists("/usr/bin/yumdownloader"): status.append(_("yumdownloader is not installed, needed for cobbler repo add with --rpm-list parameter, install/upgrade yum-utils?")) + if self.settings.yumreposync_flags.find("\-l"): + if self.checked_dist == "redhat" or self.checked_dist == "suse": + yum_utils_ver = sub_process.call("/usr/bin/rpmquery --queryformat=%{VERSION} yum-utils", shell=True, close_fds=True) + if yum_utils_ver < "1.1.17": + status.append(_("yum-utils need to be at least version 1.1.17 for reposync -l")) def check_name(self,status): """ @@ -146,7 +152,30 @@ class BootCheck: if line.find("httpd_can_network_connect ") != -1: if line.find("off") != -1: status.append(_("Must enable selinux boolean to enable Apache and web services components, run: setsebool -P httpd_can_network_connect true")) - + data3 = sub_process.Popen("/usr/sbin/semanage fcontext -l | grep public_content_t",shell=True,stdout=sub_process.PIPE).communicate()[0] + + rule1 = False + rule2 = False + rule3 = False + selinux_msg = "/usr/sbin/semanage fcontext -a -t public_content_t \"%s\"" + for line in data3.split("\n"): + if line.startswith("/tftpboot/.*") and line.find("public_content_t") != -1: + rule1 = True + if line.startswith("/var/lib/tftpboot/.*") and line.find("public_content_t") != -1: + rule2 = True + if line.startswith("/var/www/cobbler/images/.*") and line.find("public_content_t") != -1: + rule3 = True + + rules = [] + if not os.path.exists("/tftpboot") and not rule1: + rules.append(selinux_msg % "/tftpboot/.*") + else: + if not rule2: + rules.append(selinux_msg % "/var/lib/tftpboot/.*") + if not rule3: + rules.append(selinux_msg % "/var/www/cobbler/images/.*") + if len(rules) > 0: + status.append("you need to set some SELinux content rules to ensure cobbler works correctly in your SELinux environment, run the following: %s" % " && ".join(rules)) def check_for_default_password(self,status): default_pass = self.settings.default_password_crypted @@ -185,8 +214,10 @@ class BootCheck: """ Check if Apache is installed. """ - self.check_service(status,"httpd") - + if self.checked_dist == "suse": + self.check_service(status,"apache2") + else: + self.check_service(status,"httpd") def check_dhcpd_bin(self,status): """ @@ -277,7 +308,7 @@ class BootCheck: f = open(self.settings.tftpd_conf) re_disable = re.compile(r'disable.*=.*yes') for line in f.readlines(): - if re_disable.search(line): + if re_disable.search(line) and not line.strip().startswith("#"): status.append(_("change 'disable' to 'no' in %(file)s") % { "file" : self.settings.tftpd_conf }) else: status.append(_("file %(file)s does not exist") % { "file" : self.settings.tftpd_conf }) diff --git a/cobbler/action_import.py b/cobbler/action_import.py index 5b94b89a..2008c53c 100644 --- a/cobbler/action_import.py +++ b/cobbler/action_import.py @@ -88,8 +88,8 @@ class Importer: if self.arch == "x86": # be consistent self.arch = "i386" - if self.arch not in [ "i386", "ia64", "ppc", "ppc64", "s390x", "x86_64", ]: - raise CX(_("arch must be i386, ia64, ppc, ppc64, s390x or x86_64")) + if self.arch not in [ "i386", "ia64", "ppc", "ppc64", "s390", "s390x", "x86_64", ]: + raise CX(_("arch must be i386, ia64, ppc, ppc64, s390, s390x or x86_64")) # if we're going to do any copying, set where to put things # and then make sure nothing is already there. @@ -113,7 +113,7 @@ class Importer: if self.arch: # append the arch path to the name if the arch is not already # found in the name. - for x in [ "i386", "ia64", "ppc", "ppc64", "s390x", "x86_64", "x86", ]: + for x in [ "i386", "ia64", "ppc", "ppc64", "s390", "s390x", "x86_64", "x86", ]: if self.mirror_name.lower().find(x) != -1: if self.arch != x : raise CX(_("Architecture found on pathname (%s) does not fit the one given in command line (%s)")%(x,self.arch)) @@ -209,11 +209,8 @@ class Importer: print _("---------------- (associating kickstarts)") self.kickstart_finder(distros_added) - # ensure everything is nicely written out to the filesystem - # (which is not so neccessary in newer Cobbler but we're paranoid) - - print _("---------------- (syncing)") - self.api.sync() + # ensure bootloaders are present + self.api.pxegen.copy_bootloaders() return True @@ -265,9 +262,9 @@ class Importer: # print _("- skipping distro %s since it wasn't imported this time") % profile.distro continue + kdir = os.path.dirname(distro.kernel) + importer = import_factory(kdir,self.path) if self.kickstart_file == None: - kdir = os.path.dirname(distro.kernel) - importer = import_factory(kdir,self.path) for rpm in importer.get_release_files(): # FIXME : This redhat specific check should go into the importer.find_release_files method if rpm.find("notes") != -1: @@ -519,9 +516,9 @@ class Importer: print "- following symlink: %s" % fullname os.path.walk(fullname, self.distro_adder, foo) - if x.startswith("initrd") or x.startswith("ramdisk.image.gz"): + if ( x.startswith("initrd") or x.startswith("ramdisk.image.gz") ) and x != "initrd.size": initrd = os.path.join(dirname,x) - if ( x.startswith("vmlinuz") or x.startswith("kernel.img") ) and x.find("initrd") == -1: + if ( x.startswith("vmlinu") or x.startswith("kernel.img") ) and x.find("initrd") == -1: kernel = os.path.join(dirname,x) if x.lower().startswith("startrom.n1_"): startrom = os.path.join(dirname,x) @@ -774,7 +771,7 @@ class Importer: name = name.replace("chrp","ppc64") for separator in [ '-' , '_' , '.' ] : - for arch in [ "i386" , "x86_64" , "ia64" , "ppc64", "ppc32", "ppc", "x86" , "s390x" , "386" , "amd" ]: + for arch in [ "i386" , "x86_64" , "ia64" , "ppc64", "ppc32", "ppc", "x86" , "s390x", "s390" , "386" , "amd" ]: name = name.replace("%s%s" % ( separator , arch ),"") return name @@ -792,8 +789,10 @@ class Importer: return "ia64" if dirname.find("i386") != -1 or dirname.find("386") != -1 or dirname.find("x86") != -1: return "i386" - if dirname.find("s390") != -1: + if dirname.find("s390x") != -1: return "s390x" + if dirname.find("s390") != -1: + return "s390" if dirname.find("ppc64") != -1 or dirname.find("chrp") != -1: return "ppc64" if dirname.find("ppc32") != -1: @@ -828,6 +827,7 @@ def guess_breed(kerneldir,path): [ 'Packages' , "redhat" ], [ 'Fedora' , "redhat" ], [ 'Server' , "redhat" ], + [ 'Client' , "redhat" ], [ 'setup.exe' , "windows" ], ] guess = None @@ -914,7 +914,7 @@ class BaseImporter: for x in fnames: if self.match_kernelarch_file(x): # print _("- kernel header found: %s") % x - for arch in [ "i386" , "x86_64" , "ia64" , "ppc64", "ppc", "s390x" ]: + for arch in [ "i386" , "x86_64" , "ia64" , "ppc64", "ppc", "s390", "s390x" ]: if x.find(arch) != -1: foo[arch] = 1 for arch in [ "i686" , "amd64" ]: @@ -986,8 +986,11 @@ class RedHatImporter ( BaseImporter ) : data = glob.glob(os.path.join(self.get_pkgdir(), "*release-*")) data2 = [] for x in data: - if x.find("generic") == -1: - data2.append(x) + b = os.path.basename(x) + if b.find("fedora") != -1 or \ + b.find("redhat") != -1 or \ + b.find("centos") != -1: + data2.append(x) return data2 # ================================================================ @@ -1099,13 +1102,14 @@ class RedHatImporter ( BaseImporter ) : # OS_VERSION next # OS_VERSION.MINOR next # ARCH/default.ks next - # default.ks finally. + # FLAVOR.ks next kickstarts = [ "%s/%s/%s.%i.ks" % (kickbase,arch,os_version,int(minor)), "%s/%s/%s.ks" % (kickbase,arch,os_version), "%s/%s.%i.ks" % (kickbase,os_version,int(minor)), "%s/%s.ks" % (kickbase,os_version), "%s/%s/default.ks" % (kickbase,arch), + "%s/%s.ks" % (kickbase,flavor), ] for kickstart in kickstarts: if os.path.exists(kickstart): diff --git a/cobbler/action_litesync.py b/cobbler/action_litesync.py index b040db0a..7773455e 100644 --- a/cobbler/action_litesync.py +++ b/cobbler/action_litesync.py @@ -46,11 +46,11 @@ class BootLiteSync: Handles conversion of internal state to the tftpboot tree layout """ - def __init__(self,config): + def __init__(self,config,verbose=False): """ Constructor """ - self.verbose = True + self.verbose = verbose self.config = config self.distros = config.distros() self.profiles = config.profiles() @@ -58,7 +58,7 @@ class BootLiteSync: self.images = config.images() self.settings = config.settings() self.repos = config.repos() - self.sync = config.api.get_sync() + self.sync = config.api.get_sync(verbose) def add_single_distro(self, name): # get the distro record @@ -101,7 +101,7 @@ class BootLiteSync: # get the profile object: profile = self.profiles.find(name=name) if profile is None: - raise CX(_("error in profile lookup")) + raise CX(_("error in profile lookup for %s" % name)) # rebuild the yum configuration files for any attached repos # generate any templates listed in the distro self.sync.pxegen.write_templates(profile) @@ -160,10 +160,6 @@ class BootLiteSync: system_record = self.systems.find(name=name) # delete contents of kickstarts_sys/$name in webdir system_record = self.systems.find(name=name) - # delete any kickstart files related to this system - for (name,interface) in system_record.interfaces.iteritems(): - filename = utils.get_config_filename(system_record,interface=name) - utils.rmtree(os.path.join(self.settings.webdir, "kickstarts_sys", filename)) if self.settings.manage_dhcp: if self.settings.omapi_enabled: @@ -179,8 +175,12 @@ class BootLiteSync: distro = self.distros.find(name=profile.distro) if distro is not None and distro in [ "ia64", "IA64"]: itanic = True - if not itanic: - utils.rmfile(os.path.join(bootloc, "pxelinux.cfg", filename)) - else: - utils.rmfile(os.path.join(bootloc, filename)) + + for (name,interface) in system_record.interfaces.iteritems(): + filename = utils.get_config_filename(system_record,interface=name) + + if not itanic: + utils.rmfile(os.path.join(bootloc, "pxelinux.cfg", filename)) + else: + utils.rmfile(os.path.join(bootloc, filename)) diff --git a/cobbler/action_power.py b/cobbler/action_power.py index 9db4535c..789750a1 100644 --- a/cobbler/action_power.py +++ b/cobbler/action_power.py @@ -31,6 +31,7 @@ import os.path import sub_process import sys import traceback +import time import utils from cexceptions import * @@ -106,7 +107,15 @@ class PowerTool: #if not os.path.exists(tool_needed): # print "warning: %s does not seem to be installed" % tool_needed - rc = sub_process.call(cmd, shell=False, close_fds=True) + # Try the power command 5 times before giving up. + # Some power switches are flakey + for x in range(0,5): + rc = sub_process.call(cmd, shell=False, close_fds=True) + if rc == 0: + break + else: + time.sleep(2) + if not rc == 0: raise CX("command failed (rc=%s), please validate the physical setup and cobbler config" % rc) @@ -137,6 +146,8 @@ class PowerTool: "lpar" : os.path.join(powerdir,"power_lpar.template"), "bladecenter": os.path.join(powerdir,"power_bladecenter.template"), "virsh" : os.path.join(powerdir,"power_virsh.template"), + "integrity" : os.path.join(powerdir,"power_integrity.template"), + "wti" : os.path.join(powerdir,"power_wti.template"), } result = map.get(self.system.power_type, "") diff --git a/cobbler/action_reposync.py b/cobbler/action_reposync.py index 4bb484c5..14ef1112 100644 --- a/cobbler/action_reposync.py +++ b/cobbler/action_reposync.py @@ -345,6 +345,24 @@ class RepoSync: if rc !=0: raise CX(_("cobbler reposync failed")) + repodata_path = os.path.join(dest_path, "repodata") + + if not os.path.exists("/usr/bin/wget"): + raise CX(_("no /usr/bin/wget found, please install wget")) + + cmd2 = "/usr/bin/wget -q %s/repodata/comps.xml -O /dev/null" % (repo_mirror) + rc = sub_process.call(cmd2, shell=True, close_fds=True) + if rc == 0: + if not os.path.isdir(repodata_path): + os.makedirs(repodata_path) + + cmd2 = "/usr/bin/wget -q %s/repodata/comps.xml -O %s/comps.xml" % (repo_mirror, repodata_path) + print _("- %s") % cmd2 + + rc = sub_process.call(cmd2, shell=True, close_fds=True) + if rc !=0: + raise CX(_("wget failed")) + # now run createrepo to rebuild the index if repo.mirror_locally: diff --git a/cobbler/action_sync.py b/cobbler/action_sync.py index f6e5bcd8..78f3c3d5 100644 --- a/cobbler/action_sync.py +++ b/cobbler/action_sync.py @@ -56,19 +56,22 @@ class BootSync: """ Constructor """ - self.verbose = verbose - self.config = config - self.api = config.api - self.distros = config.distros() - self.profiles = config.profiles() - self.systems = config.systems() - self.settings = config.settings() - self.repos = config.repos() - self.templar = templar.Templar(config) - self.pxegen = pxegen.PXEGen(config) - self.dns = dns - self.dhcp = dhcp - self.bootloc = utils.tftpboot_location() + self.verbose = verbose + self.config = config + self.api = config.api + self.distros = config.distros() + self.profiles = config.profiles() + self.systems = config.systems() + self.settings = config.settings() + self.repos = config.repos() + self.templar = templar.Templar(config) + self.pxegen = pxegen.PXEGen(config) + self.dns = dns + self.dhcp = dhcp + self.bootloc = utils.tftpboot_location() + self.pxegen.verbose = verbose + self.dns.verbose = verbose + self.dhcp.verbose = verbose def run(self): """ @@ -78,37 +81,67 @@ class BootSync: if not os.path.exists(self.bootloc): raise CX(_("cannot find directory: %s") % self.bootloc) + if self.verbose: + print "- running pre-sync triggers" + # run pre-triggers... - utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/pre/*") + utils.run_triggers(self.api, None, "/var/lib/cobbler/triggers/sync/pre/*") # (paranoid) in case the pre-trigger modified any objects... + + if self.verbose: + print "- loading configuration" self.api.deserialize() + self.distros = self.config.distros() self.profiles = self.config.profiles() self.systems = self.config.systems() self.settings = self.config.settings() self.repos = self.config.repos() - self.pxegen = pxegen.PXEGen(self.config) # execute the core of the sync operation + + if self.verbose: + print "- cleaning trees" self.clean_trees() + + if self.verbose: + print "- copying bootloaders" self.pxegen.copy_bootloaders() + + if self.verbose: + print "- copying distros" self.pxegen.copy_distros() + + if self.verbose: + print "- copying images" self.pxegen.copy_images() self.pxegen.generate_windows_files() for x in self.systems: + if self.verbose: + print "- copying files for system: %s" % x.name self.pxegen.write_all_system_files(x) + if self.settings.manage_dhcp: + if self.verbose: + print "- rendering DHCP files" self.dhcp.write_dhcp_file() self.dhcp.regen_ethers() if self.settings.manage_dns: + if self.verbose: + print "- rendering DNS files" self.dns.regen_hosts() self.dns.write_dns_files() + + if self.verbose: + print "- generating PXE menu structure" self.pxegen.make_pxe_menu() self.pxegen.write_tftpd_rules(True) # run post-triggers - utils.run_triggers(None, "/var/lib/cobbler/triggers/sync/post/*") + if self.verbose: + print "- running post-sync triggers" + utils.run_triggers(self.api, None, "/var/lib/cobbler/triggers/sync/post/*") return True def clean_trees(self): @@ -128,14 +161,14 @@ class BootSync: path = os.path.join(self.settings.webdir,x) if os.path.isfile(path): if not x.endswith(".py"): - utils.rmfile(path) + utils.rmfile(path,verbose=self.verbose) if os.path.isdir(path): - if not x in ["web", "webui", "localmirror","repo_mirror","ks_mirror","images","links","repo_profile","repo_system","svc","rendered"] : + if not x in ["aux", "web", "webui", "localmirror","repo_mirror","ks_mirror","images","links","repo_profile","repo_system","svc","rendered"] : # delete directories that shouldn't exist - utils.rmtree(path) + utils.rmtree(path,verbose=self.verbose) if x in ["kickstarts","kickstarts_sys","images","systems","distros","profiles","repo_profile","repo_system","rendered"]: # clean out directory contents - utils.rmtree_contents(path) + utils.rmtree_contents(path,verbose=self.verbose) pxelinux_dir = os.path.join(self.bootloc, "pxelinux.cfg") images_dir = os.path.join(self.bootloc, "images") yaboot_bin_dir = os.path.join(self.bootloc, "ppc") @@ -143,21 +176,21 @@ class BootSync: s390_dir = os.path.join(self.bootloc, "s390x") rendered_dir = os.path.join(self.settings.webdir, "rendered") if not os.path.exists(pxelinux_dir): - utils.mkdir(pxelinux_dir) + utils.mkdir(pxelinux_dir,verbose=self.verbose) if not os.path.exists(images_dir): - utils.mkdir(images_dir) + utils.mkdir(images_dir,verbose=self.verbose) if not os.path.exists(rendered_dir): - utils.mkdir(rendered_dir) + utils.mkdir(rendered_dir,verbose=self.verbose) if not os.path.exists(yaboot_bin_dir): - utils.mkdir(yaboot_bin_dir) + utils.mkdir(yaboot_bin_dir,verbose=self.verbose) if not os.path.exists(yaboot_cfg_dir): - utils.mkdir(yaboot_cfg_dir) - utils.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg")) - utils.rmtree_contents(os.path.join(self.bootloc, "images")) - utils.rmtree_contents(os.path.join(self.bootloc, "s390x")) - utils.rmtree_contents(os.path.join(self.bootloc, "ppc")) - utils.rmtree_contents(os.path.join(self.bootloc, "etc")) - utils.rmtree_contents(rendered_dir) + utils.mkdir(yaboot_cfg_dir,verbose=self.verbose) + utils.rmtree_contents(os.path.join(self.bootloc, "pxelinux.cfg"),verbose=self.verbose) + utils.rmtree_contents(os.path.join(self.bootloc, "images"),verbose=self.verbose) + utils.rmtree_contents(os.path.join(self.bootloc, "s390x"),verbose=self.verbose) + utils.rmtree_contents(os.path.join(self.bootloc, "ppc"),verbose=self.verbose) + utils.rmtree_contents(os.path.join(self.bootloc, "etc"),verbose=self.verbose) + utils.rmtree_contents(rendered_dir,verbose=self.verbose) diff --git a/cobbler/api.py b/cobbler/api.py index 84c2c638..ffc62644 100644 --- a/cobbler/api.py +++ b/cobbler/api.py @@ -22,6 +22,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA """ +import yaml import config import utils import action_sync @@ -48,7 +49,8 @@ import logging import time import random import os -import yaml +import xmlrpclib +import traceback ERROR = 100 INFO = 10 @@ -65,7 +67,7 @@ class BootAPI: __shared_state = {} __has_loaded = False - def __init__(self, log_settings={}): + def __init__(self, log_settings={}, is_cobblerd=False): """ Constructor """ @@ -82,6 +84,7 @@ class BootAPI: # level (and remote.py web service level) instead. random.seed() + self.is_cobblerd = is_cobblerd try: self.logger = self.__setup_logger("api") @@ -90,14 +93,16 @@ class BootAPI: # perms_ok is False return - self.logger_remote = self.__setup_logger("remote") + # FIMXE: conslidate into 1 server instance + self.selinux_enabled = utils.is_selinux_enabled() self.dist = utils.check_dist() self.os_version = utils.os_release() self.acl_engine = acls.AclEngine() - + BootAPI.__has_loaded = True + module_loader.load_modules() self._config = config.Config(self) @@ -118,9 +123,10 @@ class BootAPI: self.pxegen = pxegen.PXEGen(self._config) self.logger.debug("API handle initialized") self.perms_ok = True - + + def __setup_logger(self,name): - return utils.setup_logger(name, **self.log_settings) + return utils.setup_logger(name, is_cobblerd=self.is_cobblerd, **self.log_settings) def is_selinux_enabled(self): """ @@ -142,6 +148,28 @@ class BootAPI: return False return True + def _internal_cache_update(self, collection_type, name, remove=False): + """ + Update cobblerd so it won't have to ever reload the config, once started. + """ + # FIXME: take value from settings, use raw port + if self.is_cobblerd: + # don't signal yourself, that's asking for trouble. + return True + self.server = xmlrpclib.Server("http://127.0.0.1:%s" % self.settings().xmlrpc_port) + try: + if not remove: + self.server.internal_cache_update(collection_type, name) + else: + self.server.internal_cache_remove(collection_type, name) + except Exception, e: + if len(e.args) == 2 and e[0] == 111: + # if cobblerd is not running, no harm done, nothing to signal + pass + else: + raise CX("error contacting cobblerd") + return False + def last_modified_time(self): """ Returns the time of the last modification to cobbler, made by any @@ -181,14 +209,16 @@ class BootAPI: version_tuple -- something like [ 1, 3, 2 ] """ fd = open("/var/lib/cobbler/version") - data = yaml.load(fd.read()).next() + ydata = fd.read() fd.close() + data = yaml.load(ydata) if not extended: # for backwards compatibility and use with koan's comparisons elems = data["version_tuple"] + print elems return int(elems[0]) + 0.1*int(elems[1]) + 0.001*int(elems[2]) else: - return data + return data def clear(self): """ @@ -237,14 +267,10 @@ class BootAPI: def update(self): """ - This can be called when you expect a cobbler object - to have changed outside of your API call. It does not - have to be called before read operations but should be - called before write operations depending on the last - modification time. For the local API it is not needed. + This can be called is no longer used by cobbler. + And is here to just avoid breaking older scripts. """ - self.clear() - self.deserialize() + return True def copy_distro(self, ref, newname): self.log("copy_distro",[ref.name, newname]) @@ -266,46 +292,45 @@ class BootAPI: self.log("copy_image",[ref.name, newname]) return self._config.images().copy(ref,newname) - def remove_distro(self, ref, recursive=False): + def remove_distro(self, ref, recursive=False, delete=True, with_triggers=True, ): if type(ref) != str: self.log("remove_distro",[ref.name]) - return self._config.distros().remove(ref.name, recursive=recursive) + return self._config.distros().remove(ref.name, recursive=recursive, with_delete=delete, with_triggers=with_triggers) else: self.log("remove_distro",ref) - return self._config.distros().remove(ref, recursive=recursive) - + return self._config.distros().remove(ref, recursive=recursive, with_delete=delete, with_triggers=with_triggers) - def remove_profile(self,ref, recursive=False): + def remove_profile(self,ref, recursive=False, delete=True, with_triggers=True): if type(ref) != str: self.log("remove_profile",[ref.name]) - return self._config.profiles().remove(ref.name, recursive=recursive) + return self._config.profiles().remove(ref.name, recursive=recursive, with_delete=delete, with_triggers=with_triggers) else: self.log("remove_profile",ref) - return self._config.profiles().remove(ref, recursive=recursive) + return self._config.profiles().remove(ref, recursive=recursive, with_delete=delete, with_triggers=with_triggers) - def remove_system(self, ref, recursive=False): + def remove_system(self, ref, recursive=False, delete=True, with_triggers=True): if type(ref) != str: self.log("remove_system",[ref.name]) - return self._config.systems().remove(ref.name) + return self._config.systems().remove(ref.name, with_delete=delete, with_triggers=with_triggers) else: self.log("remove_system",ref) - return self._config.systems().remove(ref) + return self._config.systems().remove(ref, with_delete=delete, with_triggers=with_triggers) - def remove_repo(self, ref, recursive=False): + def remove_repo(self, ref, recursive=False, delete=True, with_triggers=True): if type(ref) != str: self.log("remove_repo",[ref.name]) - return self._config.repos().remove(ref.name) + return self._config.repos().remove(ref.name, with_delete=delete, with_triggers=with_triggers) else: self.log("remove_repo",ref) - return self._config.repos().remove(ref) + return self._config.repos().remove(ref, with_delete=delete, with_triggers=with_triggers) - def remove_image(self, ref, recursive=False): + def remove_image(self, ref, recursive=False, delete=True, with_triggers=True): if type(ref) != str: self.log("remove_image",[ref.name]) - return self._config.images().remove(ref.name, recursive=recursive) + return self._config.images().remove(ref.name, recursive=recursive, with_delete=delete, with_triggers=with_triggers) else: self.log("remove_image",ref) - return self._config.images().remove(ref, recursive=recursive) + return self._config.images().remove(ref, recursive=recursive, with_delete=delete, with_triggers=with_triggers) def rename_distro(self, ref, newname): self.log("rename_distro",[ref.name,newname]) @@ -347,29 +372,34 @@ class BootAPI: self.log("new_image",[is_subobject]) return self._config.new_image(is_subobject=is_subobject) - def add_distro(self, ref, check_for_duplicate_names=False): + def add_distro(self, ref, check_for_duplicate_names=False, save=True): self.log("add_distro",[ref.name]) - return self._config.distros().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names) + rc = self._config.distros().add(ref,check_for_duplicate_names=check_for_duplicate_names,save=save) + return rc - def add_profile(self, ref, check_for_duplicate_names=False): + def add_profile(self, ref, check_for_duplicate_names=False,save=True): self.log("add_profile",[ref.name]) - return self._config.profiles().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names) + rc = self._config.profiles().add(ref,check_for_duplicate_names=check_for_duplicate_names,save=save) + return rc - def add_system(self, ref, check_for_duplicate_names=False, check_for_duplicate_netinfo=False): + def add_system(self, ref, check_for_duplicate_names=False, check_for_duplicate_netinfo=False, save=True): self.log("add_system",[ref.name]) - return self._config.systems().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names,check_for_duplicate_netinfo=check_for_duplicate_netinfo) + rc = self._config.systems().add(ref,check_for_duplicate_names=check_for_duplicate_names,check_for_duplicate_netinfo=check_for_duplicate_netinfo,save=save) + return rc - def add_repo(self, ref, check_for_duplicate_names=False): + def add_repo(self, ref, check_for_duplicate_names=False,save=True): self.log("add_repo",[ref.name]) - return self._config.repos().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names) - - def add_image(self, ref, check_for_duplicate_names=False): + rc = self._config.repos().add(ref,check_for_duplicate_names=check_for_duplicate_names,save=save) + return rc + + def add_image(self, ref, check_for_duplicate_names=False,save=True): self.log("add_image",[ref.name]) - return self._config.images().add(ref,save=True,check_for_duplicate_names=check_for_duplicate_names) + rc = self._config.images().add(ref,check_for_duplicate_names=check_for_duplicate_names,save=save) + return rc def find_distro(self, name=None, return_list=False, no_errors=False, **kargs): return self._config.distros().find(name=name, return_list=return_list, no_errors=no_errors, **kargs) - + def find_profile(self, name=None, return_list=False, no_errors=False, **kargs): return self._config.profiles().find(name=name, return_list=return_list, no_errors=no_errors, **kargs) @@ -389,12 +419,11 @@ class BootAPI: results1 = collector() results2 = [] for x in results1: - print "INPUT: %s ACTUAL: %s" % (mtime, x.mtime) if x.mtime == 0 or x.mtime >= mtime: if not collapse: - results2.append(results1) + results2.append(x) else: - results2.append(results1.to_datastruct()) + results2.append(x.to_datastruct()) return results2 def get_distros_since(self,mtime,collapse=False): @@ -511,7 +540,7 @@ class BootAPI: validator = action_validate.Validate(self._config) return validator.run() - def sync(self): + def sync(self,verbose=False): """ Take the values currently written to the configuration files in /etc, and /var, and build out the information tree found in @@ -519,10 +548,10 @@ class BootAPI: saved with serialize() will NOT be synchronized with this command. """ self.log("sync") - sync = self.get_sync() + sync = self.get_sync(verbose=verbose) return sync.run() - def get_sync(self): + def get_sync(self,verbose=False): self.dhcp = self.get_module_from_file( "dhcp", "module", @@ -533,7 +562,7 @@ class BootAPI: "module", "manage_bind" ).get_manager(self._config) - return action_sync.BootSync(self._config,dhcp=self.dhcp,dns=self.dns) + return action_sync.BootSync(self._config,dhcp=self.dhcp,dns=self.dns,verbose=verbose) def reposync(self, name=None, tries=1, nofail=False): """ @@ -646,10 +675,10 @@ class BootAPI: self.log("authorize",[user,resource,arg1,arg2,rc],debug=True) return rc - def build_iso(self,iso=None,profiles=None,systems=None,tempdir=None): + def build_iso(self,iso=None,profiles=None,systems=None,tempdir=None,distro=None,standalone=None,source=None): builder = action_buildiso.BuildIso(self._config) return builder.run( - iso=iso, profiles=profiles, systems=systems, tempdir=tempdir + iso=iso, profiles=profiles, systems=systems, tempdir=tempdir, distro=distro, standalone=standalone, source=source ) def replicate(self, cobbler_master = None, sync_all=False, sync_kickstarts=False, sync_trees=False, sync_repos=False, sync_triggers=False, systems=False): @@ -697,7 +726,7 @@ class BootAPI: Cycles power on a system that has power management configured. """ self.power_off(system, user, password) - time.sleep(1) + time.sleep(5) return self.power_on(system, user, password) def get_os_details(self): diff --git a/cobbler/cobbler.py b/cobbler/cobbler.py index c896d266..5bbbcef5 100755 --- a/cobbler/cobbler.py +++ b/cobbler/cobbler.py @@ -42,7 +42,7 @@ I18N_DOMAIN = "cobbler" class BootCLI: def __init__(self): - self.api = api.BootAPI() + self.api = api.BootAPI(is_cobblerd=False) self.loader = commands.FunctionLoader(self.api) climods = self.api.get_modules_in_category("cli") for mod in climods: diff --git a/cobbler/cobblerd.py b/cobbler/cobblerd.py index 36645750..8abe8329 100644 --- a/cobbler/cobblerd.py +++ b/cobbler/cobblerd.py @@ -41,25 +41,14 @@ import remote def main(): core(logger=None) -def core(logger=None): +def core(api): - bootapi = cobbler_api.BootAPI() + bootapi = api settings = bootapi.settings() - syslog_port = settings.syslog_port xmlrpc_port = settings.xmlrpc_port - xmlrpc_port2 = settings.xmlrpc_rw_port - - pid = os.fork() regen_ss_file() - - if pid == 0: - # part one: XMLRPC -- which may be just read-only or both read-only and read-write - do_xmlrpc_tasks(bootapi, settings, xmlrpc_port, xmlrpc_port2, logger) - else: - # part two: syslog, or syslog+avahi if avahi is installed - do_other_tasks(bootapi, settings, syslog_port, logger) - os.waitpid(pid, 0) + do_xmlrpc_tasks(bootapi, settings, xmlrpc_port) def regen_ss_file(): # this is only used for Kerberos auth at the moment. @@ -76,44 +65,23 @@ def regen_ss_file(): os.system("chown apache /var/lib/cobbler/web.ss") return 1 -def do_xmlrpc_tasks(bootapi, settings, xmlrpc_port, xmlrpc_port2, logger): - if str(settings.xmlrpc_rw_enabled) != "0": - pid2 = os.fork() - if pid2 == 0: - do_mandatory_xmlrpc_tasks(bootapi, settings, xmlrpc_port, logger) - else: - do_xmlrpc_rw(bootapi, settings, xmlrpc_port2, logger) - os.waitpid(pid2, 0) - else: - logger.debug("xmlrpc_rw is disabled in the settings file") - do_mandatory_xmlrpc_tasks(bootapi, settings, xmlrpc_port, logger) - -def do_mandatory_xmlrpc_tasks(bootapi,settings,xmlrpc_port,logger): - #pid3 = os.fork() - #if pid3 == 0: - # do_xmlrpc(bootapi, settings, xmlrpc_port, logger) - #else: - # # NOTE: this shouldn't be enabled unless we decide - # # to use it for something. - # # do_xmlrpc_unix(bootapi, settings, logger) - # pass - do_xmlrpc(bootapi, settings, xmlrpc_port, logger) - - -def do_other_tasks(bootapi, settings, syslog_port, logger): - - # FUTURE: this should also start the Web UI, if the dependencies - # are available. - - if os.path.exists("/usr/bin/avahi-publish-service"): - pid2 = os.fork() - if pid2 == 0: - do_syslog(bootapi, settings, syslog_port, logger) - else: - do_avahi(bootapi, settings, logger) - os.waitpid(pid2, 0) - else: - do_syslog(bootapi, settings, syslog_port, logger) +def do_xmlrpc_tasks(bootapi, settings, xmlrpc_port): + do_xmlrpc_rw(bootapi, settings, xmlrpc_port) + +#def do_other_tasks(bootapi, settings, syslog_port, logger): +# +# # FUTURE: this should also start the Web UI, if the dependencies +# # are available. +# +# if os.path.exists("/usr/bin/avahi-publish-service"): +# pid2 = os.fork() +# if pid2 == 0: +# do_syslog(bootapi, settings, syslog_port, logger) +# else: +# do_avahi(bootapi, settings, logger) +# os.waitpid(pid2, 0) +# else: +# do_syslog(bootapi, settings, syslog_port, logger) def log(logger,msg): @@ -122,106 +90,34 @@ def log(logger,msg): else: print >>sys.stderr, msg -def do_avahi(bootapi, settings, logger): - # publish via zeroconf. This command will not terminate - log(logger, "publishing avahi service") - cmd = [ "/usr/bin/avahi-publish-service", - "cobblerd", - "_http._tcp", - "%s" % settings.xmlrpc_port ] - proc = sub_process.Popen(cmd, shell=False, stderr=sub_process.PIPE, stdout=sub_process.PIPE, close_fds=True) - proc.communicate()[0] - log(logger, "avahi service terminated") - - -def do_xmlrpc(bootapi, settings, port, logger): - - # This is the simple XMLRPC API we provide to koan and other - # apps that do not need to manage Cobbler's config - - xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerXMLRPCInterface,False) - - server = remote.CobblerXMLRPCServer(('', port)) - server.logRequests = 0 # don't print stuff - log(logger, "XMLRPC running on %s" % port) - server.register_instance(xinterface) - - while True: - try: - server.serve_forever() - except IOError: - # interrupted? try to serve again - time.sleep(0.5) +#def do_avahi(bootapi, settings, logger): +# # publish via zeroconf. This command will not terminate +# log(logger, "publishing avahi service") +# cmd = [ "/usr/bin/avahi-publish-service", +# "cobblerd", +# "_http._tcp", +# "%s" % settings.xmlrpc_port ] +# proc = sub_process.Popen(cmd, shell=False, stderr=sub_process.PIPE, stdout=sub_process.PIPE, close_fds=True) +# proc.communicate()[0] +# log(logger, "avahi service terminated") -def do_xmlrpc_rw(bootapi,settings,port,logger): - xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,True) - server = remote.CobblerReadWriteXMLRPCServer(('127.0.0.1', port)) - server.logRequests = 0 # don't print stuff - logger.debug("XMLRPC (read-write variant) running on %s" % port) - server.register_instance(xinterface) +def do_xmlrpc_rw(bootapi,settings,port): - while True: - try: - server.serve_forever() - except IOError: - # interrupted? try to serve again - time.sleep(0.5) - -def do_xmlrpc_unix(bootapi,settings,logger): - - xinterface = remote.ProxiedXMLRPCInterface(bootapi,logger,remote.CobblerReadWriteXMLRPCInterface,True) - SOCKT = "/var/lib/cobbler/sock" - server = xmlrpclib2.UnixXMLRPCServer(SOCKT) + xinterface = remote.ProxiedXMLRPCInterface(bootapi,remote.CobblerXMLRPCInterface,True) + server = remote.CobblerXMLRPCServer(('127.0.0.1', port)) server.logRequests = 0 # don't print stuff - logger.debug("XMLRPC (socket variant) available on %s" % SOCKT) + xinterface.logger.debug("XMLRPC running on %s" % port) server.register_instance(xinterface) while True: try: + print "SERVING!" server.serve_forever() except IOError: # interrupted? try to serve again time.sleep(0.5) -def do_syslog(bootapi, settings, port, logger): - - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.bind(("0.0.0.0", port)) - log(logger, "syslog running on %s" % port) - - buf = 1024 - - while 1: - data, addr = s.recvfrom(buf) - (ip, port) = addr - system = bootapi.systems().find(ip_address = ip) - if not system: - usename = ip - else: - usename = system.name - - if not data: - break - else: - logfile = open("/var/log/cobbler/syslog/%s" % usename, "a+") - t = time.localtime() - # write numeric time - seconds = str(time.mktime(t)) - logfile.write(seconds) - logfile.write("\t") - # write string time - timestr = str(time.asctime(t)) - logfile.write(timestr) - logfile.write("\t") - # write the IP address of the client - logfile.write(ip) - logfile.write("\t") - # write the data - logfile.write(data) - logfile.write("\n") - logfile.close() - if __name__ == "__main__": #main() diff --git a/cobbler/collection.py b/cobbler/collection.py index 564a9dec..3bc15cc7 100644 --- a/cobbler/collection.py +++ b/cobbler/collection.py @@ -148,6 +148,13 @@ class Collection(serializable.Serializable): def copy(self,ref,newname): ref.name = newname ref.uid = self.config.generate_uid() + if ref.COLLECTION_TYPE == "system": + # this should only happen for systems + for iname in ref.interfaces.keys(): + # clear all these out to avoid DHCP/DNS conflicts + ref.set_dns_name("",iname) + ref.set_mac_address("",iname) + ref.set_ip_address("",iname) return self.add(ref,save=True,with_copy=True,with_triggers=True,with_sync=True,check_for_duplicate_names=True,check_for_duplicate_netinfo=False) def rename(self,ref,newname,with_sync=True,with_triggers=True): @@ -251,7 +258,7 @@ class Collection(serializable.Serializable): self.log_func("saving %s %s" % (self.collection_type(), ref.name)) # failure of a pre trigger will prevent the object from being added if with_triggers: - self._run_triggers(ref,"/var/lib/cobbler/triggers/add/%s/pre/*" % self.collection_type()) + self._run_triggers(self.api, ref,"/var/lib/cobbler/triggers/add/%s/pre/*" % self.collection_type()) self.listing[ref.name.lower()] = ref # save just this item if possible, if not, save @@ -277,17 +284,22 @@ class Collection(serializable.Serializable): # save the tree, so if neccessary, scripts can examine it. if with_triggers: - self._run_triggers(ref,"/var/lib/cobbler/triggers/add/%s/post/*" % self.collection_type()) - + self._run_triggers(self.api, ref,"/var/lib/cobbler/triggers/add/%s/post/*" % self.collection_type()) + + # update children cache in parent object parent = ref.get_parent() if parent != None: parent.children[ref.name] = ref + # signal remote cobblerd to update it's cache of this item. + if save and not self.api.is_cobblerd: + self.api._internal_cache_update(ref.COLLECTION_TYPE,ref.name) + return True - def _run_triggers(self,ref,globber): - return utils.run_triggers(ref,globber) + def _run_triggers(self,api_handle,ref,globber): + return utils.run_triggers(api_handle,ref,globber) def __duplication_checks(self,ref,check_for_duplicate_names,check_for_duplicate_netinfo): """ @@ -307,6 +319,10 @@ class Collection(serializable.Serializable): match = self.api.find_distro(ref.name) elif isinstance(ref, item_repo.Repo): match = self.api.find_repo(ref.name) + elif isinstance(ref, item_image.Image): + match = self.api.find_image(ref.name) + else: + raise CX("internal error, unknown object type") if match: raise CX(_("An object already exists with that name. Try 'edit'?")) diff --git a/cobbler/collection_distros.py b/cobbler/collection_distros.py index 1ca3ca92..ae75f407 100644 --- a/cobbler/collection_distros.py +++ b/cobbler/collection_distros.py @@ -60,11 +60,11 @@ class Distros(collection.Collection): if recursive: kids = obj.get_children() for k in kids: - self.config.api.remove_profile(k, recursive=True) + self.config.api.remove_profile(k, recursive=recursive, delete=with_delete, with_triggers=with_triggers) if with_delete: if with_triggers: - self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/distro/pre/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/distro/pre/*") if with_sync: lite_sync = action_litesync.BootLiteSync(self.config) lite_sync.remove_single_profile(name) @@ -75,7 +75,7 @@ class Distros(collection.Collection): if with_delete: self.log_func("deleted distro %s" % name) if with_triggers: - self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/distro/post/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/distro/post/*") # look through all mirrored directories and find if any directory is holding # this particular distribution's kernel and initrd @@ -99,7 +99,11 @@ class Distros(collection.Collection): if not found: utils.rmtree(path) + if with_delete and not self.api.is_cobblerd: + self.api._internal_cache_update("distro", name, remove=True) + return True - raise CX(_("cannot delete object that does not exist: %s") % name) + #if not recursive: + # raise CX(_("cannot delete object that does not exist: %s") % name) diff --git a/cobbler/collection_images.py b/cobbler/collection_images.py index 12cd5c60..2dfeeac1 100644 --- a/cobbler/collection_images.py +++ b/cobbler/collection_images.py @@ -39,11 +39,25 @@ class Images(collection.Collection): # but is left in for consistancy in the API. Unused. name = name.lower() + + # first see if any Groups use this distro + if not recursive: + for v in self.config.systems(): + if v.image is not None and v.image.lower() == name: + raise CX(_("removal would orphan system: %s") % v.name) + obj = self.find(name=name) + if obj is not None: + + if recursive: + kids = obj.get_children() + for k in kids: + self.config.api.remove_system(k, recursive=True) + if with_delete: if with_triggers: - self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/image/pre/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/image/pre/*") if with_sync: lite_sync = action_litesync.BootLiteSync(self.config) lite_sync.remove_single_image(name) @@ -54,7 +68,11 @@ class Images(collection.Collection): if with_delete: self.log_func("deleted repo %s" % name) if with_triggers: - self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/image/post/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/image/post/*") return True - raise CX(_("cannot delete an object that does not exist: %s") % name) + if with_delete and not self.api.is_cobblerd: + self.api._internal_cache_update("image", name, remove=True) + + #else: + # raise CX(_("cannot delete an object that does not exist: %s") % name) diff --git a/cobbler/collection_profiles.py b/cobbler/collection_profiles.py index 5554eeab..bb27dea4 100644 --- a/cobbler/collection_profiles.py +++ b/cobbler/collection_profiles.py @@ -58,13 +58,13 @@ class Profiles(collection.Collection): kids = obj.get_children() for k in kids: if k.COLLECTION_TYPE == "profile": - self.config.api.remove_profile(k, recursive=True) + self.config.api.remove_profile(k, recursive=recursive, delete=with_delete, with_triggers=with_triggers) else: - self.config.api.remove_system(k) + self.config.api.remove_system(k, recursive=recursive, delete=with_delete, with_triggers=with_triggers) if with_delete: if with_triggers: - self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/profile/pre/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/profile/pre/*") if with_sync: lite_sync = action_litesync.BootLiteSync(self.config) lite_sync.remove_single_profile(name) @@ -73,7 +73,12 @@ class Profiles(collection.Collection): if with_delete: self.log_func("deleted profile %s" % name) if with_triggers: - self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/profile/post/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/profile/post/*") + + if with_delete and not self.api.is_cobblerd: + self.api._internal_cache_update("profile", name, remove=True) + return True - raise CX(_("cannot delete an object that does not exist: %s") % name) + #if not recursive: + # raise CX(_("cannot delete an object that does not exist: %s") % name) diff --git a/cobbler/collection_repos.py b/cobbler/collection_repos.py index 8ce150d4..3b2c1c20 100644 --- a/cobbler/collection_repos.py +++ b/cobbler/collection_repos.py @@ -56,7 +56,7 @@ class Repos(collection.Collection): if obj is not None: if with_delete: if with_triggers: - self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/repo/pre/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/repo/pre/*") del self.listing[name] self.config.serialize_delete(self, obj) @@ -64,12 +64,16 @@ class Repos(collection.Collection): if with_delete: self.log_func("deleted repo %s" % name) if with_triggers: - self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/repo/post/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/repo/post/*") path = "/var/www/cobbler/repo_mirror/%s" % obj.name if os.path.exists(path): utils.rmtree(path) + if with_delete and not self.api.is_cobblerd: + self.api._internal_cache_update("repo", name, remove=True) + return True - raise CX(_("cannot delete an object that does not exist: %s") % name) + #if not recursive: + # raise CX(_("cannot delete an object that does not exist: %s") % name) diff --git a/cobbler/collection_systems.py b/cobbler/collection_systems.py index 211c5da9..5a628248 100644 --- a/cobbler/collection_systems.py +++ b/cobbler/collection_systems.py @@ -52,7 +52,7 @@ class Systems(collection.Collection): if with_delete: if with_triggers: - self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/system/pre/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/system/pre/*") if with_sync: lite_sync = action_litesync.BootLiteSync(self.config) lite_sync.remove_single_system(name) @@ -61,9 +61,13 @@ class Systems(collection.Collection): if with_delete: self.log_func("deleted system %s" % name) if with_triggers: - self._run_triggers(obj, "/var/lib/cobbler/triggers/delete/system/post/*") + self._run_triggers(self.config.api, obj, "/var/lib/cobbler/triggers/delete/system/post/*") + + if with_delete and not self.api.is_cobblerd: + self.api._internal_cache_update("system", name, remove=True) return True - - raise CX(_("cannot delete an object that does not exist: %s") % name) + + #if not recursive: + # raise CX(_("cannot delete an object that does not exist: %s") % name) diff --git a/cobbler/commands.py b/cobbler/commands.py index 98a3a3a7..4bb6ec33 100644 --- a/cobbler/commands.py +++ b/cobbler/commands.py @@ -307,7 +307,7 @@ class CobblerFunction: if not self.options.name: raise CX(_("name is required")) if not recursive: - collect_fn().remove(self.options.name,with_delete=True) + collect_fn().remove(self.options.name,with_delete=True,recursive=False) else: collect_fn().remove(self.options.name,with_delete=True,recursive=True) return None # signal that we want no further processing on the object @@ -332,6 +332,12 @@ class CobblerFunction: raise CX(_("object not found")) return obj + try: + # catch some invalid executions of the CLI + getattr(self, "options") + except: + sys.exit(1) + if not self.options.name: raise CX(_("name is required")) @@ -377,8 +383,18 @@ class CobblerFunction: if "copy" in self.args: if self.options.newname: - obj = obj.make_clone() - obj.set_name(self.options.newname) + # FIXME: this should just use the copy function! + if obj.COLLECTION_TYPE == "distro": + return self.api.copy_distro(obj, self.options.newname) + if obj.COLLECTION_TYPE == "profile": + return self.api.copy_profile(obj, self.options.newname) + if obj.COLLECTION_TYPE == "system": + return self.api.copy_system(obj, self.options.newname) + if obj.COLLECTION_TYPE == "repo": + return self.api.copy_repo(obj, self.options.newname) + if obj.COLLECTION_TYPE == "image": + return self.api.copy_image(obj, self.options.newname) + raise CX(_("internal error, don't know how to copy")) else: raise CX(_("--newname is required")) diff --git a/cobbler/config.py b/cobbler/config.py index 78a4d7ca..2608b84d 100644 --- a/cobbler/config.py +++ b/cobbler/config.py @@ -44,7 +44,7 @@ import settings import serializer from utils import _ - +from cexceptions import * class Config: @@ -204,7 +204,10 @@ class Config: """ Load the object hierachy from disk, using the filenames referenced in each object. """ - serializer.deserialize(self._settings) + try: + serializer.deserialize(self._settings) + except: + raise CX("/etc/cobbler/settings is not a valid YAML file") serializer.deserialize(self._distros) serializer.deserialize(self._repos) serializer.deserialize(self._profiles) diff --git a/cobbler/demo_connect.py b/cobbler/demo_connect.py index b9d5192e..12627b1e 100644 --- a/cobbler/demo_connect.py +++ b/cobbler/demo_connect.py @@ -31,7 +31,7 @@ if __name__ == "__main__": # NOTE: if you've changed your xmlrpc_rw port or # disabled xmlrpc_rw this test probably won't work - sp = ServerProxy("http://127.0.0.1:25152") + sp = ServerProxy("http://127.0.0.1:25151") (options, args) = p.parse_args() print "- trying to login with user=%s" % options.user token = sp.login(options.user,options.password) diff --git a/cobbler/item.py b/cobbler/item.py index a4ecea5b..31697a2f 100644 --- a/cobbler/item.py +++ b/cobbler/item.py @@ -138,7 +138,7 @@ class Item(serializable.Serializable): like authz_ownership, which ships with Cobbler but is off by default. Consult the Wiki docs for more info on CustomizableAuthorization. """ - owners = utils.input_string_or_list(data) + owners = utils.input_string_or_list(data, delim=" ") self.owners = owners return True @@ -196,7 +196,8 @@ class Item(serializable.Serializable): Assigns a list of configuration management classes that can be assigned to any object, such as those used by Puppet's external_nodes feature. """ - self.mgmt_classes = utils.input_string_or_list(mgmt_classes) + mgmt_classes_split = utils.input_string_or_list(mgmt_classes, delim=" ") + self.mgmt_classes = utils.input_string_or_list(mgmt_classes_split) return True def set_template_files(self,template_files,inplace=False): diff --git a/cobbler/item_distro.py b/cobbler/item_distro.py index 9b34d2e7..9be4f7d3 100644 --- a/cobbler/item_distro.py +++ b/cobbler/item_distro.py @@ -41,24 +41,25 @@ class Distro(item.Item): """ Reset this object. """ - self.name = None - self.uid = "" - self.owners = self.settings.default_ownership - self.kernel = None - self.initrd = None - self.kernel_options = {} - self.kernel_options_post = {} - self.ks_meta = {} - self.arch = 'i386' - self.breed = 'redhat' - self.os_version = '' - self.source_repos = [] - self.mgmt_classes = [] - self.depth = 0 - self.template_files = {} - self.comment = "" - self.tree_build_time = 0 - self.redhat_management_key = "<<inherit>>" + self.name = None + self.uid = "" + self.owners = self.settings.default_ownership + self.kernel = None + self.initrd = None + self.kernel_options = {} + self.kernel_options_post = {} + self.ks_meta = {} + self.arch = 'i386' + self.breed = 'redhat' + self.os_version = '' + self.source_repos = [] + self.mgmt_classes = [] + self.depth = 0 + self.template_files = {} + self.comment = "" + self.tree_build_time = 0 + self.redhat_management_key = "<<inherit>>" + self.redhat_management_server = "<<inherit>>" def make_clone(self): ds = self.to_datastruct() @@ -77,23 +78,24 @@ class Distro(item.Item): """ Modify this object to take on values in seed_data """ - self.parent = self.load_item(seed_data,'parent') - self.name = self.load_item(seed_data,'name') - self.owners = self.load_item(seed_data,'owners',self.settings.default_ownership) - self.kernel = self.load_item(seed_data,'kernel') - self.initrd = self.load_item(seed_data,'initrd') - self.kernel_options = self.load_item(seed_data,'kernel_options') - self.kernel_options_post = self.load_item(seed_data,'kernel_options_post') - self.ks_meta = self.load_item(seed_data,'ks_meta') - self.arch = self.load_item(seed_data,'arch','i386') - self.breed = self.load_item(seed_data,'breed','redhat') - self.os_version = self.load_item(seed_data,'os_version','') - self.source_repos = self.load_item(seed_data,'source_repos',[]) - self.depth = self.load_item(seed_data,'depth',0) - self.mgmt_classes = self.load_item(seed_data,'mgmt_classes',[]) - self.template_files = self.load_item(seed_data,'template_files',{}) - self.comment = self.load_item(seed_data,'comment') - self.redhat_management_key = self.load_item(seed_data,'redhat_management_key',"<<inherit>>") + self.parent = self.load_item(seed_data,'parent') + self.name = self.load_item(seed_data,'name') + self.owners = self.load_item(seed_data,'owners',self.settings.default_ownership) + self.kernel = self.load_item(seed_data,'kernel') + self.initrd = self.load_item(seed_data,'initrd') + self.kernel_options = self.load_item(seed_data,'kernel_options') + self.kernel_options_post = self.load_item(seed_data,'kernel_options_post') + self.ks_meta = self.load_item(seed_data,'ks_meta') + self.arch = self.load_item(seed_data,'arch','i386') + self.breed = self.load_item(seed_data,'breed','redhat') + self.os_version = self.load_item(seed_data,'os_version','') + self.source_repos = self.load_item(seed_data,'source_repos',[]) + self.depth = self.load_item(seed_data,'depth',0) + self.mgmt_classes = self.load_item(seed_data,'mgmt_classes',[]) + self.template_files = self.load_item(seed_data,'template_files',{}) + self.comment = self.load_item(seed_data,'comment') + self.redhat_management_key = self.load_item(seed_data,'redhat_management_key',"<<inherit>>") + self.redhat_management_server = self.load_item(seed_data,'redhat_management_server',"<<inherit>>") # backwards compatibility enforcement self.set_arch(self.arch) @@ -159,6 +161,9 @@ class Distro(item.Item): def set_redhat_management_key(self,key): return utils.set_redhat_management_key(self,key) + + def set_redhat_management_server(self,server): + return utils.set_redhat_management_server(self,server) def set_source_repos(self, repos): """ @@ -209,27 +214,28 @@ class Distro(item.Item): Return a serializable datastructure representation of this object. """ return { - 'name' : self.name, - 'kernel' : self.kernel, - 'initrd' : self.initrd, - 'kernel_options' : self.kernel_options, - 'kernel_options_post' : self.kernel_options_post, - 'ks_meta' : self.ks_meta, - 'mgmt_classes' : self.mgmt_classes, - 'template_files' : self.template_files, - 'arch' : self.arch, - 'breed' : self.breed, - 'os_version' : self.os_version, - 'source_repos' : self.source_repos, - 'parent' : self.parent, - 'depth' : self.depth, - 'owners' : self.owners, - 'comment' : self.comment, - 'tree_build_time' : self.tree_build_time, - 'ctime' : self.ctime, - 'mtime' : self.mtime, - 'uid' : self.uid, - 'redhat_management_key' : self.redhat_management_key + 'name' : self.name, + 'kernel' : self.kernel, + 'initrd' : self.initrd, + 'kernel_options' : self.kernel_options, + 'kernel_options_post' : self.kernel_options_post, + 'ks_meta' : self.ks_meta, + 'mgmt_classes' : self.mgmt_classes, + 'template_files' : self.template_files, + 'arch' : self.arch, + 'breed' : self.breed, + 'os_version' : self.os_version, + 'source_repos' : self.source_repos, + 'parent' : self.parent, + 'depth' : self.depth, + 'owners' : self.owners, + 'comment' : self.comment, + 'tree_build_time' : self.tree_build_time, + 'ctime' : self.ctime, + 'mtime' : self.mtime, + 'uid' : self.uid, + 'redhat_management_key' : self.redhat_management_key, + 'redhat_management_server' : self.redhat_management_server } def printable(self): @@ -257,28 +263,30 @@ class Distro(item.Item): buf = buf + _("owners : %s\n") % self.owners buf = buf + _("post kernel options : %s\n") % self.kernel_options_post buf = buf + _("redhat mgmt key : %s\n") % self.redhat_management_key + buf = buf + _("redhat mgmt server : %s\n") % self.redhat_management_server buf = buf + _("template files : %s\n") % self.template_files return buf def remote_methods(self): return { - 'name' : self.set_name, - 'kernel' : self.set_kernel, - 'initrd' : self.set_initrd, - 'kopts' : self.set_kernel_options, - 'kopts-post' : self.set_kernel_options_post, - 'kopts_post' : self.set_kernel_options_post, - 'arch' : self.set_arch, - 'ksmeta' : self.set_ksmeta, - 'breed' : self.set_breed, - 'os-version' : self.set_os_version, - 'os_version' : self.set_os_version, - 'owners' : self.set_owners, - 'mgmt-classes' : self.set_mgmt_classes, - 'mgmt_classes' : self.set_mgmt_classes, - 'template-files': self.set_template_files, - 'template_files': self.set_template_files, - 'comment' : self.set_comment, - 'redhat_management_key' : self.set_redhat_management_key + 'name' : self.set_name, + 'kernel' : self.set_kernel, + 'initrd' : self.set_initrd, + 'kopts' : self.set_kernel_options, + 'kopts-post' : self.set_kernel_options_post, + 'kopts_post' : self.set_kernel_options_post, + 'arch' : self.set_arch, + 'ksmeta' : self.set_ksmeta, + 'breed' : self.set_breed, + 'os-version' : self.set_os_version, + 'os_version' : self.set_os_version, + 'owners' : self.set_owners, + 'mgmt-classes' : self.set_mgmt_classes, + 'mgmt_classes' : self.set_mgmt_classes, + 'template-files' : self.set_template_files, + 'template_files' : self.set_template_files, + 'comment' : self.set_comment, + 'redhat_management_key' : self.set_redhat_management_key, + 'redhat_management_server' : self.set_redhat_management_server } diff --git a/cobbler/item_image.py b/cobbler/item_image.py index 98e02558..5a4123b2 100644 --- a/cobbler/item_image.py +++ b/cobbler/item_image.py @@ -113,11 +113,49 @@ class Image(item.Item): def set_file(self,filename): """ Stores the image location. This should be accessible on all nodes - that need to access it. Format: either /mnt/commonpath/foo.iso or - nfs://host/path/foo.iso + that need to access it. Format: can be one of the following: + * username:password@hostname:/path/to/the/filename.ext + * username@hostname:/path/to/the/filename.ext + * hostname:/path/to/the/filename.ext + * /path/to/the/filename.ext """ - # FIXME: this should accept NFS paths or filesystem paths - self.file = filename + uri = "" + scheme = auth = hostname = path = "" + # we'll discard the protocol if it's supplied, for legacy support + if filename.find("://") != -1: + scheme, uri = filename.split("://") + filename = uri + else: + uri = filename + + if filename.find("@") != -1: + auth, filename = filename.split("@") + # extract the hostname + # 1. if we have a colon, then everything before it is a hostname + # 2. if we don't have a colon, then check if we had a scheme; if + # we did, then grab all before the first forward slash as the + # hostname; otherwise, we've got a bad file + if filename.find(":") != -1: + hostname, filename = filename.split(":") + elif filename[0] != '/': + if len(scheme) > 0: + index = filename.find("/") + hostname = filename[:index] + filename = filename[index:] + else: + raise CX(_("invalid file: %s" % filename)) + # raise an exception if we don't have a valid path + if len(filename) > 0 and filename[0] != '/': + raise CX(_("file contains an invalid path: %s" % filename)) + if filename.find("/") != -1: + path, filename = filename.rsplit("/", 1) + + if len(filename) == 0: + raise CX(_("missing filename")) + if len(auth) > 0 and len(hostname) == 0: + raise CX(_("a hostname must be specified with authentication details")) + + self.file = uri return True def set_os_version(self,os_version): diff --git a/cobbler/item_profile.py b/cobbler/item_profile.py index d30eb52a..3f9e9460 100644 --- a/cobbler/item_profile.py +++ b/cobbler/item_profile.py @@ -42,60 +42,64 @@ class Profile(item.Item): """ Reset this object. """ - self.name = None - self.uid = "" - self.random_id = "" - self.owners = self.settings.default_ownership - self.distro = (None, '<<inherit>>')[is_subobject] - self.enable_menu = (self.settings.enable_menu, '<<inherit>>')[is_subobject] - self.kickstart = (self.settings.default_kickstart , '<<inherit>>')[is_subobject] - self.kernel_options = ({}, '<<inherit>>')[is_subobject] - self.kernel_options_post = ({}, '<<inherit>>')[is_subobject] - self.ks_meta = ({}, '<<inherit>>')[is_subobject] - self.template_files = ({}, '<<inherit>>')[is_subobject] - self.virt_cpus = (1, '<<inherit>>')[is_subobject] - self.virt_file_size = (self.settings.default_virt_file_size, '<<inherit>>')[is_subobject] - self.virt_ram = (self.settings.default_virt_ram, '<<inherit>>')[is_subobject] - self.repos = ([], '<<inherit>>')[is_subobject] - self.depth = 1 - self.virt_type = (self.settings.default_virt_type, '<<inherit>>')[is_subobject] - self.virt_path = ("", '<<inherit>>')[is_subobject] - self.virt_bridge = (self.settings.default_virt_bridge, '<<inherit>>')[is_subobject] - self.dhcp_tag = ("default", '<<inherit>>')[is_subobject] - self.mgmt_classes = ([], '<<inherit>>')[is_subobject] - self.parent = '' - self.server = "<<inherit>>" - self.comment = "" - self.ctime = 0 - self.mtime = 0 - self.name_servers = (self.settings.default_name_servers, '<<inherit>>')[is_subobject] - self.redhat_management_key = "<<inherit>>" + self.name = None + self.uid = "" + self.random_id = "" + self.owners = self.settings.default_ownership + self.distro = (None, '<<inherit>>')[is_subobject] + self.enable_menu = (self.settings.enable_menu, '<<inherit>>')[is_subobject] + self.kickstart = (self.settings.default_kickstart , '<<inherit>>')[is_subobject] + self.kernel_options = ({}, '<<inherit>>')[is_subobject] + self.kernel_options_post = ({}, '<<inherit>>')[is_subobject] + self.ks_meta = ({}, '<<inherit>>')[is_subobject] + self.template_files = ({}, '<<inherit>>')[is_subobject] + self.virt_cpus = (1, '<<inherit>>')[is_subobject] + self.virt_file_size = (self.settings.default_virt_file_size, '<<inherit>>')[is_subobject] + self.virt_ram = (self.settings.default_virt_ram, '<<inherit>>')[is_subobject] + self.repos = ([], '<<inherit>>')[is_subobject] + self.depth = 1 + self.virt_type = (self.settings.default_virt_type, '<<inherit>>')[is_subobject] + self.virt_path = ("", '<<inherit>>')[is_subobject] + self.virt_bridge = (self.settings.default_virt_bridge, '<<inherit>>')[is_subobject] + self.dhcp_tag = ("default", '<<inherit>>')[is_subobject] + self.mgmt_classes = ([], '<<inherit>>')[is_subobject] + self.parent = '' + self.server = "<<inherit>>" + self.comment = "" + self.ctime = 0 + self.mtime = 0 + self.name_servers = (self.settings.default_name_servers,[])[is_subobject] + self.name_servers_search = (self.settings.default_name_servers_search,[])[is_subobject] + self.redhat_management_key = "<<inherit>>" + self.redhat_management_server = "<<inherit>>" def from_datastruct(self,seed_data): """ Load this object's properties based on seed_data """ - self.parent = self.load_item(seed_data,'parent','') - self.name = self.load_item(seed_data,'name') - self.owners = self.load_item(seed_data,'owners',self.settings.default_ownership) - self.distro = self.load_item(seed_data,'distro') - self.enable_menu = self.load_item(seed_data,'enable_menu', self.settings.enable_menu) - self.kickstart = self.load_item(seed_data,'kickstart') - self.kernel_options = self.load_item(seed_data,'kernel_options') - self.kernel_options_post = self.load_item(seed_data,'kernel_options_post') - self.ks_meta = self.load_item(seed_data,'ks_meta') - self.template_files = self.load_item(seed_data,'template_files', {}) - self.repos = self.load_item(seed_data,'repos', []) - self.depth = self.load_item(seed_data,'depth', 1) - self.dhcp_tag = self.load_item(seed_data,'dhcp_tag', 'default') - self.server = self.load_item(seed_data,'server', '<<inherit>>') - self.mgmt_classes = self.load_item(seed_data,'mgmt_classes', []) - self.comment = self.load_item(seed_data,'comment','') - self.ctime = self.load_item(seed_data,'ctime',0) - self.mtime = self.load_item(seed_data,'mtime',0) - self.name_servers = self.load_item(seed_data,'name_servers',[]) - self.redhat_management_key = self.load_item(seed_data,'redhat_management_key', '<<inherit>>') + self.parent = self.load_item(seed_data,'parent','') + self.name = self.load_item(seed_data,'name') + self.owners = self.load_item(seed_data,'owners',self.settings.default_ownership) + self.distro = self.load_item(seed_data,'distro') + self.enable_menu = self.load_item(seed_data,'enable_menu', self.settings.enable_menu) + self.kickstart = self.load_item(seed_data,'kickstart') + self.kernel_options = self.load_item(seed_data,'kernel_options') + self.kernel_options_post = self.load_item(seed_data,'kernel_options_post') + self.ks_meta = self.load_item(seed_data,'ks_meta') + self.template_files = self.load_item(seed_data,'template_files', {}) + self.repos = self.load_item(seed_data,'repos', []) + self.depth = self.load_item(seed_data,'depth', 1) + self.dhcp_tag = self.load_item(seed_data,'dhcp_tag', 'default') + self.server = self.load_item(seed_data,'server', '<<inherit>>') + self.mgmt_classes = self.load_item(seed_data,'mgmt_classes', []) + self.comment = self.load_item(seed_data,'comment','') + self.ctime = self.load_item(seed_data,'ctime',0) + self.mtime = self.load_item(seed_data,'mtime',0) + self.name_servers = self.load_item(seed_data,'name_servers',[]) + self.name_servers_search = self.load_item(seed_data,'name_servers_search',[]) + self.redhat_management_key = self.load_item(seed_data,'redhat_management_key', '<<inherit>>') + self.redhat_management_server = self.load_item(seed_data,'redhat_management_server', '<<inherit>>') # backwards compatibility if type(self.repos) != list: @@ -179,11 +183,20 @@ class Profile(item.Item): def set_redhat_management_key(self,key): return utils.set_redhat_management_key(self,key) + def set_redhat_management_server(self,server): + return utils.set_redhat_management_server(self,server) + def set_name_servers(self,data): - data = utils.input_string_or_list(data) + # FIXME: move to utils since shared with system + data = utils.input_string_or_list(data, delim=" ") self.name_servers = data return True + def set_name_servers_search(self,data): + data = utils.input_string_or_list(data, delim=" ") + self.name_servers_search = data + return True + def set_enable_menu(self,enable_menu): """ Sets whether or not the profile will be listed in the default @@ -200,7 +213,7 @@ class Profile(item.Item): def set_server(self,server): if server is None or server == "": - server = "<inherit>" + server = "<<inherit>>" self.server = server return True @@ -279,34 +292,36 @@ class Profile(item.Item): Return hash representation for the serializer """ return { - 'name' : self.name, - 'owners' : self.owners, - 'distro' : self.distro, - 'enable_menu' : self.enable_menu, - 'kickstart' : self.kickstart, - 'kernel_options' : self.kernel_options, - 'kernel_options_post' : self.kernel_options_post, - 'virt_file_size' : self.virt_file_size, - 'virt_ram' : self.virt_ram, - 'virt_bridge' : self.virt_bridge, - 'virt_cpus' : self.virt_cpus, - 'ks_meta' : self.ks_meta, - 'template_files' : self.template_files, - 'repos' : self.repos, - 'parent' : self.parent, - 'depth' : self.depth, - 'virt_type' : self.virt_type, - 'virt_path' : self.virt_path, - 'dhcp_tag' : self.dhcp_tag, - 'server' : self.server, - 'mgmt_classes' : self.mgmt_classes, - 'comment' : self.comment, - 'ctime' : self.ctime, - 'mtime' : self.mtime, - 'name_servers' : self.name_servers, - 'uid' : self.uid, - 'random_id' : self.random_id, - 'redhat_management_key' : self.redhat_management_key + 'name' : self.name, + 'owners' : self.owners, + 'distro' : self.distro, + 'enable_menu' : self.enable_menu, + 'kickstart' : self.kickstart, + 'kernel_options' : self.kernel_options, + 'kernel_options_post' : self.kernel_options_post, + 'virt_file_size' : self.virt_file_size, + 'virt_ram' : self.virt_ram, + 'virt_bridge' : self.virt_bridge, + 'virt_cpus' : self.virt_cpus, + 'ks_meta' : self.ks_meta, + 'template_files' : self.template_files, + 'repos' : self.repos, + 'parent' : self.parent, + 'depth' : self.depth, + 'virt_type' : self.virt_type, + 'virt_path' : self.virt_path, + 'dhcp_tag' : self.dhcp_tag, + 'server' : self.server, + 'mgmt_classes' : self.mgmt_classes, + 'comment' : self.comment, + 'ctime' : self.ctime, + 'mtime' : self.mtime, + 'name_servers' : self.name_servers, + 'name_servers_search' : self.name_servers_search, + 'uid' : self.uid, + 'random_id' : self.random_id, + 'redhat_management_key' : self.redhat_management_key, + 'redhat_management_server' : self.redhat_management_server } def printable(self): @@ -328,9 +343,11 @@ class Profile(item.Item): buf = buf + _("mgmt classes : %s\n") % self.mgmt_classes buf = buf + _("modified : %s\n") % time.ctime(self.mtime) buf = buf + _("name servers : %s\n") % self.name_servers + buf = buf + _("name servers search : %s\n") % self.name_servers_search buf = buf + _("owners : %s\n") % self.owners buf = buf + _("post kernel options : %s\n") % self.kernel_options_post buf = buf + _("redhat mgmt key : %s\n") % self.redhat_management_key + buf = buf + _("redhat mgmt server : %s\n") % self.redhat_management_server buf = buf + _("repos : %s\n") % self.repos buf = buf + _("server : %s\n") % self.server buf = buf + _("template_files : %s\n") % self.template_files @@ -345,40 +362,42 @@ class Profile(item.Item): def remote_methods(self): return { - 'name' : self.set_name, - 'parent' : self.set_parent, - 'profile' : self.set_name, - 'distro' : self.set_distro, - 'enable-menu' : self.set_enable_menu, - 'enable_menu' : self.set_enable_menu, - 'kickstart' : self.set_kickstart, - 'kopts' : self.set_kernel_options, - 'kopts-post' : self.set_kernel_options_post, - 'kopts_post' : self.set_kernel_options_post, - 'virt-file-size' : self.set_virt_file_size, - 'virt_file_size' : self.set_virt_file_size, - 'virt-ram' : self.set_virt_ram, - 'virt_ram' : self.set_virt_ram, - 'ksmeta' : self.set_ksmeta, - 'template-files' : self.set_template_files, - 'template_files' : self.set_template_files, - 'repos' : self.set_repos, - 'virt-path' : self.set_virt_path, - 'virt_path' : self.set_virt_path, - 'virt-type' : self.set_virt_type, - 'virt_type' : self.set_virt_type, - 'virt-bridge' : self.set_virt_bridge, - 'virt_bridge' : self.set_virt_bridge, - 'virt-cpus' : self.set_virt_cpus, - 'virt_cpus' : self.set_virt_cpus, - 'dhcp-tag' : self.set_dhcp_tag, - 'dhcp_tag' : self.set_dhcp_tag, - 'server' : self.set_server, - 'owners' : self.set_owners, - 'mgmt-classes' : self.set_mgmt_classes, - 'mgmt_classes' : self.set_mgmt_classes, - 'comment' : self.set_comment, - 'name_servers' : self.set_name_servers, - 'redhat_management_key' : self.set_redhat_management_key + 'name' : self.set_name, + 'parent' : self.set_parent, + 'profile' : self.set_name, + 'distro' : self.set_distro, + 'enable-menu' : self.set_enable_menu, + 'enable_menu' : self.set_enable_menu, + 'kickstart' : self.set_kickstart, + 'kopts' : self.set_kernel_options, + 'kopts-post' : self.set_kernel_options_post, + 'kopts_post' : self.set_kernel_options_post, + 'virt-file-size' : self.set_virt_file_size, + 'virt_file_size' : self.set_virt_file_size, + 'virt-ram' : self.set_virt_ram, + 'virt_ram' : self.set_virt_ram, + 'ksmeta' : self.set_ksmeta, + 'template-files' : self.set_template_files, + 'template_files' : self.set_template_files, + 'repos' : self.set_repos, + 'virt-path' : self.set_virt_path, + 'virt_path' : self.set_virt_path, + 'virt-type' : self.set_virt_type, + 'virt_type' : self.set_virt_type, + 'virt-bridge' : self.set_virt_bridge, + 'virt_bridge' : self.set_virt_bridge, + 'virt-cpus' : self.set_virt_cpus, + 'virt_cpus' : self.set_virt_cpus, + 'dhcp-tag' : self.set_dhcp_tag, + 'dhcp_tag' : self.set_dhcp_tag, + 'server' : self.set_server, + 'owners' : self.set_owners, + 'mgmt-classes' : self.set_mgmt_classes, + 'mgmt_classes' : self.set_mgmt_classes, + 'comment' : self.set_comment, + 'name_servers' : self.set_name_servers, + 'name_servers_search' : self.set_name_servers_search, + 'redhat_management_key' : self.set_redhat_management_key, + 'redhat_management_server' : self.set_redhat_management_server } diff --git a/cobbler/item_repo.py b/cobbler/item_repo.py index 3a62a21f..02622004 100644 --- a/cobbler/item_repo.py +++ b/cobbler/item_repo.py @@ -83,6 +83,7 @@ class Repo(item.Item): self.set_mirror_locally(self.mirror_locally) self.set_owners(self.owners) self.set_environment(self.environment) + self.set_arch(self.arch) self._guess_breed() self.uid = self.load_item(seed_data,'uid','') @@ -177,17 +178,7 @@ class Repo(item.Item): contains games, and we probably don't want those), make it possible to list the packages one wants out of those repos, so only those packages + deps can be mirrored. """ - if rpms is None: - rpms = "" - if type(rpms) != list: - rpmlist = rpms.split(None) - else: - rpmlist = rpms - try: - rpmlist.remove('') - except: - pass - self.rpm_list = rpmlist + self.rpm_list = utils.input_string_or_list(rpms,delim=" ") return True def set_createrepo_flags(self,createrepo_flags): @@ -208,7 +199,7 @@ class Repo(item.Item): """ Override the arch used for reposync """ - return utils.set_arch(self,arch) + return utils.set_arch(self,arch,repo=True) def is_valid(self): """ diff --git a/cobbler/item_system.py b/cobbler/item_system.py index d1411092..b7e77448 100644 --- a/cobbler/item_system.py +++ b/cobbler/item_system.py @@ -38,65 +38,65 @@ class System(item.Item): return cloned def clear(self,is_subobject=False): - self.name = None - self.uid = "" - self.owners = self.settings.default_ownership - self.profile = None - self.image = None - self.kernel_options = {} - self.kernel_options_post = {} - self.ks_meta = {} - self.interfaces = {} - self.netboot_enabled = True - self.depth = 2 - self.mgmt_classes = [] - self.template_files = {} - self.kickstart = "<<inherit>>" # use value in profile - self.server = "<<inherit>>" # "" (or settings) - self.virt_path = "<<inherit>>" # "" - self.virt_type = "<<inherit>>" # "" - self.virt_cpus = "<<inherit>>" # "" - self.virt_file_size = "<<inherit>>" # "" - self.virt_ram = "<<inherit>>" # "" - self.virt_type = "<<inherit>>" # "" - self.virt_path = "<<inherit>>" # "" - self.virt_bridge = "<<inherit>>" # "" - self.comment = "" - self.ctime = 0 - self.mtime = 0 - self.uid = "" - self.random_id = "" - self.power_type = self.settings.power_management_default_type - self.power_address = "" - self.power_user = "" - self.power_pass = "" - self.power_id = "" - self.hostname = "" - self.gateway = "" - self.name_servers = "" - self.bonding = "" - self.bonding_master = "" - self.bonding_opts = "" - self.redhat_management_key = "<<inherit>>" + self.name = None + self.uid = "" + self.owners = self.settings.default_ownership + self.profile = None + self.image = None + self.kernel_options = {} + self.kernel_options_post = {} + self.ks_meta = {} + self.interfaces = {} + self.netboot_enabled = True + self.depth = 2 + self.mgmt_classes = [] + self.template_files = {} + self.kickstart = "<<inherit>>" # use value in profile + self.server = "<<inherit>>" # "" (or settings) + self.virt_path = "<<inherit>>" # "" + self.virt_type = "<<inherit>>" # "" + self.virt_cpus = "<<inherit>>" # "" + self.virt_file_size = "<<inherit>>" # "" + self.virt_ram = "<<inherit>>" # "" + self.virt_type = "<<inherit>>" # "" + self.virt_path = "<<inherit>>" # "" + self.virt_bridge = "<<inherit>>" # "" + self.comment = "" + self.ctime = 0 + self.mtime = 0 + self.uid = "" + self.random_id = "" + self.power_type = self.settings.power_management_default_type + self.power_address = "" + self.power_user = "" + self.power_pass = "" + self.power_id = "" + self.hostname = "" + self.gateway = "" + self.name_servers = [] + self.name_servers_search = [] + self.bonding = "" + self.bonding_master = "" + self.bonding_opts = "" + self.redhat_management_key = "<<inherit>>" + self.redhat_management_server = "<<inherit>>" def delete_interface(self,name): """ - Used to remove an interface. Not valid for the default -interface. + Used to remove an interface. """ - if self.interfaces.has_key(name) and name != "eth0": + if self.interfaces.has_key(name) and len(self.interfaces) > 1: del self.interfaces[name] else: - if name == "eth0": - raise CX(_("Interface %s can never be deleted") % name) - else: + if not self.interfaces.has_key(name): raise CX(_("Cannot delete interface that is not present: %s") % name) + else: + raise CX(_("At least one interface needs to be defined.")) + return True def __get_interface(self,name): - if name is None: - return self.__get_default_interface() if not self.interfaces.has_key(name): self.interfaces[name] = { @@ -115,8 +115,6 @@ interface. return self.interfaces[name] - def __get_default_interface(self): - return self.__get_interface("eth0") def from_datastruct(self,seed_data): @@ -179,7 +177,9 @@ interface. self.hostname = self.load_item(seed_data, 'hostname', __hostname) self.name_servers = self.load_item(seed_data, 'name_servers', '<<inherit>>') + self.name_servers_search = self.load_item(seed_data, 'name_servers_search', '<<inherit>>') self.redhat_management_key = self.load_item(seed_data, 'redhat_management_key', '<<inherit>>') + self.redhat_management_server = self.load_item(seed_data, 'redhat_management_server', '<<inherit>>') # virt specific @@ -290,6 +290,12 @@ interface. self.set_image(self.image) self.set_profile(self.profile) + + # enforce that the system extends from a profile or system but not both + # profile wins as it's the more common usage + self.set_image(self.image) + self.set_profile(self.profile) + return self def get_parent(self): @@ -308,8 +314,6 @@ interface. Set the name. If the name is a MAC or IP, and the first MAC and/or IP is not defined, go ahead and fill that value in. """ - intf = self.__get_default_interface() - if self.name not in ["",None] and self.parent not in ["",None] and self.name == self.parent: raise CX(_("self parentage is weird")) @@ -319,10 +323,15 @@ interface. if not x.isalnum() and not x in [ "_", "-", ".", ":", "+" ] : raise CX(_("invalid characters in name: %s") % x) + # Stuff here defaults to eth0. Yes, it's ugly and hardcoded, but so was + # the default interface behaviour that's now removed. ;) + # --Jasper Capel if utils.is_mac(name): + intf = self.__get_interface("eth0") if intf["mac_address"] == "": intf["mac_address"] = name elif utils.is_ip(name): + intf = self.__get_interface("eth0") if intf["ip_address"] == "": intf["ip_address"] = name self.name = name @@ -332,6 +341,9 @@ interface. def set_redhat_management_key(self,key): return utils.set_redhat_management_key(self,key) + def set_redhat_management_server(self,server): + return utils.set_redhat_management_server(self,server) + def set_server(self,server): """ If a system can't reach the boot server at the value configured in settings @@ -351,7 +363,7 @@ interface. intf = self.__get_interface(interface) if intf["mac_address"] != "": - return intf["mac_address"] + return intf["mac_address"].strip() else: return None @@ -364,7 +376,7 @@ interface. intf = self.__get_interface(interface) if intf["ip_address"] != "": - return intf["ip_address"] + return intf["ip_address"].strip() else: return None @@ -387,12 +399,6 @@ interface. return True return False - def set_default_interface(self,interface): - if self.interfaces.has_key(interface): - self.default_interface = interface - else: - raise CX(_("invalid interface (%s)") % interface) - def set_dhcp_tag(self,dhcp_tag,interface): intf = self.__get_interface(interface) intf["dhcp_tag"] = dhcp_tag @@ -427,7 +433,7 @@ interface. """ intf = self.__get_interface(interface) if address == "" or utils.is_ip(address): - intf["ip_address"] = address + intf["ip_address"] = address.strip() return True raise CX(_("invalid format for IP address (%s)") % address) @@ -445,16 +451,23 @@ interface. return True def set_name_servers(self,data): - data = utils.input_string_or_list(data) + data = utils.input_string_or_list(data, delim=" ") self.name_servers = data return True + def set_name_servers_search(self,data): + data = utils.input_string_or_list(data, delim=" ") + self.name_servers_search = data + return True + def set_subnet(self,subnet,interface): intf = self.__get_interface(interface) intf["subnet"] = subnet return True def set_virt_bridge(self,bridge,interface): + if bridge == "": + bridge = self.settings.default_virt_bridge intf = self.__get_interface(interface) intf["virt_bridge"] = bridge return True @@ -591,7 +604,7 @@ interface. if power_type is None: power_type = "" power_type = power_type.lower() - valid = "bullpap wti apc_snmp ether-wake ipmilan drac ipmitool ilo rsai lpar bladecenter virsh none" + valid = "bullpap wti apc_snmp ether-wake ipmilan drac ipmitool ilo rsa lpar bladecenter virsh integrity none" choices = valid.split(" ") choices.sort() if power_type not in choices: @@ -629,41 +642,43 @@ interface. def to_datastruct(self): return { - 'name' : self.name, - 'uid' : self.uid, - 'random_id' : self.random_id, - 'kernel_options' : self.kernel_options, - 'kernel_options_post' : self.kernel_options_post, - 'depth' : self.depth, - 'interfaces' : self.interfaces, - 'ks_meta' : self.ks_meta, - 'kickstart' : self.kickstart, - 'netboot_enabled' : self.netboot_enabled, - 'owners' : self.owners, - 'parent' : self.parent, - 'profile' : self.profile, - 'image' : self.image, - 'server' : self.server, - 'virt_cpus' : self.virt_cpus, - 'virt_bridge' : self.virt_bridge, - 'virt_file_size' : self.virt_file_size, - 'virt_path' : self.virt_path, - 'virt_ram' : self.virt_ram, - 'virt_type' : self.virt_type, - 'mgmt_classes' : self.mgmt_classes, - 'template_files' : self.template_files, - 'comment' : self.comment, - 'ctime' : self.ctime, - 'mtime' : self.mtime, - 'power_type' : self.power_type, - 'power_address' : self.power_address, - 'power_user' : self.power_user, - 'power_pass' : self.power_pass, - 'power_id' : self.power_id, - 'hostname' : self.hostname, - 'gateway' : self.gateway, - 'name_servers' : self.name_servers, - 'redhat_management_key' : self.redhat_management_key + 'name' : self.name, + 'uid' : self.uid, + 'random_id' : self.random_id, + 'kernel_options' : self.kernel_options, + 'kernel_options_post' : self.kernel_options_post, + 'depth' : self.depth, + 'interfaces' : self.interfaces, + 'ks_meta' : self.ks_meta, + 'kickstart' : self.kickstart, + 'netboot_enabled' : self.netboot_enabled, + 'owners' : self.owners, + 'parent' : self.parent, + 'profile' : self.profile, + 'image' : self.image, + 'server' : self.server, + 'virt_cpus' : self.virt_cpus, + 'virt_bridge' : self.virt_bridge, + 'virt_file_size' : self.virt_file_size, + 'virt_path' : self.virt_path, + 'virt_ram' : self.virt_ram, + 'virt_type' : self.virt_type, + 'mgmt_classes' : self.mgmt_classes, + 'template_files' : self.template_files, + 'comment' : self.comment, + 'ctime' : self.ctime, + 'mtime' : self.mtime, + 'power_type' : self.power_type, + 'power_address' : self.power_address, + 'power_user' : self.power_user, + 'power_pass' : self.power_pass, + 'power_id' : self.power_id, + 'hostname' : self.hostname, + 'gateway' : self.gateway, + 'name_servers' : self.name_servers, + 'name_servers_search' : self.name_servers_search, + 'redhat_management_key' : self.redhat_management_key, + 'redhat_management_server' : self.redhat_management_server } def printable(self): @@ -682,6 +697,7 @@ interface. buf = buf + _("modified : %s\n") % time.ctime(self.mtime) buf = buf + _("name servers : %s\n") % self.name_servers + buf = buf + _("name servers search : %s\n") % self.name_servers_search buf = buf + _("netboot enabled? : %s\n") % self.netboot_enabled buf = buf + _("owners : %s\n") % self.owners buf = buf + _("server : %s\n") % self.server @@ -745,50 +761,52 @@ interface. # compatibility. At some point they may be removed. return { - 'name' : self.set_name, - 'profile' : self.set_profile, - 'image' : self.set_image, - 'kopts' : self.set_kernel_options, - 'kopts-post' : self.set_kernel_options_post, - 'kopts_post' : self.set_kernel_options_post, - 'ksmeta' : self.set_ksmeta, - 'kickstart' : self.set_kickstart, - 'netboot-enabled' : self.set_netboot_enabled, - 'netboot_enabled' : self.set_netboot_enabled, - 'virt-path' : self.set_virt_path, - 'virt_path' : self.set_virt_path, - 'virt-type' : self.set_virt_type, - 'virt_type' : self.set_virt_type, - 'modify-interface' : self.modify_interface, - 'modify_interface' : self.modify_interface, - 'delete-interface' : self.delete_interface, - 'delete_interface' : self.delete_interface, - 'virt-path' : self.set_virt_path, - 'virt_path' : self.set_virt_path, - 'virt-ram' : self.set_virt_ram, - 'virt_ram' : self.set_virt_ram, - 'virt-type' : self.set_virt_type, - 'virt_type' : self.set_virt_type, - 'virt-cpus' : self.set_virt_cpus, - 'virt_cpus' : self.set_virt_cpus, - 'virt-file-size' : self.set_virt_file_size, - 'virt_file_size' : self.set_virt_file_size, - 'server' : self.set_server, - 'owners' : self.set_owners, - 'mgmt-classes' : self.set_mgmt_classes, - 'mgmt_classes' : self.set_mgmt_classes, - 'template-files' : self.set_template_files, - 'template_files' : self.set_template_files, - 'comment' : self.set_comment, - 'power_type' : self.set_power_type, - 'power_address' : self.set_power_address, - 'power_user' : self.set_power_user, - 'power_pass' : self.set_power_pass, - 'power_id' : self.set_power_id, - 'hostname' : self.set_hostname, - 'gateway' : self.set_gateway, - 'name_servers' : self.set_name_servers, - 'redhat_management_key' : self.set_redhat_management_key + 'name' : self.set_name, + 'profile' : self.set_profile, + 'image' : self.set_image, + 'kopts' : self.set_kernel_options, + 'kopts-post' : self.set_kernel_options_post, + 'kopts_post' : self.set_kernel_options_post, + 'ksmeta' : self.set_ksmeta, + 'kickstart' : self.set_kickstart, + 'netboot-enabled' : self.set_netboot_enabled, + 'netboot_enabled' : self.set_netboot_enabled, + 'virt-path' : self.set_virt_path, + 'virt_path' : self.set_virt_path, + 'virt-type' : self.set_virt_type, + 'virt_type' : self.set_virt_type, + 'modify-interface' : self.modify_interface, + 'modify_interface' : self.modify_interface, + 'delete-interface' : self.delete_interface, + 'delete_interface' : self.delete_interface, + 'virt-path' : self.set_virt_path, + 'virt_path' : self.set_virt_path, + 'virt-ram' : self.set_virt_ram, + 'virt_ram' : self.set_virt_ram, + 'virt-type' : self.set_virt_type, + 'virt_type' : self.set_virt_type, + 'virt-cpus' : self.set_virt_cpus, + 'virt_cpus' : self.set_virt_cpus, + 'virt-file-size' : self.set_virt_file_size, + 'virt_file_size' : self.set_virt_file_size, + 'server' : self.set_server, + 'owners' : self.set_owners, + 'mgmt-classes' : self.set_mgmt_classes, + 'mgmt_classes' : self.set_mgmt_classes, + 'template-files' : self.set_template_files, + 'template_files' : self.set_template_files, + 'comment' : self.set_comment, + 'power_type' : self.set_power_type, + 'power_address' : self.set_power_address, + 'power_user' : self.set_power_user, + 'power_pass' : self.set_power_pass, + 'power_id' : self.set_power_id, + 'hostname' : self.set_hostname, + 'gateway' : self.set_gateway, + 'name_servers' : self.set_name_servers, + 'name_servers_search' : self.set_name_servers_search, + 'redhat_management_key' : self.set_redhat_management_key, + 'redhat_management_server' : self.set_redhat_management_server } diff --git a/cobbler/manage_ctrl.py b/cobbler/manage_ctrl.py index 10d44e8b..95ad5b8f 100644 --- a/cobbler/manage_ctrl.py +++ b/cobbler/manage_ctrl.py @@ -30,7 +30,6 @@ import sys import glob import traceback import errno -import popen2 from shlex import shlex diff --git a/cobbler/module_loader.py b/cobbler/module_loader.py index a7be1f68..f50e4bf4 100644 --- a/cobbler/module_loader.py +++ b/cobbler/module_loader.py @@ -82,7 +82,7 @@ def load_modules(module_path=mod_path, blacklist=None): def get_module_by_name(name): return MODULE_CACHE.get(name, None) -def get_module_from_file(category,field,fallback_module_name=None): +def get_module_from_file(category,field,fallback_module_name=None,just_name=False): try: value = cp.get(category,field) @@ -91,6 +91,8 @@ def get_module_from_file(category,field,fallback_module_name=None): value = fallback_module_name else: raise CX(_("Cannot find config file setting for: %s") % field) + if just_name: + return value rc = MODULE_CACHE.get(value, None) if rc is None: raise CX(_("Failed to load module for %s/%s") % (category,field)) diff --git a/cobbler/modules/authn_configfile.py b/cobbler/modules/authn_configfile.py index 1f4cdb26..b9d2996e 100644 --- a/cobbler/modules/authn_configfile.py +++ b/cobbler/modules/authn_configfile.py @@ -26,7 +26,7 @@ import ConfigParser import sys import os from utils import _ -import md5 +from utils import md5 import traceback plib = distutils.sysconfig.get_python_lib() @@ -75,7 +75,7 @@ def authenticate(api_handle,username,password): for (user,realm,actual_blob) in userlist: if user == username and realm == "Cobbler": input = ":".join([user,realm,password]) - input_blob = md5.md5(input).hexdigest() + input_blob = md5(input).hexdigest() if input_blob.lower() == actual_blob.lower(): return True diff --git a/cobbler/modules/authn_ldap.py b/cobbler/modules/authn_ldap.py index e4313e07..a8718c5b 100644 --- a/cobbler/modules/authn_ldap.py +++ b/cobbler/modules/authn_ldap.py @@ -23,7 +23,6 @@ import distutils.sysconfig import sys import os from utils import _ -import md5 import traceback # we'll import this just a bit later diff --git a/cobbler/modules/authn_spacewalk.py b/cobbler/modules/authn_spacewalk.py index 45a26a01..eec3bd7c 100644 --- a/cobbler/modules/authn_spacewalk.py +++ b/cobbler/modules/authn_spacewalk.py @@ -29,35 +29,126 @@ plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) - def register(): """ The mandatory cobbler module registration hook. """ return "authn" +def __looks_like_a_token(password): + + # what spacewalk sends us could be an internal token or it could be a password + # if it's long and lowercase hex, it's /likely/ a token, and we should try to treat + # it as a token first, if not, we should treat it as a password. All of this + # code is there to avoid extra XMLRPC calls, which are slow. + + # we can't use binascii.unhexlify here as it's an "odd length string" + + if password.lower() != password: + # tokens are always lowercase, this isn't a token + return False + + #try: + # #data = binascii.unhexlify(password) + # return True # looks like a token, but we can't be sure + #except: + # return False # definitely not a token + + return (len(password) > 45) + def authenticate(api_handle,username,password): """ Validate a username/password combo, returning True/False This will pass the username and password back to Spacewalk to see if this authentication request is valid. + + See also: http://www.redhat.com/spacewalk/documentation/api/0.4/ + """ - #spacewalk_url = api_handle.settings().spacewalk_url - server = api_handle.settings().redhat_management_server + if api_handle is not None: + server = api_handle.settings().redhat_management_server + user_enabled = api_handle.settings().redhat_management_permissive + else: + server = "columbia.devel.redhat.com" + user_enabled = True + if server == "xmlrpc.rhn.redhat.com": - return False # don't bother RHN! + return False # emergency fail, don't bother RHN! + spacewalk_url = "https://%s/rpc/api" % server client = xmlrpclib.Server(spacewalk_url, verbose=0) - valid = client.auth.checkAuthToken(username,password) - - if valid is None: - return False + if __looks_like_a_token(password) or username == 'taskomatic_user': + + # The tokens + # are lowercase hex, but a password can also be lowercase hex, + # so we have to try it as both a token and then a password if + # we are unsure. We do it this way to be faster but also to avoid + # any login failed stuff in the logs that we don't need to send. + + try: + valid = client.auth.checkAuthToken(username,password) + except: + # if the token is not a token this will raise an exception + # rather than return an integer. + valid = 0 + + # problem at this point, 0xdeadbeef is valid as a token but if that + # fails, it's also a valid password, so we must try auth system #2 + + if valid != 1: + # first API code returns 1 on success + # the second uses exceptions for login failed. + # + # so... token check failed, but maybe the username/password + # is just a simple username/pass! + + if user_enabled == 0: + # this feature must be explicitly enabled. + return False + + + session = "" + try: + session = client.auth.login(username,password) + except: + # FIXME: should log exceptions that are not excepted + # as we could detect spacewalk java errors here that + # are not login related. + return False + + # login success by username, role must also match + roles = client.user.listRoles(session, username) + if not ("config_admin" in roles or "org_admin" in roles): + return False + + return True - return (valid == 1) - + else: + + # it's an older version of spacewalk, so just try the username/pass + # OR: we know for sure it's not a token because it's not lowercase hex. + + if user_enabled == 0: + # this feature must be explicitly enabled. + return False + + + session = "" + try: + session = client.auth.login(username,password) + except: + return False + + # login success by username, role must also match + roles = client.user.listRoles(session, username) + if not ("config_admin" in roles or "org_admin" in roles): + return False + return True +if __name__ == "__main__": + print authenticate(None,"admin","redhat") diff --git a/cobbler/modules/cli_distro.py b/cobbler/modules/cli_distro.py index 0c1425b1..7105b403 100644 --- a/cobbler/modules/cli_distro.py +++ b/cobbler/modules/cli_distro.py @@ -28,7 +28,7 @@ mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) from utils import _ -import commands +import cobbler.commands as commands import cexceptions @@ -61,6 +61,7 @@ class DistroFunction(commands.CobblerFunction): p.add_option("--ksmeta", dest="ksmeta", help="ex: 'blippy=7'") p.add_option("--mgmt-classes", dest="mgmt_classes", help="list of config management classes (for Puppet, etc)") p.add_option("--redhat-management-key", dest="redhat_management_key", help="authentication token for RHN/Spacewalk/Satellite") + p.add_option("--redhat-management-server", dest="redhat_management_server", help="RHN/Spacewalk/Satellite server") p.add_option("--template-files", dest="template_files", help="specify files to be generated from templates during a sync") p.add_option("--name", dest="name", help="ex: 'RHEL-5-i386' (REQUIRED)") @@ -118,6 +119,8 @@ class DistroFunction(commands.CobblerFunction): obj.set_template_files(self.options.template_files,self.options.inplace) if self.options.redhat_management_key is not None: obj.set_redhat_management_key(self.options.redhat_management_key) + if self.options.redhat_management_server is not None: + obj.set_redhat_management_server(self.options.redhat_management_server) return self.object_manipulator_finish(obj, self.api.distros, self.options) diff --git a/cobbler/modules/cli_image.py b/cobbler/modules/cli_image.py index b6b96ff4..0265bd78 100644 --- a/cobbler/modules/cli_image.py +++ b/cobbler/modules/cli_image.py @@ -20,7 +20,7 @@ mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) from utils import _ -import commands +import cobbler.commands as commands import cexceptions @@ -54,6 +54,8 @@ class ImageFunction(commands.CobblerFunction): if not self.matches_args(args,["dumpvars","remove","report","list"]): p.add_option("--os-version", dest="os_version", help="ex: rhel4, fedora 9") + if self.matches_args(args,["remove"]): + p.add_option("--recursive", action="store_true", dest="recursive", help="also delete child objects") if self.matches_args(args,["copy","rename"]): diff --git a/cobbler/modules/cli_misc.py b/cobbler/modules/cli_misc.py index 94ccc0d1..f83b5c8c 100644 --- a/cobbler/modules/cli_misc.py +++ b/cobbler/modules/cli_misc.py @@ -21,7 +21,7 @@ mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) from utils import _ -import commands +import cobbler.commands as commands from cexceptions import * HELP_FORMAT = commands.HELP_FORMAT @@ -63,7 +63,7 @@ class CheckFunction(commands.CobblerFunction): if len(status) == 0: self.logprint(fd,"No setup problems found") - self.logprint(fd,"Manual review and editing of /var/lib/cobbler/settings is recommended to tailor cobbler to your particular configuration.") + self.logprint(fd,"Manual review and editing of /etc/cobbler/settings is recommended to tailor cobbler to your particular configuration.") self.logprint(fd,"Good luck.") return True else: @@ -172,7 +172,10 @@ class StatusFunction(commands.CobblerFunction): ######################################################## class SyncFunction(commands.CobblerFunction): - + + def add_options(self, p, args): + p.add_option("--verbose", dest="verbose", action="store_true", help="run sync with more output") + def help_me(self): return HELP_FORMAT % ("cobbler sync","") @@ -180,7 +183,7 @@ class SyncFunction(commands.CobblerFunction): return "sync" def run(self): - return self.api.sync() + return self.api.sync(verbose=self.options.verbose) ######################################################## @@ -222,6 +225,9 @@ class BuildIsoFunction(commands.CobblerFunction): p.add_option("--profiles", dest="profiles", help="(OPTIONAL) use these profiles only") p.add_option("--systems", dest="systems", help="(OPTIONAL) use these systems only") p.add_option("--tempdir", dest="tempdir", help="(OPTIONAL) working directory") + p.add_option("--distro", dest="distro", help="(OPTIONAL) used with --standalone to create a distro-based ISO including all associated profiles/systems") + p.add_option("--standalone", dest="standalone", action="store_true", help="(OPTIONAL) creates a standalone ISO with all required distro files on it") + p.add_option("--source", dest="source", help="(OPTIONAL) used with --standalone to specify a source for the distribution files") def help_me(self): return HELP_FORMAT % ("cobbler buildiso","[ARGS]") @@ -234,7 +240,10 @@ class BuildIsoFunction(commands.CobblerFunction): iso=self.options.isoname, profiles=self.options.profiles, systems=self.options.systems, - tempdir=self.options.tempdir + tempdir=self.options.tempdir, + distro=self.options.distro, + standalone=self.options.standalone, + source=self.options.source ) ######################################################## diff --git a/cobbler/modules/cli_profile.py b/cobbler/modules/cli_profile.py index 1f8a5655..a3ca5d4c 100644 --- a/cobbler/modules/cli_profile.py +++ b/cobbler/modules/cli_profile.py @@ -28,7 +28,7 @@ mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) from utils import _ -import commands +import cobbler.commands as commands import cexceptions @@ -72,6 +72,7 @@ class ProfileFunction(commands.CobblerFunction): if not self.matches_args(args,["dumpvars","remove","report","getks","list"]): p.add_option("--name-servers", dest="name_servers", help="name servers for static setups") + p.add_option("--name-servers-search", dest="name_servers_search", help="name servers search path for static setups") if "copy" in args or "rename" in args: p.add_option("--newname", dest="newname") @@ -88,6 +89,7 @@ class ProfileFunction(commands.CobblerFunction): if not self.matches_args(args,["dumpvars","remove","report","getks","list"]): p.add_option("--redhat-management-key", dest="redhat_management_key", help="authentication token for RHN/Spacewalk/Satellite") + p.add_option("--redhat-management-server", dest="redhat_management_server", help="RHN/Spacewalk/Satellite server") p.add_option("--repos", dest="repos", help="names of cobbler repos") p.add_option("--server", dest="server_override", help="overrides value in settings file") p.add_option("--template-files", dest="template_files", help="specify files to be generated from templates during a sync") @@ -148,7 +150,7 @@ class ProfileFunction(commands.CobblerFunction): if self.options.dhcp_tag is not None: obj.set_dhcp_tag(self.options.dhcp_tag) if self.options.server_override is not None: - obj.set_server(self.options.server) + obj.set_server(self.options.server_overide) if self.options.owners is not None: obj.set_owners(self.options.owners) @@ -158,8 +160,12 @@ class ProfileFunction(commands.CobblerFunction): obj.set_template_files(self.options.template_files,self.options.inplace) if self.options.name_servers is not None: obj.set_name_servers(self.options.name_servers) + if self.options.name_servers_search is not None: + obj.set_name_servers_search(self.options.name_servers_search) if self.options.redhat_management_key is not None: obj.set_redhat_management_key(self.options.redhat_management_key) + if self.options.redhat_management_server is not None: + obj.set_redhat_management_server(self.options.redhat_management_server) return self.object_manipulator_finish(obj, self.api.profiles, self.options) diff --git a/cobbler/modules/cli_repo.py b/cobbler/modules/cli_repo.py index a8483dcb..53c0232c 100644 --- a/cobbler/modules/cli_repo.py +++ b/cobbler/modules/cli_repo.py @@ -28,7 +28,7 @@ mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) from utils import _ -import commands +import cobbler.commands as commands import cexceptions @@ -83,7 +83,7 @@ class RepoFunction(commands.CobblerFunction): def run(self): if self.args and "find" in self.args: - items = self.api.find_system(return_list=True, no_errors=True, **self.options.__dict__) + items = self.api.find_repo(return_list=True, no_errors=True, **self.options.__dict__) for x in items: print x.name return True diff --git a/cobbler/modules/cli_report.py b/cobbler/modules/cli_report.py index 82060dda..4bec4b5d 100644 --- a/cobbler/modules/cli_report.py +++ b/cobbler/modules/cli_report.py @@ -20,7 +20,7 @@ mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) from utils import _, get_random_mac -import commands +import cobbler.commands as commands from cexceptions import * HELP_FORMAT = commands.HELP_FORMAT diff --git a/cobbler/modules/cli_system.py b/cobbler/modules/cli_system.py index 4805f9c9..7c0924ed 100644 --- a/cobbler/modules/cli_system.py +++ b/cobbler/modules/cli_system.py @@ -28,7 +28,7 @@ mod_path="%s/cobbler" % plib sys.path.insert(0, mod_path) from utils import _, get_random_mac -import commands +import cobbler.commands as commands from cexceptions import * @@ -73,7 +73,9 @@ class SystemFunction(commands.CobblerFunction): p.add_option("--mac", dest="mac", help="ex: 'AA:BB:CC:DD:EE:FF', (RECOMMENDED)") p.add_option("--mgmt-classes", dest="mgmt_classes", help="list of config management classes (for Puppet, etc)") p.add_option("--name-servers", dest="name_servers", help="name servers for static setups") + p.add_option("--name-servers-search", dest="name_servers_search", help="name servers search path for static setups") p.add_option("--redhat-management-key", dest="redhat_management_key", help="authentication token for RHN/Spacewalk/Satellite") + p.add_option("--redhat-management-server", dest="redhat_management_server", help="RHN/Spacewalk/Satellite server") p.add_option("--static-routes", dest="static_routes", help="sets static routes (see manpage)") p.add_option("--template-files", dest="template_files",help="specify files to be generated from templates during a sync") @@ -101,7 +103,7 @@ class SystemFunction(commands.CobblerFunction): if not self.matches_args(args,["dumpvars","remove","report","getks","list"]): p.add_option("--power-pass", dest="power_pass", help="password for power management interface") if not self.matches_args(args,["dumpvars","poweron","poweroff","reboot","remove","report","getks","list"]): - p.add_option("--power-type", dest="power_type", help="one of: none, apc_snmp, bullpap, drac, ether-wake, ilo, ipmilan, ipmitool, wti, lpar, bladecenter, virsh") + p.add_option("--power-type", dest="power_type", help="one of: none, apc_snmp, bullpap, drac, ether-wake, ilo, ipmilan, ipmitool, wti, lpar, bladecenter, virsh, integrity") if not self.matches_args(args,["dumpvars","remove","report","getks","list"]): p.add_option("--power-user", dest="power_user", help="username for power management interface, if required") @@ -245,8 +247,12 @@ class SystemFunction(commands.CobblerFunction): obj.set_template_files(self.options.template_files,self.options.inplace) if self.options.name_servers is not None: obj.set_name_servers(self.options.name_servers) + if self.options.name_servers_search is not None: + obj.set_name_servers_search(self.options.name_servers_search) if self.options.redhat_management_key is not None: obj.set_redhat_management_key(self.options.redhat_management_key) + if self.options.redhat_management_server is not None: + obj.set_redhat_management_server(self.options.redhat_management_server) rc = self.object_manipulator_finish(obj, self.api.systems, self.options) diff --git a/cobbler/modules/install_post_log.py b/cobbler/modules/install_post_log.py new file mode 100644 index 00000000..02c0b55e --- /dev/null +++ b/cobbler/modules/install_post_log.py @@ -0,0 +1,29 @@ +import distutils.sysconfig +import sys +import os +from utils import _ +import traceback +import cexceptions +import os +import sys +import time + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + +def register(): + # this pure python trigger acts as if it were a legacy shell-trigger, but is much faster. + # the return of this method indicates the trigger type + return "/var/lib/cobbler/triggers/install/post/*" + +def run(api, args): + objtype = args[0] # "system" or "profile" + name = args[1] # name of system or profile + ip = args[2] # ip or "?" + + fd = open("/var/log/cobbler/install.log","a+") + fd.write("%s\t%s\t%s\tstop\t%s\n" % (objtype,name,ip,time.time())) + fd.close() + + return 0 diff --git a/cobbler/modules/install_post_report.py b/cobbler/modules/install_post_report.py new file mode 100755 index 00000000..b42753b4 --- /dev/null +++ b/cobbler/modules/install_post_report.py @@ -0,0 +1,101 @@ +# (c) 2008-2009 +# Jeff Schroeder <jeffschroeder@computer.org> +# Michael DeHaan <mdehaan@redhat.com> +# +# License: GPLv2+ + +# Post install trigger for cobbler to +# send out a pretty email report that +# contains target information. + +import distutils.sysconfig +import sys +import os +import traceback + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + +from utils import _ +import smtplib +import sys +import cobbler.templar as templar +from cobbler.cexceptions import CX +import utils + +def register(): + # this pure python trigger acts as if it were a legacy shell-trigger, but is much faster. + # the return of this method indicates the trigger type + return "/var/lib/cobbler/triggers/install/post/*" + +def run(api, args): + + settings = api.settings() + + # go no further if this feature is turned off + if not str(settings.build_reporting_enabled).lower() in [ "1", "yes", "y", "true"]: + print "not enabled" + return 0 + + objtype = args[0] # "target" or "profile" + name = args[1] # name of target or profile + boot_ip = args[2] # ip or "?" + + if objtype == "system": + target = api.find_system(name) + else: + target = api.find_profile(name) + + # collapse the object down to a rendered datastructure + target = utils.blender(api, False, target) + + if target == {}: + raise CX("failure looking up target") + + to_addr = settings.build_reporting_email + if to_addr == "": + return 0 + + # add the ability to specify an MTA for servers that don't run their own + smtp_server = settings.build_reporting_smtp_server + if smtp_server == "": + smtp_server = "localhost" + + # use a custom from address or fall back to a reasonable default + from_addr = settings.build_reporting_sender + if from_addr == "": + from_addr = "cobbler@%s" % settings.server + + subject = settings.build_reporting_subject + if subject == "": + subject = '[Cobbler] install complete ' + + to_addr = ", ".join(to_addr) + metadata = { + "from_addr" : from_addr, + "to_addr" : to_addr, + "subject" : subject, + "boot_ip" : boot_ip + } + metadata.update(target) + + input_template = open("/etc/cobbler/reporting/build_report_email.template") + input_data = input_template.read() + input_template.close() + + message = templar.Templar().render(input_data, metadata, None) + # for debug, call + # print message + + # Send the mail + # FIXME: on error, return non-zero + server_handle = smtplib.SMTP(smtp_server) + server_handle.sendmail(from_addr, to_addr, message) + server_handle.quit() + + return 0 + + + + diff --git a/cobbler/modules/install_pre_clear_anamon_logs.py b/cobbler/modules/install_pre_clear_anamon_logs.py new file mode 100755 index 00000000..381759aa --- /dev/null +++ b/cobbler/modules/install_pre_clear_anamon_logs.py @@ -0,0 +1,51 @@ +import distutils.sysconfig +import sys +import os +from utils import _ +import traceback +import cexceptions + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + +import os +import glob +import sys + + +def register(): + # this pure python trigger acts as if it were a legacy shell-trigger, but is much faster. + # the return of this method indicates the trigger type + return "/var/lib/cobbler/triggers/install/pre/*" + +def run(api, args): + + if len(args) < 3: + raise CX("invalid invocation") + + objtype = args[0] # "system" or "profile" + name = args[1] # name of system or profile + ip = args[2] # ip or "?" + + settings = api.settings() + anamon_enabled = str(settings.anamon_enabled) + + # Remove any files matched with the given glob pattern + def unlink_files(globex): + for f in glob.glob(globex): + if os.path.isfile(f): + try: + os.unlink(f) + except OSError, e: + pass + + if str(anamon_enabled) in [ "true", "1", "y", "yes"]: + dirname = "/var/log/cobbler/anamon/%s" % name + if os.path.isdir(dirname): + unlink_files(os.path.join(dirname, "*")) + + # TODO - log somewhere that we cleared a systems anamon logs + return 0 + + diff --git a/cobbler/modules/install_pre_log.py b/cobbler/modules/install_pre_log.py new file mode 100644 index 00000000..4469a514 --- /dev/null +++ b/cobbler/modules/install_pre_log.py @@ -0,0 +1,29 @@ +import distutils.sysconfig +import sys +import os +from utils import _ +import traceback +import cexceptions +import os +import sys +import time + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + +def register(): + # this pure python trigger acts as if it were a legacy shell-trigger, but is much faster. + # the return of this method indicates the trigger type + return "/var/lib/cobbler/triggers/install/pre/*" + +def run(api, args): + objtype = args[0] # "system" or "profile" + name = args[1] # name of system or profile + ip = args[2] # ip or "?" + + fd = open("/var/log/cobbler/install.log","a+") + fd.write("%s\t%s\t%s\tstart\t%s\n" % (objtype,name,ip,time.time())) + fd.close() + + return 0 diff --git a/cobbler/modules/manage_bind.py b/cobbler/modules/manage_bind.py index e7cdc3a7..657a9a77 100644 --- a/cobbler/modules/manage_bind.py +++ b/cobbler/modules/manage_bind.py @@ -30,7 +30,6 @@ import sys import glob import traceback import errno -import popen2 import re from shlex import shlex @@ -82,7 +81,13 @@ class BindManager: in them """ zones = {} - for zone in self.settings.manage_forward_zones: + forward_zones = self.settings.manage_forward_zones + if type(forward_zones) != type([]): + # gracefully handle when user inputs only a single zone + # as a string instead of a list with only a single item + forward_zones = [forward_zones] + + for zone in forward_zones: zones[zone] = {} for system in self.systems: @@ -126,7 +131,13 @@ class BindManager: in them """ zones = {} - for zone in self.settings.manage_reverse_zones: + reverse_zones = self.settings.manage_reverse_zones + if type(reverse_zones) != type([]): + # gracefully handle when user inputs only a single zone + # as a string instead of a list with only a single item + reverse_zones = [reverse_zones] + + for zone in reverse_zones: zones[zone] = {} for sys in self.systems: @@ -223,14 +234,14 @@ zone "%(arpa)s." { octets = map(lambda x: [str(i) for i in x], octets) return ['.'.join(i) for i in octets] - def __pretty_print_host_records(self, hosts, type='A', rclass='IN'): + def __pretty_print_host_records(self, hosts, rectype='A', rclass='IN'): """ Format host records by order and with consistent indentation """ names = [k for k,v in hosts.iteritems()] if not names: return '' # zones with no hosts - if type == 'PTR': + if rectype == 'PTR': names = self.__ip_sort(names) else: names.sort() @@ -239,10 +250,10 @@ zone "%(arpa)s." { s = "" for name in names: - s += "%s %s %s %s\n" % (name + (" " * (max_name - len(name))), - rclass, - type, - hosts[name]) + spacing = " " * (max_name - len(name)) + my_name = "%s%s" % (name, spacing) + my_host = hosts[name] + s += "%s %s %s %s\n" % (my_name, rclass, rectype, my_host) return s def __write_zone_files(self): @@ -297,7 +308,7 @@ zone "%(arpa)s." { except: template_data = default_template_data - metadata['host_record'] = self.__pretty_print_host_records(hosts, type='PTR') + metadata['host_record'] = self.__pretty_print_host_records(hosts, rectype='PTR') self.templar.render(template_data, metadata, '/var/named/' + zone, None) diff --git a/cobbler/modules/manage_dnsmasq.py b/cobbler/modules/manage_dnsmasq.py index 344020f8..4a9b47d5 100644 --- a/cobbler/modules/manage_dnsmasq.py +++ b/cobbler/modules/manage_dnsmasq.py @@ -31,7 +31,6 @@ import sys import glob import traceback import errno -import popen2 from shlex import shlex import utils from cexceptions import * diff --git a/cobbler/modules/manage_isc.py b/cobbler/modules/manage_isc.py index fb22a380..9846314b 100644 --- a/cobbler/modules/manage_isc.py +++ b/cobbler/modules/manage_isc.py @@ -30,7 +30,7 @@ import sys import glob import traceback import errno -import popen2 +from utils import popen2 from shlex import shlex @@ -83,7 +83,7 @@ class IscManager: if ip.find("/") != -1: return try: - fromchild, tochild = popen2.popen2(self.settings.omshell_bin) + fromchild, tochild = popen2([self.settings.omshell_bin]) tochild.write("port %s\n" % port) tochild.flush() tochild.write("connect\n") @@ -111,7 +111,7 @@ class IscManager: Use DHCP's API to delete a DHCP entry in the /var/lib/dhcpd/dhcpd.leases file """ - fromchild, tochild = popen2.popen2(self.settings.omshell_bin) + fromchild, tochild = popen2([self.settings.omshell_bin]) try: tochild.write("port %s\n" % port) tochild.flush() diff --git a/cobbler/modules/serializer_catalog.py b/cobbler/modules/serializer_catalog.py index 35d1517a..72ab2cc3 100644 --- a/cobbler/modules/serializer_catalog.py +++ b/cobbler/modules/serializer_catalog.py @@ -25,6 +25,7 @@ import os import sys import glob import traceback +import yaml # PyYAML plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib @@ -32,8 +33,7 @@ sys.path.insert(0, mod_path) from utils import _ import utils -import yaml # Howell-Clark version -import cexceptions +from cexceptions import * import os def register(): @@ -46,7 +46,10 @@ def serialize_item(obj, item): filename = "/var/lib/cobbler/config/%ss.d/%s" % (obj.collection_type(),item.name) datastruct = item.to_datastruct() fd = open(filename,"w+") - fd.write(yaml.dump(datastruct)) + ydata = yaml.dump(datastruct) + if ydata is None or ydata == "": + raise CX("internal yaml error, tried to write empty file to %s, data was %s" % (filename, datastruct)) + fd.write(ydata) fd.close() return True @@ -63,7 +66,7 @@ def deserialize_item_raw(collection_type, item_name): if not os.path.exists(filename): return None fd = open(filename) - datastruct = yaml.load(fd.read()).next() + datastruct = yaml.load(fd.read()) fd.close() return datastruct @@ -84,14 +87,14 @@ def deserialize_raw(collection_type): old_filename = "/var/lib/cobbler/%ss" % collection_type if collection_type == "settings": fd = open("/etc/cobbler/settings") - datastruct = yaml.load(fd.read()).next() + datastruct = yaml.load(fd.read()) fd.close() return datastruct elif os.path.exists(old_filename): # for use in migration sys.stderr.write("reading from old config format: %s\n" % old_filename) fd = open(old_filename) - datastruct = yaml.load(fd.read()).next() + datastruct = yaml.load(fd.read()) fd.close() return datastruct else: @@ -99,7 +102,14 @@ def deserialize_raw(collection_type): files = glob.glob("/var/lib/cobbler/config/%ss.d/*" % collection_type) for f in files: fd = open(f) - results.append(yaml.load(fd.read()).next()) + ydata = fd.read() + if ydata is None or ydata == "": + raise CX("error, empty file %s" % f) + try: + datastruct = yaml.load(ydata) + except: + raise CX("error parsing yaml file: %s" % f) + results.append(datastruct) fd.close() return results diff --git a/cobbler/modules/serializer_yaml.py b/cobbler/modules/serializer_yaml.py index 75f89cf9..f583c7cc 100644 --- a/cobbler/modules/serializer_yaml.py +++ b/cobbler/modules/serializer_yaml.py @@ -25,6 +25,7 @@ import os import sys import glob import traceback +import yaml # PyYAML plib = distutils.sysconfig.get_python_lib() mod_path="%s/cobbler" % plib @@ -32,7 +33,6 @@ sys.path.insert(0, mod_path) from utils import _ import utils -import yaml # Howell-Clark version import cexceptions import os @@ -87,7 +87,7 @@ def deserialize_raw(collection_type): except IOError, ioe: return [{}] data = fd.read() - datastruct = yaml.load(data).next() # first record + datastruct = yaml.load(data) # first record fd.close() return datastruct @@ -120,7 +120,7 @@ def deserialize(obj,topological=False): raise cexceptions.CX(_("Need permissions to read %s") % obj.filename()) data = fd.read() try: - datastruct = yaml.load(data).next() # first record + datastruct = yaml.load(data) # first record except: # load failure, make empty list datastruct = [] diff --git a/cobbler/modules/sync_post_restart_services.py b/cobbler/modules/sync_post_restart_services.py new file mode 100644 index 00000000..fa9699b9 --- /dev/null +++ b/cobbler/modules/sync_post_restart_services.py @@ -0,0 +1,70 @@ +import distutils.sysconfig +import sys +import os +from utils import _ +import traceback +import cexceptions +import os +import sys +import xmlrpclib +import cobbler.module_loader as module_loader + +plib = distutils.sysconfig.get_python_lib() +mod_path="%s/cobbler" % plib +sys.path.insert(0, mod_path) + +def register(): + # this pure python trigger acts as if it were a legacy shell-trigger, but is much faster. + # the return of this method indicates the trigger type + return "/var/lib/cobbler/triggers/sync/post/*" + +def run(api,args): + + settings = api.settings() + + manage_dhcp = str(settings.manage_dhcp).lower() + manage_dns = str(settings.manage_dns).lower() + manage_xinetd = str(settings.manage_xinetd).lower() + restart_dhcp = str(settings.restart_dhcp).lower() + restart_dns = str(settings.restart_dns).lower() + restart_xinetd = str(settings.restart_xinetd).lower() + omapi_enabled = str(settings.omapi_enabled).lower() + omapi_port = str(settings.omapi_port).lower() + + which_dhcp_module = module_loader.get_module_from_file("dhcp","module",just_name=True).strip() + which_dns_module = module_loader.get_module_from_file("dns","module",just_name=True).strip() + + # special handling as we don't want to restart it twice + has_restarted_dnsmasq = False + + rc = 0 + if manage_dhcp != "0": + if which_dhcp_module == "manage_isc": + if not omapi_enabled in [ "1", "true", "yes", "y" ] and restart_dhcp: + rc = os.system("/usr/sbin/dhcpd -t") + if rc != 0: + print "/usr/sbin/dhcpd -t failed" + return 1 + rc = os.system("/sbin/service dhcpd restart") + elif which_dhcp_module == "manage_dnsmasq": + if restart_dhcp: + rc = os.system("/sbin/service dnsmasq restart") + has_restarted_dnsmasq = True + else: + print "- error: unknown DHCP engine: %s" % which_dhcp_module + rc = 411 + + if manage_dns != "0" and restart_dns != "0": + if which_dns_module == "manage_bind": + rc = os.system("/sbin/service named restart") + elif which_dns_module == "manage_dnsmasq" and not has_restarted_dnsmasq: + rc = os.ssytem("/sbin/service dnsmasq restart") + else: + print "- error: unknown DNS engine: %s" % which_dns_module + rc = 412 + + if manage_xinetd != "0" and restart_xinetd != "0": + rc = os.system("/sbin/service xinetd restart") + + return rc + diff --git a/cobbler/pxegen.py b/cobbler/pxegen.py index 3fcc4030..7ff07808 100644 --- a/cobbler/pxegen.py +++ b/cobbler/pxegen.py @@ -24,6 +24,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA import os import os.path import shutil +import shlex import time import sys import glob @@ -64,6 +65,7 @@ class PXEGen: self.images = config.images() self.templar = templar.Templar(config) self.bootloc = utils.tftpboot_location() + self.verbose = False def copy_bootloaders(self): """ @@ -75,27 +77,27 @@ class PXEGen: # copy syslinux from one of two locations try: - utils.copyfile_pattern('/usr/lib/syslinux/pxelinux.0', dst, api=self.api) + utils.copyfile_pattern('/usr/lib/syslinux/pxelinux.0', dst, api=self.api, verbose=self.verbose) except: - utils.copyfile_pattern('/usr/share/syslinux/pxelinux.0', dst, api=self.api) + utils.copyfile_pattern('/usr/share/syslinux/pxelinux.0', dst, api=self.api, verbose=self.verbose) # copy memtest only if we find it - utils.copyfile_pattern('/boot/memtest*', dst, require_match=False, api=self.api) + utils.copyfile_pattern('/boot/memtest*', dst, require_match=False, api=self.api, verbose=self.verbose) # copy elilo which we include for IA64 targets - utils.copyfile_pattern('/var/lib/cobbler/elilo-3.8-ia64.efi', dst, api=self.api) + utils.copyfile_pattern('/var/lib/cobbler/elilo-3.8-ia64.efi', dst, api=self.api, verbose=self.verbose) # copy menu.c32 as the older one has some bugs on certain RHEL - utils.copyfile_pattern('/var/lib/cobbler/menu.c32', dst, api=self.api) + utils.copyfile_pattern('/var/lib/cobbler/menu.c32', dst, api=self.api, verbose=self.verbose) # copy yaboot which we include for PowerPC targets - utils.copyfile_pattern('/var/lib/cobbler/yaboot-1.3.14', dst, api=self.api) + utils.copyfile_pattern('/var/lib/cobbler/yaboot-1.3.14', dst, api=self.api, verbose=self.verbose) # copy memdisk as we need it to boot ISOs try: - utils.copyfile_pattern('/usr/lib/syslinux/memdisk', dst, api=self.api) + utils.copyfile_pattern('/usr/lib/syslinux/memdisk', dst, api=self.api, verbose=self.verbose) except: - utils.copyfile_pattern('/usr/share/syslinux/memdisk', dst, api=self.api) + utils.copyfile_pattern('/usr/share/syslinux/memdisk', dst, api=self.api, verbose=self.verbose) def copy_distros(self): @@ -114,14 +116,14 @@ class PXEGen: if d.breed == "windows": continue try: + if self.verbose: + print "- copying files for distro: %s" % d.name self.copy_single_distro_files(d) except CX, e: errors.append(e) print e.value # FIXME: using logging module so this ends up in cobbler.log? - if len(errors) > 0: - raise CX(_("Error(s) encountered while copying distro files")) def copy_images(self): """ @@ -130,14 +132,14 @@ class PXEGen: errors = list() for i in self.images: try: + if self.verbose: + print "- copying files for image: %s" % i.name self.copy_single_image_files(i) except CX, e: errors.append(e) print e.value # FIXME: using logging module so this ends up in cobbler.log? - if len(errors) > 0: - raise CX(_("Error(s) encountered while copying image files")) def copy_single_distro_files(self, d): for dirtree in [self.bootloc, self.settings.webdir]: @@ -157,8 +159,9 @@ class PXEGen: allow_symlink=True dst1 = os.path.join(distro_dir, b_kernel) dst2 = os.path.join(distro_dir, b_initrd) - utils.linkfile(kernel, dst1, symlink_ok=allow_symlink, api=self.api) - utils.linkfile(initrd, dst2, symlink_ok=allow_symlink, api=self.api) + utils.linkfile(kernel, dst1, symlink_ok=allow_symlink, api=self.api, verbose=self.verbose) + + utils.linkfile(initrd, dst2, symlink_ok=allow_symlink, api=self.api, verbose=self.verbose) def copy_single_image_files(self, img): images_dir = os.path.join(self.bootloc, "images2") @@ -170,7 +173,7 @@ class PXEGen: os.makedirs(images_dir) basename = os.path.basename(img.file) newfile = os.path.join(images_dir, img.name) - utils.linkfile(filename, newfile, api=self.api) + utils.linkfile(filename, newfile, api=self.api, verbose=self.verbose) return True def generate_windows_files(self): @@ -316,6 +319,150 @@ class PXEGen: return True + def generate_windows_files(self): + # FIXME: hard coding of /tftpboot here is wrong + utils.mkdir("/tftpboot/profiles") + for p in self.profiles: + distro = p.get_conceptual_parent() + if distro and distro.breed != "windows": + continue + else: + self.generate_windows_profile_pxe(p) + utils.mkdir("/tftpboot/systems") + for s in self.systems: + profile = s.get_conceptual_parent() + if profile: + distro = profile.get_conceptual_parent() + if distro and distro.breed != "windows": + continue + else: + self.generate_windows_system_pxe(s) + + def generate_windows_profile_pxe(self, profile): + distro = profile.get_conceptual_parent() + + dest_dir = os.path.join("/tftpboot/profiles", profile.name) + utils.mkdir(dest_dir) + + utils.cabextract(distro.kernel, dest_dir, self.api) + + src_file = os.path.join(dest_dir, "startrom.n12") + dest_file = os.path.join(dest_dir, "winpxe.0") + cmd = [ "/bin/sed", "-i", "-e", "s/ntldr/L%s/gi" % profile.random_id, src_file ] + sub_process.call(cmd, shell=False, close_fds=False) + os.rename(src_file, dest_file) + + utils.cabextract(distro.initrd, dest_dir, self.api) + + src_file = os.path.join(dest_dir, "setupldr.exe") + dest_file = os.path.join(dest_dir, "NTLDR") + cmd = [ "/bin/sed", "-i", "-e", "s/winnt\\.sif/w%s.sif/gi" % profile.random_id, src_file ] + sub_process.call(cmd, shell=False, close_fds=False) + cmd = [ "/bin/sed", "-i", "-e", "s/ntdetect\\.com/ntd_%s.com/gi" % profile.random_id, src_file ] + sub_process.call(cmd, shell=False, close_fds=False) + os.rename(src_file, dest_file) + + src_dir = os.path.dirname(distro.kernel) + src_file = os.path.join(src_dir, "ntdetect.com") + file_name = os.path.join(dest_dir, "ntdetect.com") + utils.copyfile(src_file, file_name, self.api) + + template = profile.kickstart + fd = open(template, "r") + template_data = fd.read() + fd.close() + + blended = utils.blender(self.api, False, profile) + blended['next_server'] = self.settings.next_server + + ksmeta = blended.get("ks_meta",{}) + del blended["ks_meta"] + blended.update(ksmeta) # make available at top level + + # this is a workaround for a dumb bug in cheetah... + # a backslash before a variable is always treated as + # escaping the $, so things are not rendered correctly. + # The only option is to use another variable for + # backslashes when leading another variable. i.e.: + # \\$variable + blended['bsp'] = '\\' + + data = self.templar.render(template_data, blended, None) + + # write .sif to /tftpboot/profiles/$name/winnt.sif + file_name = os.path.join(dest_dir, "winnt.sif") + fd = open(file_name, "w") + fd.write(data) + fd.close() + + return True + + def generate_windows_system_pxe(self, system): + profile = system.get_conceptual_parent() + if not profile: + return False + + distro = profile.get_conceptual_parent() + + dest_dir = os.path.join("/tftpboot/systems", system.name) + utils.mkdir(dest_dir) + + utils.cabextract(distro.kernel, dest_dir, self.api) + + src_file = os.path.join(dest_dir, "startrom.n12") + dest_file = os.path.join(dest_dir, "winpxe.0") + cmd = [ "/bin/sed", "-i", "-e", "s/ntldr/L%s/gi" % system.random_id, src_file ] + sub_process.call(cmd, shell=False, close_fds=False) + os.rename(src_file, dest_file) + + utils.cabextract(distro.initrd, dest_dir, self.api) + + src_file = os.path.join(dest_dir, "setupldr.exe") + dest_file = os.path.join(dest_dir, "NTLDR") + cmd = [ "/bin/sed", "-i", "-e", "s/winnt\\.sif/w%s.sif/gi" % system.random_id, src_file ] + sub_process.call(cmd, shell=False, close_fds=False) + cmd = [ "/bin/sed", "-i", "-e", "s/ntdetect\\.com/ntd_%s.com/gi" % system.random_id, src_file ] + sub_process.call(cmd, shell=False, close_fds=False) + os.rename(src_file, dest_file) + + src_dir = os.path.dirname(distro.kernel) + src_file = os.path.join(src_dir, "ntdetect.com") + file_name = os.path.join(dest_dir, "ntdetect.com") + utils.copyfile(src_file, file_name, self.api) + + template = system.kickstart + if template == "<<inherit>>": + template = profile.kickstart + + fd = open(template, "r") + template_data = fd.read() + fd.close() + + blended = utils.blender(self.api, False, system) + blended['next_server'] = self.settings.next_server + + ksmeta = blended.get("ks_meta",{}) + del blended["ks_meta"] + blended.update(ksmeta) # make available at top level + + # this is a workaround for a dumb bug in cheetah... + # a backslash before a variable is always treated as + # escaping the $, so things are not rendered correctly. + # The only option is to use another variable for + # backslashes when leading another variable. i.e.: + # \\$variable + blended['bsp'] = '\\' + + data = self.templar.render(template_data, blended, None) + + # write .sif to /tftpboot/systems/$name/winnt.sif + file_name = os.path.join(dest_dir, "winnt.sif") + fd = open(file_name, "w") + fd.write(data) + fd.close() + + return True + def write_all_system_files(self,system): profile = system.get_conceptual_parent() @@ -331,8 +478,35 @@ class PXEGen: image_based = True image = profile - # this used to just generate a single PXE config file, but now must - # generate one record for each described NIC ... + # hack: s390 generates files per system not per interface + if not image_based and distro.arch.startswith("s390"): + # Always write a system specific _conf and _parm file + f2 = os.path.join(self.bootloc, "s390x", "s_%s" % system.name) + cf = "%s_conf" % f2 + pf = "%s_parm" % f2 + template_cf = open("/etc/cobbler/pxe/s390x_conf.template") + template_pf = open("/etc/cobbler/pxe/s390x_parm.template") + blended = utils.blender(self.api, True, system) + self.templar.render(template_cf, blended, cf) + # FIXME: profiles also need this data! + # FIXME: the _conf and _parm files are limited to 80 characters in length + kickstart_path = "http://%s/cblr/svc/op/ks/system/%s" % (blended["http_server"], system.name) + # gather default kernel_options and default kernel_options_s390x + kopts = blended.get("kernel_options","") + hkopts = shlex.split(utils.hash_to_string(kopts)) + blended["kickstart_expanded"] = "ks=%s" % kickstart_path + blended["kernel_options"] = hkopts + self.templar.render(template_pf, blended, pf) + + # Write system specific zPXE file + if system.is_management_supported(): + self.write_pxe_file(f2,system,profile,distro,distro.arch) + else: + # ensure the file doesn't exist + utils.rmfile(f2) + return + + # generate one record for each described NIC .. for (name,interface) in system.interfaces.iteritems(): @@ -345,8 +519,8 @@ class PXEGen: else: working_arch = distro.arch - if working_arch is None or working_arch == "": - working_arch = "x86" + if working_arch is None: + raise "internal error, invalid arch supplied" # for tftp only ... if working_arch in [ "i386", "x86", "x86_64", "standard"]: @@ -370,10 +544,6 @@ class PXEGen: if os.path.lexists(f3): utils.rmfile(f3) os.symlink("../yaboot-1.3.14", f3) - - elif working_arch == "s390x": - filename = "%s" % utils.get_config_filename(system,interface=name) - f2 = os.path.join(self.bootloc, "s390x", filename) else: continue @@ -405,18 +575,27 @@ class PXEGen: distro = profile.get_conceptual_parent() if distro is None: raise CX(_("profile is missing distribution: %s, %s") % (profile.name, profile.distro)) - if distro.arch == "s390x": + if distro.arch.startswith("s390"): listfile.write("%s\n" % profile.name) - f2 = os.path.join(self.bootloc, "s390x", profile.name) - self.write_pxe_file(f2,None,profile,distro,distro.arch) - listfile2 = open(os.path.join(s390path, "image_list"),"w+") - for image in image_list: - if os.path.exists(image.file): - listfile2.write("%s\n" % image.name) - f2 = os.path.join(self.bootloc, "s390x", image.name) - self.write_pxe_file(f2,None,None,None,image.arch,image=image) + f2 = os.path.join(self.bootloc, "s390x", "p_%s" % profile.name) + self.write_pxe_file(f2,None,profile,distro,distro.arch) + cf = "%s_conf" % f2 + pf = "%s_parm" % f2 + template_cf = open("/etc/cobbler/pxe/s390x_conf.template") + template_pf = open("/etc/cobbler/pxe/s390x_parm.template") + blended = utils.blender(self.api, True, profile) + self.templar.render(template_cf, blended, cf) + # FIXME: profiles also need this data! + # FIXME: the _conf and _parm files are limited to 80 characters in length + kickstart_path = "http://%s/cblr/svc/op/ks/profile/%s" % (blended["http_server"], profile.name) + # gather default kernel_options and default kernel_options_s390x + kopts = blended.get("kernel_options","") + hkopts = shlex.split(utils.hash_to_string(kopts)) + blended["kickstart_expanded"] = "ks=%s" % kickstart_path + blended["kernel_options"] = hkopts + self.templar.render(template_pf, blended, pf) + listfile.close() - listfile2.close() def make_actual_pxe_menu(self): # only do this if there is NOT a system named default. @@ -448,7 +627,7 @@ class PXEGen: continue distro = profile.get_conceptual_parent() # xen distros can be ruled out as they won't boot - if distro.name.find("-xen") != -1: + if distro.name.find("-xen") != -1 or distro.arch not in ["i386", "x86_64"]: # can't PXE Xen continue contents = self.write_pxe_file(None,None,profile,distro,distro.arch,include_header=False) @@ -524,6 +703,9 @@ class PXEGen: more details """ + if arch is None: + raise "missing arch" + if image and not os.path.exists(image.file): return None # nfs:// URLs or something, can't use for TFTP @@ -539,9 +721,10 @@ class PXEGen: initrd_path = None if image is None: - # profile or system+profile based, not image based, or system+image based + # not image based, it's something normalish if distro is not None and distro.breed == "windows": + # this is to support linux-ris if system: kernel_path = "systems/%s/winpxe.0" % system.name elif profile: @@ -551,8 +734,14 @@ class PXEGen: initrd_path = os.path.join("/images",distro.name,os.path.basename(distro.initrd)) # Find the kickstart if we inherit from another profile - kickstart_path = utils.blender(self.api, True, profile)["kickstart"] + if system: + blended = utils.blender(self.api, True, system) + else: + blended = utils.blender(self.api, True, profile) + kickstart_path = blended["kickstart"] + else: + # this is an image we are making available, not kernel+initrd if image.image_type == "direct": kernel_path = os.path.join("/images2",image.name) elif image.image_type == "memdisk": @@ -571,19 +760,18 @@ class PXEGen: else: template = os.path.join(self.settings.pxe_template_dir,"pxesystem.template") - if arch == "s390x": - template = os.path.join(self.settings.pxe_template_dir,"pxesystem_s390x.template") - elif arch == "ia64": - template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ia64.template") - elif arch.startswith("ppc"): - template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ppc.template") + if arch.startswith("s390"): + template = os.path.join(self.settings.pxe_template_dir,"pxesystem_s390x.template") + elif arch == "ia64": + template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ia64.template") + elif arch.startswith("ppc"): + template = os.path.join(self.settings.pxe_template_dir,"pxesystem_ppc.template") else: # local booting on ppc requires removing the system-specific dhcpd.conf filename if arch is not None and arch.startswith("ppc"): # Disable yaboot network booting for all interfaces on the system for (name,interface) in system.interfaces.iteritems(): - # Determine filename for system-specific yaboot.conf filename = "%s" % utils.get_config_filename(system, interface=name).lower() # Remove symlink to the yaboot binary @@ -599,15 +787,20 @@ class PXEGen: # Yaboot/OF doesn't support booting locally once you've # booted off the network, so nothing left to do return None + elif arch is not None and arch.startswith("s390"): + template = os.path.join(self.settings.pxe_template_dir,"pxelocal_s390x.template") else: template = os.path.join(self.settings.pxe_template_dir,"pxelocal.template") else: + # not a system record, so this is a profile record + if distro is not None and distro.breed == "windows": template = os.path.join(self.settings.pxe_template_dir,"pxeprofile_win.template") + elif arch.startswith("s390"): + template = os.path.join(self.settings.pxe_template_dir,"pxeprofile_s390x.template") else: template = os.path.join(self.settings.pxe_template_dir,"pxeprofile.template") - # now build the kernel command line if system is not None: blended = utils.blender(self.api, True, system) @@ -624,15 +817,18 @@ class PXEGen: else: append_line = "append %s" % hkopts + # FIXME - the append_line length limit is architecture specific if len(append_line) >= 255 + len("append "): print _("warning: kernel option length exceeds 255") # kickstart path rewriting (get URLs for local files) if kickstart_path is not None and kickstart_path != "": + # FIXME: need to make shorter rewrite rules for these URLs + if system is not None and kickstart_path.startswith("/"): kickstart_path = "http://%s/cblr/svc/op/ks/system/%s" % (blended["http_server"], system.name) - elif kickstart_path.startswith("/") or kickstart_path.find("/cobbler/kickstarts/") != -1: + elif kickstart_path.startswith("/"): kickstart_path = "http://%s/cblr/svc/op/ks/profile/%s" % (blended["http_server"], profile.name) if distro.breed is None or distro.breed == "redhat": @@ -644,14 +840,14 @@ class PXEGen: # interface=bootif causes a failure # append_line = append_line.replace("ksdevice","interface") - if arch in ["s390x", "ppc", "ppc64"]: + if arch.startswith("ppc") or arch.startswith("s390"): # remove the prefix "append" append_line = append_line[7:] # store variables for templating metadata["menu_label"] = "" if profile: - if not arch in [ "ia64", "ppc", "ppc64", "s390x" ]: + if not arch in [ "ia64", "ppc", "ppc64", "s390", "s390x" ]: metadata["menu_label"] = "MENU LABEL %s" % profile.name metadata["profile_name"] = profile.name elif image: @@ -706,10 +902,15 @@ class PXEGen: return results blended = utils.blender(self.api, False, obj) + ksmeta = blended.get("ks_meta",{}) del blended["ks_meta"] blended.update(ksmeta) # make available at top level + templates = blended.get("template_files",{}) + del blended["template_files"] + blended.update(templates) # make available at top level + (success, templates) = utils.input_string_or_hash(templates) if not success: @@ -718,7 +919,9 @@ class PXEGen: for template in templates.keys(): dest = templates[template] - + if dest is None: + continue + # Run the source and destination files through # templar first to allow for variables in the path template = self.templar.render(template, blended, None).strip() diff --git a/cobbler/remote.py b/cobbler/remote.py index 8154dc58..10e9cc20 100644 --- a/cobbler/remote.py +++ b/cobbler/remote.py @@ -27,10 +27,13 @@ import sys import socket import time import os +import base64 import SimpleXMLRPCServer import xmlrpclib import random +import stat import base64 +import fcntl import string import traceback import glob @@ -45,7 +48,7 @@ import item_system import item_repo import item_image from utils import * -from utils import _ # * does not import _ +from utils import _ # FIXME: make configurable? TOKEN_TIMEOUT = 60*60 # 60 minutes @@ -53,25 +56,86 @@ OBJECT_TIMEOUT = 60*60 # 60 minutes TOKEN_CACHE = {} OBJECT_CACHE = {} +class DataCache: + + def __init__(self, api): + """ + Constructor + """ + self.api = api + + def update(self,collection_type, name): + data = self.api.deserialize_item_raw(collection_type, name) + + if data is None: + return False + + if collection_type == "distro": + obj = item_distro.Distro(self.api._config) + obj.from_datastruct(data) + self.api.add_distro(obj, False, False) + + if collection_type == "profile": + subprofile = False + if data.has_key("parent") and data["parent"] != "": + subprofile = True + obj = item_profile.Profile(self.api._config, is_subobject = subprofile) + obj.from_datastruct(data) + self.api.add_profile(obj, False, False) + + if collection_type == "system": + obj = item_system.System(self.api._config) + obj.from_datastruct(data) + self.api.add_system(obj, False, False, False) + + if collection_type == "repo": + obj = item_repo.Repo(self.api._config) + obj.from_datastruct(data) + self.api.add_repo(obj, False, False) + + if collection_type == "image": + obj = item_image.Image(self.api._config) + obj.from_datastruct(data) + self.api.add_image(obj, False, False) + + + def remove(self,collection_type, name): + # for security reasons, only remove if actually gone + data = self.api.deserialize_item_raw(collection_type, name) + if data is None: + if collection_type == "distro": + self.api.remove_distro(name, delete=False, recursive=True, with_triggers=False) + if collection_type == "profile": + self.api.remove_profile(name, delete=False, recursive=True, with_triggers=False) + if collection_type == "system": + self.api.remove_system(name, delete=False, recursive=True, with_triggers=False) + if collection_type == "repo": + self.api.remove_repo(name, delete=False, recursive=True, with_triggers=False) + if collection_type == "image": + self.api.remove_image(name, delete=False, recursive=True, with_triggers=False) + # ********************************************************************* # ********************************************************************* class CobblerXMLRPCInterface: """ - This is the interface used for all public XMLRPC methods, for instance, - as used by koan. The read-write interface which inherits from this adds - more methods, though that interface can be disabled. + This is the interface used for all XMLRPC methods, for instance, + as used by koan or CobblerWeb note: public methods take an optional parameter token that is just - here for consistancy with the ReadWrite API. The tokens for the read only - interface are intentionally /not/ validated. It's a public API. + here for consistancy with the ReadWrite API. Read write operations do + require the token. """ - def __init__(self,api,logger,enable_auth_if_relevant): + def __init__(self,api,enable_auth_if_relevant): self.api = api - self.logger = logger self.auth_enabled = enable_auth_if_relevant + self.cache = DataCache(self.api) + self.logger = self.api.logger + self.token_cache = TOKEN_CACHE + self.object_cache = OBJECT_CACHE self.timestamp = self.api.last_modified_time() + random.seed(time.time()) def __sorter(self,a,b): return cmp(a["name"],b["name"]) @@ -85,10 +149,15 @@ class CobblerXMLRPCInterface: return self.api.last_modified_time() def update(self, token=None): - now = self.api.last_modified_time() - if (now > self.timestamp): - self.timestamp = now - self.api.update() + # no longer neccessary + return True + + def internal_cache_update(self, collection_type, data): + self.cache.update(collection_type, data) + return True + + def internal_cache_remove(self, collection_type, data): + self.cache.remove(collection_type, data) return True def ping(self): @@ -163,11 +232,26 @@ class CobblerXMLRPCInterface: # FIXME: a global lock or module around data access loading # would be useful for non-db backed storage - data = self.api.deserialize_raw(collection_name) - total_items = len(data) - if collection_name == "settings": - return self._fix_none(data) + data = self.api.deserialize_raw("settings") + return self.xmlrpc_hacks(data) + else: + contents = [] + if collection_name.startswith("distro"): + contents = self.api.distros() + elif collection_name.startswith("profile"): + contents = self.api.profiles() + elif collection_name.startswith("system"): + contents = self.api.systems() + elif collection_name.startswith("repo"): + contents = self.api.repos() + elif collection_name.startswith("image"): + contents = self.api.images() + else: + raise CX("internal error, collection name is %s" % collection_name) + # FIXME: speed this up + data = contents.to_datastruct() + total_items = len(data) data.sort(self.__sorter) @@ -184,9 +268,9 @@ class CobblerXMLRPCInterface: start_point = total_items - 1 # correct ??? if end_point > total_items: end_point = total_items - data = self._fix_none(data[start_point:end_point]) + data = self.xmlrpc_hacks(data[start_point:end_point]) - return self._fix_none(data) + return self.xmlrpc_hacks(data) def get_kickstart_templates(self,token=None,**rest): """ @@ -208,10 +292,6 @@ class CobblerXMLRPCInterface: def generate_kickstart(self,profile=None,system=None,REMOTE_ADDR=None,REMOTE_MAC=None,**rest): self._log("generate_kickstart") - - if profile and not system: - regrc = self.register_mac(REMOTE_MAC,profile) - return self.api.generate_kickstart(profile,system) def get_settings(self,token=None,**rest): @@ -219,7 +299,9 @@ class CobblerXMLRPCInterface: Return the contents of /etc/cobbler/settings, which is a hash. """ self._log("get_settings",token=token) - return self.__get_all("settings") + results = self.api.settings().to_datastruct() + self._log("my settings are: %s" % results) + return self.xmlrpc_hacks(results) def get_repo_config_for_profile(self,profile_name,**rest): """ @@ -259,43 +341,78 @@ class CobblerXMLRPCInterface: return "# object not found: %s" % system_name return self.api.get_template_file_for_system(obj,path) - def register_mac(self,mac,profile,token=None,**rest): + def register_new_system(self,info,token=None,**rest): """ If register_new_installs is enabled in settings, this allows - kickstarts to add new system records for per-profile-provisioned - systems automatically via a wget in %post. This has security - implications. - READ: https://fedorahosted.org/cobbler/wiki/AutoRegistration + /usr/bin/cobbler-register (part of the koan package) to add + new system records remotely if they don't already exist. + There is a cobbler_register snippet that helps with doing + this automatically for new installs but it can also be used + for existing installs. See "AutoRegistration" on the Wiki. """ - - if mac is None: - # don't go further if not being called by anaconda - return 1 - - if not self.api.settings().register_new_installs: - # must be enabled in settings - return 2 - - system = self.api.find_system(mac_address=mac) - if system is not None: - # do not allow overwrites - return 3 - - # the MAC probably looks like "eth0 AA:BB:CC:DD:EE:FF" now, fix it - if mac.find(" ") != -1: - mac = mac.split()[-1] - - dup = self.api.find_system(mac_address=mac) - if dup is not None: - return 4 - - self._log("register mac for profile %s" % profile,token=token,name=mac) + + enabled = self.api.settings().register_new_installs + if not str(enabled) in [ "1", "y", "yes", "true" ]: + raise CX("registration is disabled in cobbler settings") + + # validate input + name = info.get("name","") + profile = info.get("profile","") + hostname = info.get("hostname","") + interfaces = info.get("interfaces",{}) + ilen = len(interfaces.keys()) + + if name == "": + raise CX("no system name submitted") + if profile == "": + raise CX("profile not submitted") + if ilen == 0: + raise CX("no interfaces submitted") + if ilen >= 64: + raise CX("too many interfaces submitted") + + # validate things first + name = info.get("name","") + inames = interfaces.keys() + if self.api.find_system(name=name): + raise CX("system name conflicts") + if hostname != "" and self.api.find_system(hostname=hostname): + raise CX("hostname conflicts") + + for iname in inames: + mac = info["interfaces"][iname].get("mac_address","") + ip = info["interfaces"][iname].get("ip_address","") + if ip.find("/") != -1: + raise CX("no CIDR ips are allowed") + if mac == "": + raise CX("missing MAC address for interface %s" % iname) + if mac != "": + system = self.api.find_system(mac_address=mac) + if system is not None: + raise CX("mac conflict: %s" % mac) + if ip != "": + system = self.api.find_system(ip_address=ip) + if system is not None: + raise CX("ip conflict: %s"% ip) + + # looks like we can go ahead and create a system now obj = self.api.new_system() obj.set_profile(profile) - name = mac.replace(":","_") obj.set_name(name) - obj.set_mac_address(mac, "eth0") + if hostname != "": + obj.set_hostname(hostname) obj.set_netboot_enabled(False) + for iname in inames: + mac = info["interfaces"][iname].get("mac_address","") + ip = info["interfaces"][iname].get("ip_address","") + netmask = info["interfaces"][iname].get("netmask","") + obj.set_mac_address(mac, iname) + if hostname != "": + obj.set_dns_name(hostname, iname) + if ip != "": + obj.set_ip_address(ip, iname) + if netmask != "": + obj.set_subnet(netmask, iname) self.api.add_system(obj) return 0 @@ -320,6 +437,109 @@ class CobblerXMLRPCInterface: systems.add(obj,save=True,with_triggers=False,with_sync=False,quick_pxe_update=True) return True + def upload_log_data(self, sys_name, file, size, offset, data, token=None,**rest): + + """ + This is a logger function used by the "anamon" logging system to + upload all sorts of auxilliary data from Anaconda. + As it's a bit of a potential log-flooder, it's off by default + and needs to be enabled in /etc/cobbler/settings. + """ + + self._log("upload_log_data (file: '%s', size: %s, offset: %s)" % (file, size, offset), token=token, name=sys_name) + + # Check if enabled in self.api.settings() + if not self.api.settings().anamon_enabled: + # feature disabled! + return False + + # Find matching system record + systems = self.api.systems() + obj = systems.find(name=sys_name) + if obj == None: + # system not found! + self._log("upload_log_data - system '%s' not found" % sys_name, token=token, name=sys_name) + return False + + return self.__upload_file(sys_name, file, size, offset, data) + + def __upload_file(self, sys_name, file, size, offset, data): + ''' + system: the name of the system + name: the name of the file + size: size of contents (bytes) + data: base64 encoded file contents + offset: the offset of the chunk + files can be uploaded in chunks, if so the size describes + the chunk rather than the whole file. the offset indicates where + the chunk belongs + the special offset -1 is used to indicate the final chunk''' + contents = base64.decodestring(data) + del data + if offset != -1: + if size is not None: + if size != len(contents): + return False + + #XXX - have an incoming dir and move after upload complete + # SECURITY - ensure path remains under uploadpath + tt = string.maketrans("/","+") + fn = string.translate(file, tt) + if fn.startswith('..'): + raise CX(_("invalid filename used: %s") % fn) + + # FIXME ... get the base dir from cobbler settings() + udir = "/var/log/cobbler/anamon/%s" % sys_name + if not os.path.isdir(udir): + os.mkdir(udir, 0755) + + fn = "%s/%s" % (udir, fn) + try: + st = os.lstat(fn) + except OSError, e: + if e.errno == errno.ENOENT: + pass + else: + raise + else: + if not stat.S_ISREG(st.st_mode): + raise CX(_("destination not a file: %s") % fn) + + fd = os.open(fn, os.O_RDWR | os.O_CREAT, 0644) + # log_error("fd=%r" %fd) + try: + if offset == 0 or (offset == -1 and size == len(contents)): + #truncate file + fcntl.lockf(fd, fcntl.LOCK_EX|fcntl.LOCK_NB) + try: + os.ftruncate(fd, 0) + # log_error("truncating fd %r to 0" %fd) + finally: + fcntl.lockf(fd, fcntl.LOCK_UN) + if offset == -1: + os.lseek(fd,0,2) + else: + os.lseek(fd,offset,0) + #write contents + fcntl.lockf(fd, fcntl.LOCK_EX|fcntl.LOCK_NB, len(contents), 0, 2) + try: + os.write(fd, contents) + # log_error("wrote contents") + finally: + fcntl.lockf(fd, fcntl.LOCK_UN, len(contents), 0, 2) + if offset == -1: + if size is not None: + #truncate file + fcntl.lockf(fd, fcntl.LOCK_EX|fcntl.LOCK_NB) + try: + os.ftruncate(fd, size) + # log_error("truncating fd %r to size %r" % (fd,size)) + finally: + fcntl.lockf(fd, fcntl.LOCK_UN) + finally: + os.close(fd) + return True + def run_install_triggers(self,mode,objtype,name,ip,token=None,**rest): """ @@ -339,7 +559,7 @@ class CobblerXMLRPCInterface: # time if reinstalling all of a cluster all at once. # we can do that at "cobbler check" time. - utils.run_triggers(None, "/var/lib/cobbler/triggers/install/%s/*" % mode, additional=[objtype,name,ip]) + utils.run_triggers(self.api, None, "/var/lib/cobbler/triggers/install/%s/*" % mode, additional=[objtype,name,ip]) return True @@ -366,41 +586,81 @@ class CobblerXMLRPCInterface: self._log("get_distros",token=token) return self.__get_all("distro",page,results_per_page) + + def __find(self,find_function,criteria={},expand=False,token=None): + name = criteria.get("name",None) + if name is not None: + del criteria["name"] + if not expand: + data = [x.name for x in find_function(name, True, True, **criteria)] + else: + data = [x.to_datastruct() for x in find_function(name, True, True, **criteria)] + return self.xmlrpc_hacks(data) + + def find_distro(self,criteria={},expand=False,token=None,**rest): + self._log("find_distro", token=token) + # FIXME DEBUG + self._log(criteria) + data = self.__find(self.api.find_distro,criteria,expand=expand,token=token) + # FIXME DEBUG + self._log(data) + return data + + def find_profile(self,criteria={},expand=False,token=None,**rest): + self._log("find_profile", token=token) + data = self.__find(self.api.find_profile,criteria,expand=expand,token=token) + return data + + def find_system(self,criteria={},expand=False,token=None,**rest): + self._log("find_system", token=token) + data = self.__find(self.api.find_system,criteria,expand=expand,token=token) + return data + + def find_repo(self,criteria={},expand=False,token=None,**rest): + self._log("find_repo", token=token) + data = self.__find(self.api.find_repo,criteria,expand=expand,token=token) + return data + + def find_image(self,criteria={},expand=False,token=None,**rest): + self._log("find_image", token=token) + data = self.__find(self.api.find_image,criteria,expand=expand,token=token) + return data + def get_distros_since(self,mtime): """ Return all of the distro objects that have been modified after mtime. """ data = self.api.get_distros_since(mtime, collapse=True) - return self._fix_none(data) + return self.xmlrpc_hacks(data) def get_profiles_since(self,mtime): """ See documentation for get_distros_since """ data = self.api.get_profiles_since(mtime, collapse=True) - return self._fix_none(data) + return self.xmlrpc_hacks(data) def get_systems_since(self,mtime): """ See documentation for get_distros_since """ data = self.api.get_systems_since(mtime, collapse=True) - return self._fix_none(data) + return self.xmlrpc_hacks(data) def get_repos_since(self,mtime): """ See documentation for get_distros_since """ data = self.api.get_repos_since(mtime, collapse=True) - return self._fix_none(data) + return self.xmlrpc_hacks(data) def get_images_since(self,mtime): """ See documentation for get_distros_since """ data = self.api.get_images_since(mtime, collapse=True) - return self._fix_none(data) + return self.xmlrpc_hacks(data) def get_profiles(self,page=None,results_per_page=None,token=None,**rest): """ @@ -476,7 +736,7 @@ class CobblerXMLRPCInterface: return {} if flatten: result = utils.flatten(result) - return self._fix_none(result) + return self.xmlrpc_hacks(result) def get_distro(self,name,flatten=False,token=None,**rest): """ @@ -541,8 +801,8 @@ class CobblerXMLRPCInterface: self._log("get_distro_as_rendered",name=name,token=token) obj = self.api.find_distro(name=name) if obj is not None: - return self._fix_none(utils.blender(self.api, True, obj)) - return self._fix_none({}) + return self.xmlrpc_hacks(utils.blender(self.api, True, obj)) + return self.xmlrpc_hacks({}) def get_profile_as_rendered(self,name,token=None,**rest): """ @@ -559,8 +819,8 @@ class CobblerXMLRPCInterface: self._log("get_profile_as_rendered", name=name, token=token) obj = self.api.find_profile(name=name) if obj is not None: - return self._fix_none(utils.blender(self.api, True, obj)) - return self._fix_none({}) + return self.xmlrpc_hacks(utils.blender(self.api, True, obj)) + return self.xmlrpc_hacks({}) def get_system_as_rendered(self,name,token=None,**rest): """ @@ -577,8 +837,8 @@ class CobblerXMLRPCInterface: self._log("get_system_as_rendered",name=name,token=token) obj = self.api.find_system(name=name) if obj is not None: - return self._fix_none(utils.blender(self.api, True, obj)) - return self._fix_none({}) + return self.xmlrpc_hacks(utils.blender(self.api, True, obj)) + return self.xmlrpc_hacks({}) def get_repo_as_rendered(self,name,token=None,**rest): """ @@ -595,8 +855,8 @@ class CobblerXMLRPCInterface: self._log("get_repo_as_rendered",name=name,token=token) obj = self.api.find_repo(name=name) if obj is not None: - return self._fix_none(utils.blender(self.api, True, obj)) - return self._fix_none({}) + return self.xmlrpc_hacks(utils.blender(self.api, True, obj)) + return self.xmlrpc_hacks({}) def get_image_as_rendered(self,name,token=None,**rest): """ @@ -613,8 +873,8 @@ class CobblerXMLRPCInterface: self._log("get_image_as_rendered",name=name,token=token) obj = self.api.find_image(name=name) if obj is not None: - return self._fix_none(utils.blender(self.api, True, obj)) - return self._fix_none({}) + return self.xmlrpc_hacks(utils.blender(self.api, True, obj)) + return self.xmlrpc_hacks({}) def get_random_mac(self,token=None,**rest): """ @@ -625,21 +885,29 @@ class CobblerXMLRPCInterface: self._log("get_random_mac",token=None) return utils.get_random_mac(self.api) - def _fix_none(self,data): + def xmlrpc_hacks(self,data): """ - Convert None in XMLRPC to just '~'. The above - XMLRPC module hack should do this, but let's make extra sure. + Convert None in XMLRPC to just '~' to make extra sure a client + that can't allow_none can deal with this. ALSO: a weird hack ensuring + that when dicts with integer keys (or other types) are transmitted + with string keys. """ if data is None: data = '~' elif type(data) == list: - data = [ self._fix_none(x) for x in data ] + data = [ self.xmlrpc_hacks(x) for x in data ] elif type(data) == dict: + data2 = {} for key in data.keys(): - data[key] = self._fix_none(data[key]) + keydata = data[key] + data2[str(key)] = self.xmlrpc_hacks(data[key]) + return data2 + + else: + data = '~' return data @@ -649,50 +917,11 @@ class CobblerXMLRPCInterface: """ return self.api.status() -# ********************************************************************************* -# ********************************************************************************* - -class CobblerXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): - def __init__(self, args): - self.allow_reuse_address = True - SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self,args) - -# ********************************************************************************* -# ********************************************************************************* - + ###### + # READ WRITE METHODS BELOW REQUIRE A TOKEN, use login() + # TO OBTAIN ONE + ###### -class ProxiedXMLRPCInterface: - - def __init__(self,api,logger,proxy_class,enable_auth_if_relevant=True): - self.logger = logger - self.proxied = proxy_class(api,logger,enable_auth_if_relevant) - - def _dispatch(self, method, params, **rest): - - if not hasattr(self.proxied, method): - self.logger.error("remote:unknown method %s" % method) - raise CX(_("Unknown remote method")) - - method_handle = getattr(self.proxied, method) - - try: - return method_handle(*params) - except Exception, e: - utils.log_exc(self.logger) - raise e - -# ********************************************************************** - -class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): - - def __init__(self,api,logger,enable_auth_if_relevant): - self.api = api - self.auth_enabled = enable_auth_if_relevant - self.logger = logger - self.token_cache = TOKEN_CACHE - self.object_cache = OBJECT_CACHE - self.timestamp = self.api.last_modified_time() - random.seed(time.time()) def __next_id(self,retry=0): """ @@ -701,7 +930,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): """ if retry > 10: # I have no idea why this would happen but I want to be through :) - raise CX(_("internal error")) + raise CX(_("internal error, retry exceeded")) next_id = self.__get_random(25) if self.object_cache.has_key(next_id): return self.__next_id(retry=retry+1) @@ -907,6 +1136,7 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): do reposync this way. Would be nice to send output over AJAX/other later. """ + # FIXME: performance self._log("sync",token=token) self.check_access(token,"sync") return self.api.sync() @@ -1136,7 +1366,6 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): object. """ self._log("rename_distro",object_id=object_id,token=token) - self.api.deserialize() # FIXME: make this unneeded obj = self.__get_object(object_id) return self.api.rename_distro(obj,newname) @@ -1161,7 +1390,6 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): def rename_image(self,object_id,newname,token=None): self._log("rename_image",object_id=object_id,token=token) self.check_access(token,"rename_image") - self.api.deserialize() # FIXME: make this unneeded obj = self.__get_object(object_id) return self.api.rename_image(obj,newname) @@ -1335,18 +1563,41 @@ class CobblerReadWriteXMLRPCInterface(CobblerXMLRPCInterface): return rc -# ********************************************************************* -# ********************************************************************* -class CobblerReadWriteXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): - """ - This is just a wrapper used for launching the Read/Write XMLRPC Server. - """ + +# ********************************************************************************* +# ********************************************************************************* + +class CobblerXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer): def __init__(self, args): self.allow_reuse_address = True SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(self,args) +# ********************************************************************************* +# ********************************************************************************* + + +class ProxiedXMLRPCInterface: + + def __init__(self,api,proxy_class,enable_auth_if_relevant=True): + self.proxied = proxy_class(api,enable_auth_if_relevant) + self.logger = self.proxied.api.logger + + def _dispatch(self, method, params, **rest): + + if not hasattr(self.proxied, method): + self.logger.error("remote:unknown method %s" % method) + raise CX(_("Unknown remote method")) + + method_handle = getattr(self.proxied, method) + + try: + return method_handle(*params) + except Exception, e: + utils.log_exc(self.logger) + raise e + # ********************************************************************* # ********************************************************************* @@ -1360,7 +1611,9 @@ def _test_setup_modules(authn="authn_testing",authz="authz_allowall",pxe_once=1) MODULES_TEMPLATE = "installer_templates/modules.conf.template" DEFAULTS = "installer_templates/defaults" - data = yaml.loadFile(DEFAULTS).next() + fh = open(DEFAULTS) + data = yaml.load(fh.read()) + fh.close() data["authn_module"] = authn data["authz_module"] = authz data["pxe_once"] = pxe_once @@ -1379,7 +1632,9 @@ def _test_setup_settings(pxe_once=1): MODULES_TEMPLATE = "installer_templates/settings.template" DEFAULTS = "installer_templates/defaults" - data = yaml.loadFile(DEFAULTS).next() + fh = open(DEFAULTS) + data = yaml.load(fh.read()) + fh.close() data["pxe_once"] = pxe_once t = Template.Template(file=MODULES_TEMPLATE, searchList=[data]) @@ -1393,13 +1648,13 @@ def _test_bootstrap_restart(): assert rc1 == 0 rc2 = subprocess.call(["/sbin/service","httpd","restart"],shell=False,close_fds=True) assert rc2 == 0 - time.sleep(2) + time.sleep(5) _test_remove_objects() def _test_remove_objects(): - api = cobbler_api.BootAPI() + api = cobbler_api.BootAPI() # local handle # from ro tests d0 = api.find_distro("distro0") @@ -1444,8 +1699,7 @@ def test_xmlrpc_ro(): # now populate with something more useful # using the non-remote API - api = cobbler_api.BootAPI() - api.deserialize() # FIXME: redundant + api = cobbler_api.BootAPI() # local handle before_distros = len(api.distros()) before_profiles = len(api.profiles()) @@ -1453,10 +1707,14 @@ def test_xmlrpc_ro(): before_repos = len(api.repos()) before_images = len(api.images()) + fake = open("/tmp/cobbler.fake","w+") + fake.write("") + fake.close() + distro = api.new_distro() distro.set_name("distro0") - distro.set_kernel("/etc/hosts") - distro.set_initrd("/etc/hosts") + distro.set_kernel("/tmp/cobbler.fake") + distro.set_initrd("/tmp/cobbler.fake") api.add_distro(distro) repo = api.new_repo() @@ -1488,7 +1746,7 @@ def test_xmlrpc_ro(): image = api.new_image() image.set_name("image0") - image.set_file("/etc/hosts") + image.set_file("/tmp/cobbler.fake") api.add_image(image) # reposync is required in order to create the repo config files @@ -1676,8 +1934,8 @@ def test_xmlrpc_rw(): _test_setup_modules(authn="authn_testing",authz="authz_allowall") _test_bootstrap_restart() - server = xmlrpclib.Server("http://127.0.0.1/cobbler_api_rw") # remote - api = cobbler_api.BootAPI() # local + server = xmlrpclib.Server("http://127.0.0.1/cobbler_api") # remote + api = cobbler_api.BootAPI() # local instance, /DO/ ping cobblerd # note if authn_testing is not engaged this will not work # test getting token, will raise remote exception on fail @@ -1687,17 +1945,18 @@ def test_xmlrpc_rw(): # create distro did = server.new_distro(token) server.modify_distro(did, "name", "distro1", token) - server.modify_distro(did, "kernel", "/etc/hosts", token) - server.modify_distro(did, "initrd", "/etc/hosts", token) + server.modify_distro(did, "kernel", "/tmp/cobbler.fake", token) + server.modify_distro(did, "initrd", "/tmp/cobbler.fake", token) server.modify_distro(did, "kopts", { "dog" : "fido", "cat" : "fluffy" }, token) # hash or string server.modify_distro(did, "ksmeta", "good=sg1 evil=gould", token) # hash or string server.modify_distro(did, "breed", "redhat", token) server.modify_distro(did, "os-version", "rhel5", token) server.modify_distro(did, "owners", "sam dave", token) # array or string server.modify_distro(did, "mgmt-classes", "blip", token) # list or string - server.modify_distro(did, "template-files", "/etc/hosts=/tmp/a /etc/fstab=/tmp/b",token) # hash or string + server.modify_distro(did, "template-files", "/tmp/cobbler.fake=/tmp/a /etc/fstab=/tmp/b",token) # hash or string server.modify_distro(did, "comment", "...", token) server.modify_distro(did, "redhat_management_key", "ALPHA", token) + server.modify_distro(did, "redhat_management_server", "rhn.example.com", token) server.save_distro(did, token) # use the non-XMLRPC API to check that it's added seeing we tested XMLRPC RW APIs above @@ -1723,7 +1982,9 @@ def test_xmlrpc_rw(): server.modify_profile(pid, "mgmt-classes", "one two three", token) server.modify_profile(pid, "comment", "...", token) server.modify_profile(pid, "name_servers", ["one","two"], token) + server.modify_profile(pid, "name_servers_search", ["one","two"], token) server.modify_profile(pid, "redhat_management_key", "BETA", token) + server.modify_distro(did, "redhat_management_server", "sat.example.com", token) server.save_profile(pid, token) api.deserialize() @@ -1741,6 +2002,7 @@ def test_xmlrpc_rw(): server.modify_system(sid, 'virt-path', "/opt/images", token) server.modify_system(sid, 'virt-type', 'qemu', token) server.modify_system(sid, 'name_servers', 'one two three four', token) + server.modify_system(sid, 'name_servers_search', 'one two three four', token) server.modify_system(sid, 'modify-interface', { "macaddress-eth0" : "AA:BB:CC:EE:EE:EE", "ipaddress-eth0" : "192.168.10.50", @@ -1764,6 +2026,7 @@ def test_xmlrpc_rw(): server.modify_system(sid, "power_pass", "magic", token) server.modify_system(sid, "power_id", "7", token) server.modify_system(sid, "redhat_management_key", "GAMMA", token) + server.modify_distro(did, "redhat_management_server", "spacewalk.example.com", token) server.save_system(sid,token) diff --git a/cobbler/services.py b/cobbler/services.py index 98a7a8dd..9b9f10e5 100644 --- a/cobbler/services.py +++ b/cobbler/services.py @@ -29,7 +29,7 @@ import string import sys import time import urlgrabber -import yaml # cobbler packaged version +import yaml # PyYAML # the following imports are largely for the test code import urlgrabber @@ -68,7 +68,6 @@ class CobblerSvc(object): """ if self.remote is None: self.remote = xmlrpclib.Server(self.server, allow_none=True) - self.remote.update() def index(self,**args): return "no mode specified" @@ -241,12 +240,15 @@ def __test_setup(): # module later. api = cobbler_api.BootAPI() - api.deserialize() # FIXME: redundant + + fake = open("/tmp/cobbler.fake","w+") + fake.write("") + fake.close() distro = api.new_distro() distro.set_name("distro0") - distro.set_kernel("/etc/hosts") - distro.set_initrd("/etc/hosts") + distro.set_kernel("/tmp/cobbler.fake") + distro.set_initrd("/tmp/cobbler.fake") api.add_distro(distro) repo = api.new_repo() @@ -291,7 +293,7 @@ def __test_setup(): image = api.new_image() image.set_name("image0") - image.set_file("/etc/hosts") + image.set_file("/tmp/cobbler.fake") api.add_image(image) # perhaps an artifact of the test process? @@ -374,12 +376,9 @@ def test_services_access(): url = "http://127.0.0.1/cblr/svc/op/nopxe/system/system0" data = urlgrabber.urlread(url) - print "NOPXE DATA: %s" % data - time.sleep(10) + time.sleep(2) - api.deserialize() # ensure we have the latest data in the API handle sys = api.find_system("system0") - print "NE STATUS: %s" % sys.netboot_enabled assert str(sys.netboot_enabled).lower() not in [ "1", "true", "yes" ] # now let's test the listing URLs since we document @@ -415,13 +414,12 @@ def test_services_access(): url = "http://127.0.0.1/cblr/svc/op/puppet/hostname/hostname0" data = urlgrabber.urlread(url) - print "puppet DATA: %s" % data assert data.find("alpha") != -1 assert data.find("beta") != -1 assert data.find("gamma") != -1 assert data.find("3") != -1 - data = yaml.load(data).next() + data = yaml.load(data) assert data.has_key("classes") assert data.has_key("parameters") @@ -431,13 +429,11 @@ def test_services_access(): url = "http://127.0.0.1/cblr/svc/op/template/profile/profile0/path/_tmp_t1-rendered" data = urlgrabber.urlread(url) - print "T1: %s" % data assert data.find("profile0") != -1 assert data.find("$profile_name") == -1 url = "http://127.0.0.1/cblr/svc/op/template/system/system0/path/_tmp_t2-rendered" data = urlgrabber.urlread(url) - print "T2: %s" % data assert data.find("system0") != -1 assert data.find("$system_name") == -1 diff --git a/cobbler/settings.py b/cobbler/settings.py index c415150b..2f2fddb3 100644 --- a/cobbler/settings.py +++ b/cobbler/settings.py @@ -30,14 +30,21 @@ TESTMODE = False # we need. DEFAULTS = { + "anamon_enabled" : 0, "allow_duplicate_hostnames" : 0, "allow_duplicate_macs" : 0, "allow_duplicate_ips" : 0, "bind_bin" : "/usr/sbin/named", + "build_reporting_enabled" : 0, + "build_reporting_to_address" : "", + "build_reporting_sender" : "", + "build_reporting_subject" : "", + "build_reporting_smtp_server" : "localhost", "cheetah_import_whitelist" : [ "re", "random", "time" ], "cobbler_master" : '', "default_kickstart" : "/var/lib/cobbler/kickstarts/default.ks", "default_name_servers" : '', + "default_name_servers_search" : '', "default_password_crypted" : "\$1\$mF86/UHC\$WvcIcX2t6crBz2onWxyac.", "default_virt_bridge" : "xenbr0", "default_virt_type" : "auto", @@ -68,6 +75,7 @@ DEFAULTS = { "text" : None, "ksdevice" : "eth0" }, + "kernel_options_s390x" : {}, "manage_dhcp" : 0, "manage_dns" : 0, "manage_xinetd" : 0, @@ -84,6 +92,7 @@ DEFAULTS = { "power_template_dir" : "/etc/cobbler/power", "pxe_just_once" : 0, "pxe_template_dir" : "/etc/cobbler/pxe", + "redhat_management_permissive" : 0, "redhat_management_type" : "off", "redhat_management_key" : "", "redhat_management_server" : "xmlrpc.rhn.redhat.com", @@ -94,15 +103,12 @@ DEFAULTS = { "run_install_triggers" : 1, "server" : "127.0.0.1", "snippetsdir" : "/var/lib/cobbler/snippets", - "syslog_port" : 25150, "tftpd_bin" : "/usr/sbin/in.tftpd", "tftpd_conf" : "/etc/xinetd.d/tftp", "tftpd_rules" : "/etc/tftpd.rules", "vsftpd_bin" : "/usr/sbin/vsftpd", "webdir" : "/var/www/cobbler", "xmlrpc_port" : 25151, - "xmlrpc_rw_enabled" : 1, - "xmlrpc_rw_port" : 25152, "yum_post_install_mirror" : 1, "yumdownloader_flags" : "--resolve", "yumreposync_flags" : "-l" @@ -145,8 +151,9 @@ class Settings(serializable.Serializable): if datastruct is None: print _("warning: not loading empty structure for %s") % self.filename() return - - self._attributes = datastruct + + self._attributes = DEFAULTS + self._attributes.update(datastruct) return self diff --git a/cobbler/templar.py b/cobbler/templar.py index 8a261084..f2e4b7f1 100644 --- a/cobbler/templar.py +++ b/cobbler/templar.py @@ -32,13 +32,14 @@ import utils class Templar: - def __init__(self,config): + def __init__(self,config=None): """ Constructor """ - self.config = config - self.api = config.api - self.settings = config.settings() + if config is not None: + self.config = config + self.api = config.api + self.settings = config.settings() def check_for_invalid_imports(self,data): """ @@ -130,11 +131,12 @@ class Templar: search_table["http_server"] = repstr for x in search_table.keys(): - data_out = data_out.replace("@@%s@@" % str(x), str(search_table[str(x)])) + if type(x) == str: + data_out = data_out.replace("@@%s@@" % str(x), str(search_table[str(x)])) # remove leading newlines which apparently breaks AutoYAST ? if data_out.startswith("\n"): - data_out = data_out.strip() + data_out = data_out.lstrip() if out_path is not None: utils.mkdir(os.path.dirname(out_path)) diff --git a/cobbler/test_basic.py b/cobbler/test_basic.py index 2a839c5a..12bdc26a 100644 --- a/cobbler/test_basic.py +++ b/cobbler/test_basic.py @@ -109,7 +109,7 @@ class BootTest(unittest.TestCase): image = self.api.new_image() self.assertTrue(image.set_name("testimage0")) - self.assertTrue(image.set_file("/etc/hosts")) # meaningless path + self.assertTrue(image.set_file(self.fk_initrd)) # meaningless path self.assertTrue(self.api.add_image(image)) @@ -134,13 +134,9 @@ class RenameTest(BootTest): def test_renames(self): self.__tester(self.api.find_distro, self.api.rename_distro, "testdistro0", "testdistro1") - self.api.update() # unneccessary? self.__tester(self.api.find_profile, self.api.rename_profile, "testprofile0", "testprofile1") - self.api.update() # unneccessary? self.__tester(self.api.find_system, self.api.rename_system, "testsystem0", "testsystem1") - self.api.update() # unneccessary? self.__tester(self.api.find_repo, self.api.rename_repo, "testrepo0", "testrepo1") - self.api.update() # unneccessary? self.__tester(self.api.find_image, self.api.rename_image, "testimage0", "testimage1") @@ -763,20 +759,17 @@ class SyncContents(BootTest): def test_blender_cache_works(self): - # this is just a file that exists that we don't have to create - fake_file = "/etc/hosts" - distro = self.api.new_distro() self.assertTrue(distro.set_name("D1")) - self.assertTrue(distro.set_kernel(fake_file)) - self.assertTrue(distro.set_initrd(fake_file)) + self.assertTrue(distro.set_kernel(self.fk_kernel)) + self.assertTrue(distro.set_initrd(self.fk_initrd)) self.assertTrue(self.api.add_distro(distro)) self.assertTrue(self.api.find_distro(name="D1")) profile = self.api.new_profile() self.assertTrue(profile.set_name("P1")) self.assertTrue(profile.set_distro("D1")) - self.assertTrue(profile.set_kickstart(fake_file)) + self.assertTrue(profile.set_kickstart("/var/lib/cobbler/kickstarts/sample.ks")) self.assertTrue(self.api.add_profile(profile)) assert self.api.find_profile(name="P1") != None @@ -818,17 +811,17 @@ class SyncContents(BootTest): class Deletions(BootTest): - def test_invalid_delete_profile_doesnt_exist(self): - self.failUnlessRaises(CobblerException, self.api.profiles().remove, "doesnotexist") + #def test_invalid_delete_profile_doesnt_exist(self): + # self.failUnlessRaises(CobblerException, self.api.profiles().remove, "doesnotexist") def test_invalid_delete_profile_would_orphan_systems(self): self.failUnlessRaises(CobblerException, self.api.profiles().remove, "testprofile0") - def test_invalid_delete_system_doesnt_exist(self): - self.failUnlessRaises(CobblerException, self.api.systems().remove, "doesnotexist") + #def test_invalid_delete_system_doesnt_exist(self): + # self.failUnlessRaises(CobblerException, self.api.systems().remove, "doesnotexist") - def test_invalid_delete_distro_doesnt_exist(self): - self.failUnlessRaises(CobblerException, self.api.distros().remove, "doesnotexist") + #def test_invalid_delete_distro_doesnt_exist(self): + # self.failUnlessRaises(CobblerException, self.api.distros().remove, "doesnotexist") def test_invalid_delete_distro_would_orphan_profile(self): self.failUnlessRaises(CobblerException, self.api.distros().remove, "testdistro0") @@ -870,6 +863,22 @@ class TestListings(BootTest): self.assertTrue(len(self.api.profiles().printable()) > 0) self.assertTrue(len(self.api.distros().printable()) > 0) +class TestImage(BootTest): + + def test_image_file(self): + # ensure that only valid names are accepted and invalid ones are rejected + image = self.api.new_image() + self.assertTrue(image.set_file("nfs://hostname/path/to/filename.iso")) + self.assertTrue(image.set_file("nfs://mcpierce@hostname:/path/to/filename.iso")) + self.assertTrue(image.set_file("nfs://hostname:/path/to/filename.iso")) + self.assertTrue(image.set_file("nfs://hostname/filename.iso")) + self.assertTrue(image.set_file("hostname:/path/to/the/filename.iso")) + self.failUnlessRaises(CX, image.set_file, "hostname:filename.iso") + self.failUnlessRaises(CX, image.set_file, "path/to/filename.iso") + self.failUnlessRaises(CX, image.set_file, "hostname:") + # port is not allowed + self.failUnlessRaises(CX, image.set_file, "nfs://hostname:1234/path/to/the/filename.iso") + #class TestCLIBasic(BootTest): # # def test_cli(self): diff --git a/cobbler/utils.py b/cobbler/utils.py index 23794a83..18535638 100644 --- a/cobbler/utils.py +++ b/cobbler/utils.py @@ -37,6 +37,20 @@ import tempfile import signal from cexceptions import * import codes +import time +import netaddr +import shlex + +try: + import hashlib as fiver + def md5(key): + return fiver.md5(key) +except ImportError: + # for Python < 2.5 + import md5 as fiver + def md5(key): + return fiver.md5(key) + CHEETAH_ERROR_DISCLAIMER=""" # *** ERROR *** @@ -62,12 +76,12 @@ def _(foo): MODULE_CACHE = {} -# import api # factor out - _re_kernel = re.compile(r'vmlinuz(.*)') _re_initrd = re.compile(r'initrd(.*).img') -def setup_logger(name, log_level=logging.INFO, log_file="/var/log/cobbler/cobbler.log"): +def setup_logger(name, is_cobblerd=False, log_level=logging.INFO, log_file="/var/log/cobbler/cobbler.log"): + if is_cobblerd: + log_file = "/var/log/cobbler/cobblerd.log" logger = logging.getLogger(name) logger.setLevel(log_level) try: @@ -90,18 +104,6 @@ def log_exc(logger): logger.info("Exception Info:\n%s" % string.join(traceback.format_list(traceback.extract_tb(tb)))) -def print_exc(exc,full=False): - (t, v, tb) = sys.exc_info() - try: - getattr(exc, "from_cobbler") - print >> sys.stderr, str(exc)[1:-1] - except: - print >> sys.stderr, t - print >> sys.stderr, v - if full: - print >> sys.stderr, string.join(traceback.format_list(traceback.extract_tb(tb))) - return 1 - def get_exc(exc,full=True): (t, v, tb) = sys.exc_info() buf = "" @@ -133,39 +135,33 @@ def trace_me(): bar = string.join(traceback.format_list(x)) return bar +def pretty_hex(ip, length=8): + """ + Pads an IP object with leading zeroes so that the result is + _length_ hex digits. Also do an upper(). + """ + hexval = "%x" % ip.value + if len(hexval) < length: + hexval = '0' * (length - len(hexval)) + hexval + return hexval.upper() def get_host_ip(ip, shorten=True): """ Return the IP encoding needed for the TFTP boot tree. """ + ip = netaddr.IP(ip) + cidr = ip.cidr() - slash = None - if ip.find("/") != -1: - # CIDR notation - (ip, slash) = ip.split("/") - - handle = sub_process.Popen("/usr/bin/gethostip %s" % ip, shell=True, stdout=sub_process.PIPE, close_fds=True) - out = handle.stdout - results = out.read() - converted = results.split(" ")[-1][0:8] - - if slash is None: - return converted + if len(cidr) == 1: # Just an IP, e.g. a /32 + return pretty_hex(ip) else: - slash = int(slash) - num = int(converted, 16) - delta = 32 - slash - mask = (0xFFFFFFFF << delta) - num = num & mask - num = "%0x" % num - if len(num) != 8: - num = '0' * (8 - len(num)) + num - num = num.upper() - if shorten: - nibbles = delta / 4 - for x in range(0,nibbles): - num = num[0:-1] - return num + pretty = pretty_hex(cidr[0]) + if not shorten or len(cidr) <= 8: + # not enough to make the last nibble insignificant + return pretty + else: + cutoff = (32 - cidr.prefixlen) / 4 + return pretty[0:-cutoff] def get_config_filename(sys,interface): """ @@ -393,28 +389,32 @@ def input_string_or_hash(options,delim=",",allow_multiples=True): raise CX(_("No idea what to do with list: %s") % options) elif type(options) == str: new_dict = {} - tokens = options.split(delim) + tokens = shlex.split(options) for t in tokens: - tokens2 = t.split("=") - if len(tokens2) == 1 and tokens2[0] != '': + tokens2 = t.split("=",1) + if len(tokens2) == 1: # this is a singleton option, no value - tokens2.append(None) - elif tokens2[0] == '': - return (False, {}) + key = tokens2[0] + value = None + else: + key = tokens2[0] + value = tokens2[1] # if we're allowing multiple values for the same key, # check to see if this token has already been # inserted into the dictionary of values already - if tokens2[0] in new_dict.keys() and allow_multiples: + + if key in new_dict.keys() and allow_multiples: # if so, check to see if there is already a list of values # otherwise convert the dictionary value to an array, and add # the new value to the end of the list - if type(new_dict[tokens2[0]]) == list: - new_dict[tokens2[0]].append(tokens2[1]) + if type(new_dict[key]) == list: + new_dict[key].append(value) else: - new_dict[tokens2[0]] = [new_dict[tokens2[0]], tokens2[1]] + new_dict[key] = [new_dict[key], value] else: - new_dict[tokens2[0]] = tokens2[1] + new_dict[key] = value + # make sure we have no empty entries new_dict.pop('', None) return (True, new_dict) elif type(options) == dict: @@ -457,12 +457,13 @@ def blender(api_handle,remove_hashes, root_obj): for node in tree: __consolidate(node,results) - # add in syslog to results (magic) - if settings.syslog_port != 0: - if not results.has_key("kernel_options"): - results["kernel_options"] = {} - syslog = "%s:%s" % (results["server"], settings.syslog_port) - results["kernel_options"]["syslog"] = syslog + # hack -- s390 nodes get additional default kernel options + arch = results.get("arch","?") + if arch.startswith("s390"): + keyz = settings.kernel_options_s390x.keys() + for k in keyz: + if not results.has_key(k): + results["kernel_options"][k] = settings.kernel_options_s390x[k] # determine if we have room to add kssendmac to the kernel options line kernel_txt = hash_to_string(results["kernel_options"]) @@ -633,7 +634,7 @@ def hash_removals(results,subkey): return scan = results[subkey].keys() for k in scan: - if k.startswith("!") and k != "!": + if str(k).startswith("!") and k != "!": remove_me = k[1:] if results[subkey].has_key(remove_me): del results[subkey][remove_me] @@ -662,15 +663,33 @@ def hash_to_string(hash): buffer = buffer + str(key) + "=" + str(value) + " " return buffer -def run_triggers(ref,globber,additional=[]): +def run_triggers(api,ref,globber,additional=[]): """ Runs all the trigger scripts in a given directory. ref can be a cobbler object, if not None, the name will be passed to the script. If ref is None, the script will be called with no argumenets. Globber is a wildcard expression indicating which triggers to run. Example: "/var/lib/cobbler/triggers/blah/*" + + As of Cobbler 1.5.X, this also runs cobbler modules that match the globbing paths. """ + # Python triggers first, before shell + + modules = api.get_modules_in_category(globber) + for m in modules: + arglist = [] + if ref: + arglist.append(ref.name) + for x in additional: + arglist.append(x) + rc = m.run(api, arglist) + if rc != 0: + raise CX("cobbler trigger failed: %s" % m.__name__) + + # now do the old shell triggers, which are usually going to be slower, but are easier to write + # and support any language + triggers = glob.glob(globber) triggers.sort() for file in triggers: @@ -730,7 +749,7 @@ def os_release(): if not os.path.exists("/bin/rpm"): return ("unknown", 0) - args = ["/bin/rpm", "-q", "--whatprovides", "redhat-release"] + args = ["/bin/rpm", "-q", "--whatprovides", "redhat-release", "--queryformat", "%"+"{name}"+"-%" + "{version}" + "-%" + "{release}"] cmd = sub_process.Popen(args,shell=False,stdout=sub_process.PIPE,close_fds=True) data = cmd.communicate()[0] data = data.rstrip().lower() @@ -821,7 +840,7 @@ def is_safe_to_hardlink(src,dst,api): # we're dealing with SELinux and files that are not safe to chcon return False -def linkfile(src, dst, symlink_ok=False, api=None): +def linkfile(src, dst, symlink_ok=False, api=None, verbose=False): """ Attempt to create a link dst that points to src. Because file systems suck we attempt several different methods or bail to @@ -842,25 +861,30 @@ def linkfile(src, dst, symlink_ok=False, api=None): if not is_safe_to_hardlink(src,dst,api): # may have to remove old hardlinks for SELinux reasons # as previous implementations were not complete - os.remove(dst) + if verbose: + print "- removing: %s" % dst + os.remove(dst) else: - restorecon(dst,api=api) + # restorecon(dst,api=api,verbose=verbose) return True elif os.path.islink(dst): # existing path exists and is a symlink, update the symlink + if verbose: + print "- removing: %s" % dst os.remove(dst) if is_safe_to_hardlink(src,dst,api): # we can try a hardlink if the destination isn't to NFS or Samba # this will help save space and sync time. try: + if verbose: + print "- trying hardlink %s -> %s" % (src,dst) rc = os.link(src, dst) - restorecon(dst,api=api) + # restorecon(dst,api=api,verbose=verbose) return rc except (IOError, OSError): # hardlink across devices, or link already exists - # can result in extra call to restorecon but no - # major harm, we'll just symlink it if we can + # we'll just symlink it if we can # or otherwise copy it pass @@ -868,20 +892,24 @@ def linkfile(src, dst, symlink_ok=False, api=None): # we can symlink anywhere except for /tftpboot because # that is run chroot, so if we can symlink now, try it. try: + if verbose: + print "- trying symlink %s -> %s" % (src,dst) rc = os.symlink(src, dst) - restorecon(dst,api=api) + # restorecon(dst,api=api,verbose=verbose) return rc except (IOError, OSError): pass # we couldn't hardlink and we couldn't symlink so we must copy - return copyfile(src, dst, api=api) + return copyfile(src, dst, api=api, verbose=verbose) -def copyfile(src,dst,api=None): +def copyfile(src,dst,api=None,verbose=False): try: + if verbose: + print "- copying: %s -> %s" % (src,dst) rc = shutil.copyfile(src,dst) - restorecon(dst,api) + # restorecon(dst,api,verbose=verbose) return rc except: if not os.access(src,os.R_OK): @@ -982,58 +1010,44 @@ def umount(src): # raise CX(_("Error bind-mounting %(src)s to %(dst)s") % { "src" : src, "dst" : dst}) -def copyfile_pattern(pattern,dst,require_match=True,symlink_ok=False,api=None): +def copyfile_pattern(pattern,dst,require_match=True,symlink_ok=False,api=None, verbose=False): files = glob.glob(pattern) if require_match and not len(files) > 0: raise CX(_("Could not find files matching %s") % pattern) for file in files: base = os.path.basename(file) dst1 = os.path.join(dst,os.path.basename(file)) - linkfile(file,dst1,symlink_ok=symlink_ok,api=api) - restorecon(dst1,api=api) - -def restorecon(dest, api): + linkfile(file,dst1,symlink_ok=symlink_ok,api=api,verbose=verbose) + # restorecon(dst1,api=api,verbose=verbose) - """ - Wrapper around functions to manage SELinux contexts. - Use chcon public_content_t where we can to allow - hardlinking between /var/www and tftpboot but use - restorecon everywhere else. - """ - - if not api.is_selinux_enabled(): - return True - - tdest = os.path.realpath(dest) - - matched_path = False - if dest.startswith("/var/www"): - matched_path = True - elif dest.find("/tftpboot/"): - matched_path = True - remoted = is_remote_file(tdest) - - - if matched_path and not is_remote_file(tdest): - # ensure the file is flagged as public_content_t - # because it's something we've likely hardlinked - # three ways between tftpboot, /var/www and the source - cmd = ["/usr/bin/chcon","-t","public_content_t", tdest] - rc = sub_process.call(cmd,shell=False,close_fds=True) - if rc != 0: - raise CX("chcon operation failed: %s" % cmd) - - if (not matched_path) or (matched_path and remoted): - # the basic restorecon stuff... - cmd = [ "/sbin/restorecon",dest ] - rc = sub_process.call(cmd,shell=False,close_fds=True) - if rc != 0: - raise CX("restorecon operation failed: %s" % cmd) - - return 0 +#def restorecon(dest, api, verbose=False): +# +# """ +# Wrapper around functions to manage SELinux contexts. +# Use chcon public_content_t where we can to allow +# hardlinking between /var/www and tftpboot but use +# restorecon everywhere else. +# """ +# +# if not api.is_selinux_enabled(): +# return True +# +# tdest = os.path.realpath(dest) +# # remoted = is_remote_file(tdest) +# +# cmd = [ "/sbin/restorecon",dest ] +# if verbose: +# print "- %s" % " ".join(cmd) +# rc = sub_process.call(cmd,shell=False,close_fds=True) +# if rc != 0: +# raise CX("restorecon operation failed: %s" % cmd) +# +# return 0 -def rmfile(path): +def rmfile(path,verbose=False): try: + if verbose: + print "- removing: %s" % path os.unlink(path) return True except OSError, ioe: @@ -1042,16 +1056,18 @@ def rmfile(path): raise CX(_("Error deleting %s") % path) return True -def rmtree_contents(path): +def rmtree_contents(path,verbose=False): what_to_delete = glob.glob("%s/*" % path) for x in what_to_delete: - rmtree(x) + rmtree(x,verbose=verbose) -def rmtree(path): +def rmtree(path,verbose=False): try: if os.path.isfile(path): - return rmfile(path) + return rmfile(path,verbose=verbose) else: + if verbose: + print "- removing: %s" % path return shutil.rmtree(path,ignore_errors=True) except OSError, ioe: traceback.print_exc() @@ -1059,8 +1075,10 @@ def rmtree(path): raise CX(_("Error deleting %s") % path) return True -def mkdir(path,mode=0777): +def mkdir(path,mode=0777,verbose=False): try: + if verbose: + "- mkdir: %s" % path return os.makedirs(path,mode) except OSError, oe: if not oe.errno == 17: # already exists (no constant for 17?) @@ -1072,16 +1090,24 @@ def set_redhat_management_key(self,key): self.redhat_management_key = key return True -def set_arch(self,arch): - if arch is None or arch == "": - arch = "x86" - if arch in [ "standard", "ia64", "x86", "i386", "ppc", "ppc64", "x86_64", "s390x" ]: - if arch == "x86" or arch == "standard": - # be consistent - arch = "i386" +def set_redhat_management_server(self,server): + self.redhat_management_server = server + return True + +def set_arch(self,arch,repo=False): + if arch is None or arch == "" or arch == "standard" or arch == "x86": + arch = "i386" + + if repo: + valids = [ "i386", "x86_64", "ia64", "ppc", "ppc64", "s390", "s390x", "noarch", "src" ] + else: + valids = [ "i386", "x86_64", "ia64", "ppc", "ppc64", "s390", "s390x" ] + + if arch in valids: self.arch = arch return True - raise CX(_("arch choices include: x86, x86_64, ppc, ppc64, s390x and ia64")) + + raise CX("arch choices include: %s" % ", ".join(valids)) def set_os_version(self,os_version): if os_version == "" or os_version is None: @@ -1238,8 +1264,8 @@ def set_virt_bridge(self,vbridge): """ The default bridge for all virtual interfaces under this profile. """ - if vbridge is None: - vbridge = "" + if vbridge is None or vbridge == "": + vbridge = self.settings.default_virt_bridge self.virt_bridge = vbridge return True @@ -1304,6 +1330,8 @@ def safe_filter(var): raise CX("Invalid characters found in input") def is_selinux_enabled(): + if not os.path.exists("/usr/sbin/selinuxenabled"): + return False args = "/usr/sbin/selinuxenabled" selinuxenabled = sub_process.call(args,close_fds=True) if selinuxenabled == 0: @@ -1411,7 +1439,7 @@ def popen2(args, **kwargs): Leftovers from borrowing some bits from Snake, replace this function with just the subprocess call. """ - p = sub_process.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, **kwargs) + p = sub_process.Popen(args, stdout=sub_process.PIPE, stdin=sub_process.PIPE, **kwargs) return (p.stdout, p.stdin) if __name__ == "__main__": diff --git a/cobbler/webui/CobblerWeb.py b/cobbler/webui/CobblerWeb.py index ccdfe9c0..6175ef50 100644 --- a/cobbler/webui/CobblerWeb.py +++ b/cobbler/webui/CobblerWeb.py @@ -128,7 +128,7 @@ class CobblerWeb(object): }) def menu(self,**args): - return self.__render( 'blank.tmpl', { } ) + return self.__render( 'blank.tmpl', {} ) # ------------------------------------------------------------------------ # # Settings @@ -151,6 +151,85 @@ class CobblerWeb(object): # Distributions # ------------------------------------------------------------------------ # + def distro_menu(self,**spam): + return self.__render('blank.tmpl',{ 'more_blank' : 1}) + + def __search_execute(self,what,key1=None,value1=None,key2=None,value2=None,key3=None,value3=None): + if not self.__xmlrpc_setup(): + return self.xmlrpc_auth_failure() + + criteria={} + if key1 is not None and key1 != "": + criteria[key1] = value1.replace('"','') + if key2 is not None and key2 != "": + criteria[key2] = value2.replace('"','') + if key3 is not None and key3 != "": + criteria[key3] = value3.replace('"','') + + params = {} + params['page'] = -1 + + results = [] + if what == "distro": + results = params['distros'] = self.remote.find_distro(criteria,True) + elif what == "profile": + results = params['profiles'] = self.remote.find_profile(criteria,True) + elif what == "system": + results = params['systems'] = self.remote.find_system(criteria,True) + elif what == "image": + results = params['images'] = self.remote.find_image(criteria,True) + elif what == "repo": + results = params['repos'] = self.remote.find_repo(criteria,True) + else: + raise "internal error, unknown search type" + + + if len(results) > 0: + return self.__render( "%s_list.tmpl" % what, params) + else: + return self.__render('empty.tmpl', { 'search' : 1 }) + + def distro_search_execute(self,key1=None,value1=None,key2=None,value2=None,key3=None,value3=None,**rest): + return self.__search_execute("distro",key1,value1,key2,value2,key3,value3) + def profile_search_execute(self,key1=None,value1=None,key2=None,value2=None,key3=None,value3=None,**rest): + return self.__search_execute("profile",key1,value1,key2,value2,key3,value3) + def system_search_execute(self,key1=None,value1=None,key2=None,value2=None,key3=None,value3=None,**rest): + return self.__search_execute("system",key1,value1,key2,value2,key3,value3) + def image_search_execute(self,key1=None,value1=None,key2=None,value2=None,key3=None,value3=None,**rest): + return self.__search_execute("image",key1,value1,key2,value2,key3,value3) + def repo_search_execute(self,key1=None,value1=None,key2=None,value2=None,key3=None,value3=None,**rest): + return self.__search_execute("repo",key1,value1,key2,value2,key3,value3) + + def __search(self, what): + caption = "" + dest = "" + if what == "distro": + caption = "Search distros" + dest = "distro_search_execute" + elif what == "profile": + caption = "Search profiles" + dest = "profile_search_execute" + elif what == "system": + caption = "Search systems" + dest = "system_search_execute" + elif what == "repo": + caption = "Search repos" + dest = "repo_search_execute" + elif what == "image": + caption = "Search image" + dest = "image_search_execute" + else: + raise "internal error, unknown object type in search" + + return self.__render('search.tmpl', { + 'what' : what, + 'caption' : caption, + 'submit_dest' : dest + }) + + def distro_search(self,**spam): + return self.__search('distro') + def distro_list(self,page=None,limit=None,**spam): if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() @@ -197,7 +276,7 @@ class CobblerWeb(object): def distro_save(self,name=None,comment=None,oldname=None,new_or_edit=None,editmode='edit',kernel=None, initrd=None,kopts=None,koptspost=None,ksmeta=None,owners=None,arch=None,breed=None,redhatmanagementkey=None, - osversion=None,delete1=False,delete2=False,recursive=False,**args): + mgmt_classes=None,osversion=None,delete1=False,delete2=False,recursive=False,**args): if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() @@ -258,6 +337,8 @@ class CobblerWeb(object): self.remote.modify_distro(distro, 'os-version', osversion, self.token) self.remote.modify_distro(distro, 'comment', comment, self.token) self.remote.modify_distro(distro, 'redhat_management_key', redhatmanagementkey, self.token) + self.remote.modify_distro(distro, 'redhat_management_server', redhatmanagementserver, self.token) + self.remote.modify_distro(distro, 'mgmt_classes', mgmt_classes, self.token) # now time to save, do we want to run duplication checks? self.remote.save_distro(distro, self.token, editmode) @@ -302,6 +383,9 @@ class CobblerWeb(object): pages = total_size / results_per_page return (page, results_per_page, pages) + + def system_menu(self,**spam): + return self.__render('blank.tmpl',{ 'more_blank' : 1}) def system_list(self,page=None,limit=None,**spam): @@ -323,50 +407,225 @@ class CobblerWeb(object): else: return self.__render('empty.tmpl',{}) - def system_save(self,name=None,oldname=None,comment=None,editmode="edit",profile=None, - new_or_edit=None, + + def system_list_action(self,actionname=None,targetlist=None,**args): + if not self.__xmlrpc_setup(): + return self.xmlrpc_auth_failure() + + if actionname is None: + return self.error_page("Actionname parameter is REQUIRED.") + + if actionname == 'add': + return self.system_edit_new() + + if targetlist is None: + return self.error_page("Targetlist parameter is REQUIRED.") + + # Single item actions + if actionname == 'copy': + return self.system_edit_copy(targetlist) + if actionname == 'edit': + return self.system_edit(targetlist) + + # Multiple items actions + systems=[] + for targetname in targetlist.split(): + systems.append(self.remote.get_system(targetname,self.token)) + + return self.__render( 'system_'+actionname+'.tmpl', { + 'systems' : systems, + 'profiles' : self.remote.get_profiles(), + 'targetlist' : targetlist, + } ) + + + def system_netboot(self,targetlist=None,netboot=None,**args): + if not self.__xmlrpc_setup(): + return self.xmlrpc_auth_failure() + if targetlist is None: + return self.error_page("Targetlist parameter is REQUIRED.") + if netboot is None: + return self.error_page("Netboot parameter is REQUIRED.") + try: + systems=[] + for targetname in targetlist.split(): + systems.append(self.remote.get_system_handle(targetname,self.token)) + for system in systems: + self.remote.modify_system(system, 'netboot-enabled', netboot, self.token) + self.remote.save_system(system, self.token) + return self.system_list() + except Exception, e: + log_exc(self.apache) + return self.error_page("Error while saving system: %s" % str(e)) + + + def system_profile(self,targetlist=None,profile=None,**args): + if not self.__xmlrpc_setup(): + return self.xmlrpc_auth_failure() + if targetlist is None: + return self.error_page("Targetlist parameter is REQUIRED.") + if profile is None: + return self.error_page("Profile parameter is REQUIRED.") + try: + systems=[] + for targetname in targetlist.split(): + systems.append(self.remote.get_system_handle(targetname,self.token)) + for system in systems: + self.remote.modify_system(system, 'profile', profile, self.token) + self.remote.save_system(system, self.token) + return self.system_list() + except Exception, e: + log_exc(self.apache) + return self.error_page("Error while saving system: %s" % str(e)) + + + def system_power(self,targetlist=None,power=None,**args): + if not self.__xmlrpc_setup(): + return self.xmlrpc_auth_failure() + if targetlist is None: + return self.error_page("Targetlist parameter is REQUIRED.") + if power is None: + return self.error_page("Power parameter is REQUIRED.") + try: + systems=[] + for targetname in targetlist.split(): + systems.append(self.remote.get_system_handle(targetname,self.token)) + for system in systems: + self.remote.power_system(system, power, self.token) + return self.system_list() + except Exception, e: + log_exc(self.apache) + return self.error_page("Error while controlling power of system: %s" % str(e)) + + + def system_rename(self,targetlist=None,name=None,**args): + if not self.__xmlrpc_setup(): + return self.xmlrpc_auth_failure() + if targetlist is None: + return self.error_page("Targetlist parameter is REQUIRED.") + try: + systems=[] + for targetname in targetlist.split(): + systems.append(self.remote.get_system_handle(targetname,self.token)) + for system in systems: + self.remote.rename_system(system, name, self.token) + return self.system_list() + except Exception, e: + log_exc(self.apache) + return self.error_page("Error while renaming system: %s" % str(e)) + + + def system_delete(self,targetlist=None,**args): + if not self.__xmlrpc_setup(): + return self.xmlrpc_auth_failure() + if targetlist is None: + return self.error_page("Targetlist parameter is REQUIRED.") + try: + for targetname in targetlist.split(): + self.remote.remove_system(targetname, self.token) + return self.system_list() + except Exception, e: + log_exc(self.apache) + return self.error_page("Error while deleting system: %s" % str(e)) + + + + def system_edit(self, name=None,**spam): + if not self.__xmlrpc_setup(): + return self.xmlrpc_auth_failure() + + if name is None: + return self.error_page("Name parameter is REQUIRED") + input_system = self.remote.get_system(name,True) + can_edit = self.remote.check_access_no_fail(self.token,"modify_system",name) + + return self.__render( 'system_edit.tmpl', { + 'user' : self.username, + 'editmode' : 'edit', + 'editable' : can_edit, + 'system': input_system, + 'profiles': self.remote.get_profiles() + } ) + + + def system_edit_new(self,**spam): + if not self.__xmlrpc_setup(): + return self.xmlrpc_auth_failure() + + can_edit = self.remote.check_access_no_fail(self.token,"new_system",None) + if not can_edit: + return self.__render('message.tmpl', { + 'message1' : "Access denied.", + 'message2' : "You do not have permission to create new objects." + }) + + return self.__render( 'system_edit.tmpl', { + 'user' : self.username, + 'editmode' : 'new', + 'editable' : True, + 'system': None, + 'profiles': self.remote.get_profiles() + } ) + + + def system_edit_copy(self, name=None,**spam): + if not self.__xmlrpc_setup(): + return self.xmlrpc_auth_failure() + + if name is None: + return self.error_page("Name parameter is REQUIRED") + + can_edit = self.remote.check_access_no_fail(self.token,"new_system",None) + if not can_edit: + return self.__render('message.tmpl', { + 'message1' : "Access denied.", + 'message2' : "You do not have permission to create new objects." + }) + + input_system = self.remote.get_system(name,True) + + return self.__render( 'system_edit.tmpl', { + 'user' : self.username, + 'editmode' : 'copy', + 'editable' : True, + 'system': input_system, + 'profiles': self.remote.get_profiles() + } ) + + + def system_save(self,name=None,comment=None,editmode="edit",profile=None, kopts=None, koptspost=None, ksmeta=None, owners=None, server_override=None, netboot='n', virtpath=None,virtram=None,virttype=None,virtcpus=None,virtfilesize=None, - name_servers=None, + name_servers=None,name_servers_search=None, power_type=None, power_user=None, power_pass=None, power_id=None, power_address=None, - gateway=None,hostname=None,redhatmanagementkey=None,delete1=None, delete2=None, **args): + gateway=None,hostname=None,redhatmanagementkey=None,mgmt_classes=None,delete1=None, delete2=None, **args): if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() # parameter checking - if name is None and oldname is not None: - name = oldname if name is None: return self.error_page("System name parameter is REQUIRED.") - if (editmode == 'rename' or editmode == 'copy') and name == oldname: - return self.error_page("The name has not been changed.") - - # handle deletes as a special case - if new_or_edit == 'edit' and delete1 and delete2: - try: - self.remote.remove_system(name,self.token) - except Exception, e: - return self.error_page("could not delete %s, %s" % (name,str(e))) - return self.system_list() # grab a reference to the object - if new_or_edit == "edit" and editmode in [ "edit", "rename" ] : + if editmode == "edit": try: - if editmode == "edit": - system = self.remote.get_system_handle( name, self.token ) - else: - system = self.remote.get_system_handle( oldname, self.token ) - + system = self.remote.get_system_handle( name, self.token ) except: return self.error_page("Failed to lookup system: %s" % name) else: + try: + system = self.remote.get_system_handle( name, self.token ) + except: + system = None + if system is not None: + return self.error_page("Failed to create new system: %s already exists." % name) system = self.remote.new_system( self.token ) # go! try: - if editmode != "rename" and name: + if editmode != "edit": self.remote.modify_system(system, 'name', name, self.token ) self.remote.modify_system(system, 'profile', profile, self.token) self.remote.modify_system(system, 'kopts', kopts, self.token) @@ -390,9 +649,12 @@ class CobblerWeb(object): self.remote.modify_system(system, 'power_id', power_id, self.token) self.remote.modify_system(system, 'power_address', power_address, self.token) self.remote.modify_system(system, 'name_servers', name_servers, self.token) + self.remote.modify_system(system, 'name_servers_search', name_servers_search, self.token) self.remote.modify_system(system, 'gateway', gateway, self.token) self.remote.modify_system(system, 'hostname', hostname, self.token) self.remote.modify_system(system, 'redhat_management_key', redhatmanagementkey, self.token) + self.remote.modify_system(system, 'redhat_management_server', redhatmanagementserver, self.token) + self.remote.modify_system(system, 'mgmt_classes', mgmt_classes, self.token) interfaces = args.get("interface_list","") interfaces = interfaces.split(",") @@ -439,46 +701,21 @@ class CobblerWeb(object): log_exc(self.apache) return self.error_page("Error while saving system: %s" % str(e)) - - - if editmode == "rename" and name != oldname: - try: - self.remote.rename_system(system, name, self.token) - except Exception, e: - return self.error_page("Rename unsuccessful") - return self.system_list() - - def system_edit(self, name=None,**spam): - - if not self.__xmlrpc_setup(): - return self.xmlrpc_auth_failure() - - input_system = None - if name is not None: - input_system = self.remote.get_system(name,True) - can_edit = self.remote.check_access_no_fail(self.token,"modify_system",name) - else: - can_edit = self.remote.check_access_no_fail(self.token,"new_system",None) - if not can_edit: - return self.__render('message.tmpl', { - 'message1' : "Access denied.", - 'message2' : "You do not have permission to create new objects." - }) - - - return self.__render( 'system_edit.tmpl', { - 'user' : self.username, - 'edit' : True, - 'editable' : can_edit, - 'system': input_system, - 'profiles': self.remote.get_profiles() - } ) + def system_search(self,**spam): + return self.__search('system') # ------------------------------------------------------------------------ # # Profiles # ------------------------------------------------------------------------ # + + def profile_search(self,**spam): + return self.__search('profile') + + def profile_menu(self,**spam): + return self.__render('blank.tmpl', { 'more_blank' : 1}) + def profile_list(self,page=None,limit=None,**spam): if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() @@ -537,7 +774,7 @@ class CobblerWeb(object): ksmeta=None,owners=None,enablemenu=None,virtfilesize=None,virtram=None,virttype=None, virtpath=None,repos=None,dhcptag=None,delete1=False,delete2=False, parent=None,virtcpus=None,virtbridge=None,subprofile=None,server_override=None, - name_servers=None,redhatmanagementkey=None,recursive=False,**args): + name_servers=None,name_servers_search=None,redhatmanagementkey=None,mgmt_classes=None,recursive=False,**args): if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() @@ -604,7 +841,10 @@ class CobblerWeb(object): self.remote.modify_profile(profile, 'server', server_override, self.token) self.remote.modify_profile(profile, 'comment', comment, self.token) self.remote.modify_profile(profile, 'name_servers', name_servers, self.token) + self.remote.modify_profile(profile, 'name_servers_search', name_servers_search, self.token) self.remote.modify_profile(profile, 'redhat_management_key', redhatmanagementkey, self.token) + self.remote.modify_profile(profile, 'redhat_management_server', redhatmanagementserver, self.token) + self.remote.modify_profile(profile, 'mgmt_classes', mgmt_classes, self.token) if repos is None: repos = [] @@ -634,6 +874,12 @@ class CobblerWeb(object): # Repos # ------------------------------------------------------------------------ # + def repo_search(self,**spam): + return self.__search('repo') + + def repo_menu(self,**spam): + return self.__render('blank.tmpl', { 'more_blank' : 1}) + def repo_list(self,page=None,limit=None,**spam): if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() @@ -748,6 +994,12 @@ class CobblerWeb(object): # Images # ------------------------------------------------------------------------ # + def image_search(self,**spam): + return self.__search('image') + + def image_menu(self,**spam): + return self.__render('blank.tmpl', { 'more_blank' : 1}) + def image_list(self,page=None,limit=None,**spam): if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() @@ -871,6 +1123,9 @@ class CobblerWeb(object): # Kickstart files # ------------------------------------------------------------------------ # + def ksfile_menu(self,**spam): + return self.__render('blank.tmpl', { 'more_blank' : 1}) + def ksfile_list(self,**spam): if not self.__xmlrpc_setup(): return self.xmlrpc_auth_failure() @@ -982,26 +1237,49 @@ class CobblerWeb(object): index.exposed = True menu.exposed = True + distro_menu.exposed = True distro_edit.exposed = True distro_list.exposed = True distro_save.exposed = True - + distro_search.exposed = True + distro_search_execute.exposed = True + + profile_menu.exposed = True subprofile_edit.exposed = True profile_edit.exposed = True profile_list.exposed = True + profile_search.exposed = True profile_save.exposed = True + profile_search_execute.exposed = True + system_menu.exposed = True system_edit.exposed = True + system_edit_new.exposed = True + system_edit_copy.exposed = True system_list.exposed = True + system_list_action.exposed = True + system_netboot.exposed = True + system_profile.exposed = True + system_power.exposed = True + system_rename.exposed = True + system_delete.exposed = True system_save.exposed = True + system_search.exposed = True + system_search_execute.exposed = True + repo_menu.exposed = True repo_edit.exposed = True repo_list.exposed = True repo_save.exposed = True + repo_search.exposed = True + repo_search_execute.exposed = True + image_menu.exposed = True image_edit.exposed = True image_list.exposed = True image_save.exposed = True + image_search.exposed = True + image_search_execute.exposed = True settings_view.exposed = True ksfile_edit.exposed = True diff --git a/cobbler/yaml/__init__.py b/cobbler/yaml/__init__.py deleted file mode 100644 index bd21b40e..00000000 --- a/cobbler/yaml/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-__version__ = "0.32"
-from load import loadFile, load, Parser, l
-from dump import dump, dumpToFile, Dumper, d
-from stream import YamlLoaderException, StringStream, FileStream
-from timestamp import timestamp
-import sys
-if sys.hexversion >= 0x02020000:
- from redump import loadOrdered
-
-try:
- from ypath import ypath
-except NameError:
- def ypath(expr,target='',cntx=''):
- raise NotImplementedError("ypath requires Python 2.2")
-
-if sys.hexversion < 0x02010000:
- raise 'YAML is not tested for pre-2.1 versions of Python'
diff --git a/cobbler/yaml/dump.py b/cobbler/yaml/dump.py deleted file mode 100644 index eb34955b..00000000 --- a/cobbler/yaml/dump.py +++ /dev/null @@ -1,305 +0,0 @@ -"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-
-import types
-import string
-from types import StringType, UnicodeType, IntType, FloatType
-from types import DictType, ListType, TupleType, InstanceType
-from klass import hasMethod, isDictionary
-import re
-
-"""
- The methods from this module that are exported to the top
- level yaml package should remain stable. If you call
- directly into other methods of this module, be aware that
- they may change or go away in future implementations.
- Contact the authors if there are methods in this file
- that you wish to remain stable.
-"""
-
-def dump(*data):
- return Dumper().dump(*data)
-
-def d(data): return dump(data)
-
-def dumpToFile(file, *data):
- return Dumper().dumpToFile(file, *data)
-
-class Dumper:
- def __init__(self):
- self.currIndent = "\n"
- self.indent = " "
- self.keysrt = None
- self.alphaSort = 1 # legacy -- on by default
-
- def setIndent(self, indent):
- self.indent = indent
- return self
-
- def setSort(self, sort_hint):
- self.keysrt = sortMethod(sort_hint)
- return self
-
- def dump(self, *data):
- self.result = []
- self.output = self.outputToString
- self.dumpDocuments(data)
- return string.join(self.result,"")
-
- def outputToString(self, data):
- self.result.append(data)
-
- def dumpToFile(self, file, *data):
- self.file = file
- self.output = self.outputToFile
- self.dumpDocuments(data)
-
- def outputToFile(self, data):
- self.file.write(data)
-
- def dumpDocuments(self, data):
- for obj in data:
- self.anchors = YamlAnchors(obj)
- self.output("---")
- self.dumpData(obj)
- self.output("\n")
-
- def indentDump(self, data):
- oldIndent = self.currIndent
- self.currIndent += self.indent
- self.dumpData(data)
- self.currIndent = oldIndent
-
- def dumpData(self, data):
- anchor = self.anchors.shouldAnchor(data)
- # Disabling anchors because they are lame for strings that the user might want to view/edit -- mdehaan
- #
- #if anchor:
- # self.output(" &%d" % anchor )
- #else:
- # anchor = self.anchors.isAlias(data)
- # if anchor:
- # self.output(" *%d" % anchor )
- # return
- if (data is None):
- self.output(' ~')
- elif hasMethod(data, 'to_yaml'):
- self.dumpTransformedObject(data)
- elif hasMethod(data, 'to_yaml_implicit'):
- self.output(" " + data.to_yaml_implicit())
- elif type(data) is InstanceType:
- self.dumpRawObject(data)
- elif isDictionary(data):
- self.dumpDict(data)
- elif type(data) in [ListType, TupleType]:
- self.dumpList(data)
- else:
- self.dumpScalar(data)
-
- def dumpTransformedObject(self, data):
- obj_yaml = data.to_yaml()
- if type(obj_yaml) is not TupleType:
- self.raiseToYamlSyntaxError()
- (data, typestring) = obj_yaml
- if typestring:
- self.output(" " + typestring)
- self.dumpData(data)
-
- def dumpRawObject(self, data):
- self.output(' !!%s.%s' % (data.__module__, data.__class__.__name__))
- self.dumpData(data.__dict__)
-
- def dumpDict(self, data):
- keys = data.keys()
- if len(keys) == 0:
- self.output(" {}")
- return
- if self.keysrt:
- keys = sort_keys(keys,self.keysrt)
- else:
- if self.alphaSort:
- keys.sort()
- for key in keys:
- self.output(self.currIndent)
- self.dumpKey(key)
- self.output(":")
- self.indentDump(data[key])
-
- def dumpKey(self, key):
- if type(key) is TupleType:
- self.output("?")
- self.indentDump(key)
- self.output("\n")
- else:
- self.output(quote(key))
-
- def dumpList(self, data):
- if len(data) == 0:
- self.output(" []")
- return
- for item in data:
- self.output(self.currIndent)
- self.output("-")
- self.indentDump(item)
-
- def dumpScalar(self, data):
- if isUnicode(data):
- self.output(' "%s"' % repr(data)[2:-1])
- elif isMulti(data):
- self.dumpMultiLineScalar(data.splitlines())
- else:
- self.output(" ")
- self.output(quote(data))
-
- def dumpMultiLineScalar(self, lines):
- self.output(" |")
- if lines[-1] == "":
- self.output("+")
- for line in lines:
- self.output(self.currIndent)
- self.output(line)
-
- def raiseToYamlSyntaxError(self):
- raise """
-to_yaml should return tuple w/object to dump
-and optional YAML type. Example:
-({'foo': 'bar'}, '!!foobar')
-"""
-
-#### ANCHOR-RELATED METHODS
-
-def accumulate(obj,occur):
- typ = type(obj)
- if obj is None or \
- typ is IntType or \
- typ is FloatType or \
- ((typ is StringType or typ is UnicodeType) \
- and len(obj) < 32): return
- obid = id(obj)
- if 0 == occur.get(obid,0):
- occur[obid] = 1
- if typ is ListType:
- for x in obj:
- accumulate(x,occur)
- if typ is DictType:
- for (x,y) in obj.items():
- accumulate(x,occur)
- accumulate(y,occur)
- else:
- occur[obid] = occur[obid] + 1
-
-class YamlAnchors:
- def __init__(self,data):
- occur = {}
- accumulate(data,occur)
- anchorVisits = {}
- for (obid, occur) in occur.items():
- if occur > 1:
- anchorVisits[obid] = 0
- self._anchorVisits = anchorVisits
- self._currentAliasIndex = 0
- def shouldAnchor(self,obj):
- ret = self._anchorVisits.get(id(obj),None)
- if 0 == ret:
- self._currentAliasIndex = self._currentAliasIndex + 1
- ret = self._currentAliasIndex
- self._anchorVisits[id(obj)] = ret
- return ret
- return 0
- def isAlias(self,obj):
- return self._anchorVisits.get(id(obj),0)
-
-### SORTING METHODS
-
-def sort_keys(keys,fn):
- tmp = []
- for key in keys:
- val = fn(key)
- if val is None: val = '~'
- tmp.append((val,key))
- tmp.sort()
- return [ y for (x,y) in tmp ]
-
-def sortMethod(sort_hint):
- typ = type(sort_hint)
- if DictType == typ:
- return sort_hint.get
- elif ListType == typ or TupleType == typ:
- indexes = {}; idx = 0
- for item in sort_hint:
- indexes[item] = idx
- idx += 1
- return indexes.get
- else:
- return sort_hint
-
-### STRING QUOTING AND SCALAR HANDLING
-def isStr(data):
- # XXX 2.1 madness
- if type(data) == type(''):
- return 1
- if type(data) == type(u''):
- return 1
- return 0
-
-def doubleUpQuotes(data):
- return data.replace("'", "''")
-
-def quote(data):
- if not isStr(data):
- return str(data)
- single = "'"
- double = '"'
- quote = ''
- if len(data) == 0:
- return "''"
- if hasSpecialChar(data) or data[0] == single:
- data = `data`[1:-1]
- data = string.replace(data, r"\x08", r"\b")
- quote = double
- elif needsSingleQuote(data):
- quote = single
- data = doubleUpQuotes(data)
- return "%s%s%s" % (quote, data, quote)
-
-def needsSingleQuote(data):
- if re.match(r"^-?\d", data):
- return 1
- if re.match(r"\*\S", data):
- return 1
- if data[0] in ['&', ' ']:
- return 1
- if data[0] == '"':
- return 1
- if data[-1] == ' ':
- return 1
- return (re.search(r'[:]', data) or re.search(r'(\d\.){2}', data))
-
-def hasSpecialChar(data):
- # need test to drive out '#' from this
- return re.search(r'[\t\b\r\f#]', data)
-
-def isMulti(data):
- if not isStr(data):
- return 0
- if hasSpecialChar(data):
- return 0
- return re.search("\n", data)
-
-def isUnicode(data):
- return type(data) == unicode
-
-def sloppyIsUnicode(data):
- # XXX - hack to make tests pass for 2.1
- return repr(data)[:2] == "u'" and repr(data) != data
-
-import sys
-if sys.hexversion < 0x20200000:
- isUnicode = sloppyIsUnicode
-
-
-
diff --git a/cobbler/yaml/implicit.py b/cobbler/yaml/implicit.py deleted file mode 100644 index 49d65e03..00000000 --- a/cobbler/yaml/implicit.py +++ /dev/null @@ -1,52 +0,0 @@ -"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-import re
-import string
-from timestamp import timestamp, matchTime
-
-DATETIME_REGEX = re.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}$")
-FLOAT_REGEX = re.compile("^[-+]?[0-9][0-9,]*\.[0-9]*$")
-SCIENTIFIC_REGEX = re.compile("^[-+]?[0-9]+(\.[0-9]*)?[eE][-+][0-9]+$")
-OCTAL_REGEX = re.compile("^[-+]?([0][0-7,]*)$")
-HEX_REGEX = re.compile("^[-+]?0x[0-9a-fA-F,]+$")
-INT_REGEX = re.compile("^[-+]?(0|[1-9][0-9,]*)$")
-
-def convertImplicit(val):
- if val == '~':
- return None
- if val == '+':
- return 1
- if val == '-':
- return 0
- if val[0] == "'" and val[-1] == "'":
- val = val[1:-1]
- return string.replace(val, "''", "\'")
- if val[0] == '"' and val[-1] == '"':
- if re.search(r"\u", val):
- val = "u" + val
- unescapedStr = eval (val)
- return unescapedStr
- if matchTime.match(val):
- return timestamp(val)
- if INT_REGEX.match(val):
- return int(cleanseNumber(val))
- if OCTAL_REGEX.match(val):
- return int(val, 8)
- if HEX_REGEX.match(val):
- return int(val, 16)
- if FLOAT_REGEX.match(val):
- return float(cleanseNumber(val))
- if SCIENTIFIC_REGEX.match(val):
- return float(cleanseNumber(val))
- return val
-
-def cleanseNumber(str):
- if str[0] == '+':
- str = str[1:]
- str = string.replace(str,',','')
- return str
-
diff --git a/cobbler/yaml/inline.py b/cobbler/yaml/inline.py deleted file mode 100644 index d4f6439a..00000000 --- a/cobbler/yaml/inline.py +++ /dev/null @@ -1,44 +0,0 @@ -"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-import re
-import string
-
-class InlineTokenizer:
- def __init__(self, data):
- self.data = data
-
- def punctuation(self):
- puncts = [ '[', ']', '{', '}' ]
- for punct in puncts:
- if self.data[0] == punct:
- self.data = self.data[1:]
- return punct
-
- def up_to_comma(self):
- match = re.match('(.*?)\s*, (.*)', self.data)
- if match:
- self.data = match.groups()[1]
- return match.groups()[0]
-
- def up_to_end_brace(self):
- match = re.match('(.*?)(\s*[\]}].*)', self.data)
- if match:
- self.data = match.groups()[1]
- return match.groups()[0]
-
- def next(self):
- self.data = string.strip(self.data)
- productions = [
- self.punctuation,
- self.up_to_comma,
- self.up_to_end_brace
- ]
- for production in productions:
- token = production()
- if token:
- return token
-
diff --git a/cobbler/yaml/klass.py b/cobbler/yaml/klass.py deleted file mode 100644 index c182fcf2..00000000 --- a/cobbler/yaml/klass.py +++ /dev/null @@ -1,54 +0,0 @@ -"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-import new
-import re
-
-class DefaultResolver:
- def resolveType(self, data, typestring):
- match = re.match('!!(.*?)\.(.*)', typestring)
- if not match:
- raise "Invalid private type specifier"
- (modname, classname) = match.groups()
- return makeClass(modname, classname, data)
-
-def makeClass(module, classname, dict):
- exec('import %s' % (module))
- klass = eval('%s.%s' % (module, classname))
- obj = new.instance(klass)
- if hasMethod(obj, 'from_yaml'):
- return obj.from_yaml(dict)
- obj.__dict__ = dict
- return obj
-
-def hasMethod(object, method_name):
- try:
- klass = object.__class__
- except:
- return 0
- if not hasattr(klass, method_name):
- return 0
- method = getattr(klass, method_name)
- if not callable(method):
- return 0
- return 1
-
-def isDictionary(data):
- return isinstance(data, dict)
-
-try:
- isDictionary({})
-except:
- def isDictionary(data): return type(data) == type({}) # XXX python 2.1
-
-if __name__ == '__main__':
- print isDictionary({'foo': 'bar'})
- try:
- print isDictionary(dict())
- from ordered_dict import OrderedDict
- print isDictionary(OrderedDict())
- except:
- pass
diff --git a/cobbler/yaml/load.py b/cobbler/yaml/load.py deleted file mode 100644 index 54931d67..00000000 --- a/cobbler/yaml/load.py +++ /dev/null @@ -1,333 +0,0 @@ -"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-import re, string
-from implicit import convertImplicit
-from inline import InlineTokenizer
-from klass import DefaultResolver
-from stream import YamlLoaderException, FileStream, StringStream, NestedDocs
-
-try:
- iter(list()) # is iter supported by this version of Python?
-except:
- # XXX - Python 2.1 does not support iterators
- class StopIteration: pass
- class iter:
- def __init__(self,parser):
- self._docs = []
- try:
- while 1:
- self._docs.append(parser.next())
- except StopIteration: pass
- self._idx = 0
- def __len__(self): return len(self._docs)
- def __getitem__(self,idx): return self._docs[idx]
- def next(self):
- if self._idx < len(self._docs):
- ret = self._docs[self._idx]
- self._idx = self._idx + 1
- return ret
- raise StopIteration
-
-def loadFile(filename, typeResolver=None):
- return loadStream(FileStream(filename),typeResolver)
-
-def load(str, typeResolver=None):
- return loadStream(StringStream(str), typeResolver)
-
-def l(str): return load(str).next()
-
-def loadStream(stream, typeResolver):
- return iter(Parser(stream, typeResolver))
-
-def tryProductions(productions, value):
- for production in productions:
- results = production(value)
- if results:
- (ok, result) = results
- if ok:
- return (1, result)
-
-def dumpDictionary(): return {}
-
-class Parser:
- def __init__(self, stream, typeResolver=None):
- try:
- self.dictionary = dict
- except:
- self.dictionary = dumpDictionary
- self.nestedDocs = NestedDocs(stream)
- self.aliases = {}
- if typeResolver:
- self.typeResolver = typeResolver
- else:
- self.typeResolver = DefaultResolver()
-
- def error(self, msg):
- self.nestedDocs.error(msg, self.line)
-
- def nestPop(self):
- line = self.nestedDocs.pop()
- if line is not None:
- self.line = line
- return 1
-
- def value(self, indicator):
- return getToken(indicator+"\s*(.*)", self.line)
-
- def getNextDocument(self): raise "getNextDocument() deprecated--use next()"
-
- def next(self):
- line = self.nestedDocs.popDocSep()
- indicator = getIndicator(line)
- if indicator:
- return self.parse_value(indicator)
- if line:
- self.nestedDocs.nestToNextLine()
- return self.parseLines()
- raise StopIteration
-
- def __iter__(self): return self
-
- def parseLines(self):
- peekLine = self.nestedDocs.peek()
- if peekLine:
- if re.match("\s*-", peekLine):
- return self.parse_collection([], self.parse_seq_line)
- else:
- return self.parse_collection(self.dictionary(), self.parse_map_line)
- raise StopIteration
-
- def parse_collection(self, items, lineParser):
- while self.nestPop():
- if self.line:
- lineParser(items)
- return items
-
- def parse_seq_line(self, items):
- value = self.value("-")
- if value is not None:
- items.append(self.parse_seq_value(value))
- else:
- self.error("missing '-' for seq")
-
- def parse_map_line(self, items):
- if (self.line == '?'):
- self.parse_map_line_nested(items)
- else:
- self.parse_map_line_simple(items, self.line)
-
- def parse_map_line_nested(self, items):
- self.nestedDocs.nestToNextLine()
- key = self.parseLines()
- if self.nestPop():
- value = self.value(':')
- if value is not None:
- items[tuple(key)] = self.parse_value(value)
- return
- self.error("key has no value for nested map")
-
- def parse_map_line_simple(self, items, line):
- map_item = self.key_value(line)
- if map_item:
- (key, value) = map_item
- key = convertImplicit(key)
- if items.has_key(key):
- self.error("Duplicate key "+key)
- items[key] = self.parse_value(value)
- else:
- self.error("bad key for map")
-
- def is_map(self, value):
- # XXX - need real tokenizer
- if len(value) == 0:
- return 0
- if value[0] == "'":
- return 0
- if re.search(':(\s|$)', value):
- return 1
-
- def parse_seq_value(self, value):
- if self.is_map(value):
- return self.parse_compressed_map(value)
- else:
- return self.parse_value(value)
-
- def parse_compressed_map(self, value):
- items = self.dictionary()
- line = self.line
- token = getToken("(\s*-\s*)", line)
- self.nestedDocs.nestBySpecificAmount(len(token))
- self.parse_map_line_simple(items, value)
- return self.parse_collection(items, self.parse_map_line)
-
- def parse_value(self, value):
- (alias, value) = self.testForRepeatOfAlias(value)
- if alias:
- return value
- (alias, value) = self.testForAlias(value)
- value = self.parse_unaliased_value(value)
- if alias:
- self.aliases[alias] = value
- return value
-
- def parse_unaliased_value(self, value):
- match = re.match(r"(!\S*)(.*)", value)
- if match:
- (url, value) = match.groups()
- value = self.parse_untyped_value(value)
- if url[:2] == '!!':
- return self.typeResolver.resolveType(value, url)
- else:
- # XXX - allows syntax, but ignores it
- return value
- return self.parse_untyped_value(value)
-
- def parseInlineArray(self, value):
- if re.match("\s*\[", value):
- return self.parseInline([], value, ']',
- self.parseInlineArrayItem)
-
- def parseInlineHash(self, value):
- if re.match("\s*{", value):
- return self.parseInline(self.dictionary(), value, '}',
- self.parseInlineHashItem)
-
- def parseInlineArrayItem(self, result, token):
- return result.append(convertImplicit(token))
-
- def parseInlineHashItem(self, result, token):
- (key, value) = self.key_value(token)
- result[key] = value
-
- def parseInline(self, result, value, end_marker, itemMethod):
- tokenizer = InlineTokenizer(value)
- tokenizer.next()
- while 1:
- token = tokenizer.next()
- if token == end_marker:
- break
- itemMethod(result, token)
- return (1, result)
-
- def parseSpecial(self, value):
- productions = [
- self.parseMultiLineScalar,
- self.parseInlineHash,
- self.parseInlineArray,
- ]
- return tryProductions(productions, value)
-
- def parse_untyped_value(self, value):
- parse = self.parseSpecial(value)
- if parse:
- (ok, data) = parse
- return data
- token = getToken("(\S.*)", value)
- if token:
- lines = [token] + \
- pruneTrailingEmpties(self.nestedDocs.popNestedLines())
- return convertImplicit(joinLines(lines))
- else:
- self.nestedDocs.nestToNextLine()
- return self.parseLines()
-
- def parseNative(self, value):
- return (1, convertImplicit(value))
-
- def parseMultiLineScalar(self, value):
- if value == '>':
- return (1, self.parseFolded())
- elif value == '|':
- return (1, joinLiteral(self.parseBlock()))
- elif value == '|+':
- return (1, joinLiteral(self.unprunedBlock()))
-
- def parseFolded(self):
- data = self.parseBlock()
- i = 0
- resultString = ''
- while i < len(data)-1:
- resultString = resultString + data[i]
- resultString = resultString + foldChar(data[i], data[i+1])
- i = i + 1
- return resultString + data[-1] + "\n"
-
- def unprunedBlock(self):
- self.nestedDocs.nestToNextLine()
- data = []
- while self.nestPop():
- data.append(self.line)
- return data
-
- def parseBlock(self):
- return pruneTrailingEmpties(self.unprunedBlock())
-
- def testForAlias(self, value):
- match = re.match("&(\S*)\s*(.*)", value)
- if match:
- return match.groups()
- return (None, value)
-
- def testForRepeatOfAlias(self, value):
- match = re.match("\*(\S+)", value)
- if match:
- alias = match.groups()[0]
- if self.aliases.has_key(alias):
- return (alias, self.aliases[alias])
- else:
- self.error("Unknown alias")
- return (None, value)
-
- def key_value(self, str):
- if str[-1] == ' ':
- self.error("Trailing spaces not allowed without quotes.")
- # XXX This allows mis-balanced " vs. ' stuff
- match = re.match("[\"'](.+)[\"']\s*:\s*(.*)", str)
- if match:
- (key, value) = match.groups()
- return (key, value)
- match = re.match("(.+?)\s*:\s*(.*)", str)
- if match:
- (key, value) = match.groups()
- if len(value) and value[0] == '#':
- value = ''
- return (key, value)
-
-def getToken(regex, value):
- match = re.search(regex, value)
- if match:
- return match.groups()[0]
-
-def pruneTrailingEmpties(data):
- while len(data) > 0 and data[-1] == '':
- data = data[:-1]
- return data
-
-def foldChar(line1, line2):
- if re.match("^\S", line1) and re.match("^\S", line2):
- return " "
- return "\n"
-
-def getIndicator(line):
- if line:
- header = r"(#YAML:\d+\.\d+\s*){0,1}"
- match = re.match("--- "+header+"(\S*.*)", line)
- if match:
- return match.groups()[-1]
-
-def joinLines(lines):
- result = ''
- for line in lines[:-1]:
- if line[-1] == '\\':
- result = result + line[:-1]
- else:
- result = result + line + " "
- return result + lines[-1]
-
-def joinLiteral(data):
- return string.join(data,"\n") + "\n"
-
diff --git a/cobbler/yaml/ordered_dict.py b/cobbler/yaml/ordered_dict.py deleted file mode 100644 index 5bc2e3e0..00000000 --- a/cobbler/yaml/ordered_dict.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -pyyaml legacy -Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved -(see open source license information in docs/ directory) -""" - - -# This is extremely crude implementation of an OrderedDict. -# If you know of a better implementation, please send it to -# the author Steve Howell. You can find my email via -# the YAML mailing list or wiki. - -class OrderedDict(dict): - def __init__(self): - self._keys = [] - - def __setitem__(self, key, val): - self._keys.append(key) - dict.__setitem__(self, key, val) - - def keys(self): - return self._keys - - def items(self): - return [(key, self[key]) for key in self._keys] - -if __name__ == '__main__': - data = OrderedDict() - data['z'] = 26 - data['m'] = 13 - data['a'] = 1 - for key in data.keys(): - print "The value for %s is %s" % (key, data[key]) - print data - - - - diff --git a/cobbler/yaml/redump.py b/cobbler/yaml/redump.py deleted file mode 100644 index eefd68ec..00000000 --- a/cobbler/yaml/redump.py +++ /dev/null @@ -1,22 +0,0 @@ -""" -pyyaml legacy -Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved -(see open source license information in docs/ directory) -""" - -from ordered_dict import OrderedDict -from load import Parser -from dump import Dumper -from stream import StringStream - -def loadOrdered(stream): - parser = Parser(StringStream(stream)) - parser.dictionary = OrderedDict - return iter(parser) - -def redump(stream): - docs = list(loadOrdered(stream)) - dumper = Dumper() - dumper.alphaSort = 0 - return dumper.dump(*docs) - diff --git a/cobbler/yaml/stream.py b/cobbler/yaml/stream.py deleted file mode 100644 index dcd65c34..00000000 --- a/cobbler/yaml/stream.py +++ /dev/null @@ -1,199 +0,0 @@ -"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-import re
-import string
-
-def indentLevel(line):
- n = 0
- while n < len(line) and line[n] == ' ':
- n = n + 1
- return n
-
-class LineNumberStream:
- def __init__(self, filename=None):
- self.curLine = 0
- self.filename = filename
-
- def get(self):
- line = self.getLine()
- self.curLine += 1 # used by subclass
- if line:
- line = noLineFeed(line)
- return line
-
- def lastLineRead(self):
- return self.curLine
-
-class FileStream(LineNumberStream):
- def __init__(self, filename):
- self.fp = open(filename)
- LineNumberStream.__init__(self, filename)
-
- def getLine(self):
- line = self.fp.readline()
- if line == '': line = None
- return line
-
-class StringStream(LineNumberStream):
- def __init__(self, text):
- self.lines = split(text)
- self.numLines = len(self.lines)
- LineNumberStream.__init__(self)
-
- def getLine(self):
- if self.curLine < self.numLines:
- return self.lines[self.curLine]
-
-def split(text):
- lines = string.split(text, '\n')
- if lines[-1] == '':
- lines.pop()
- return lines
-
-def eatNewLines(stream):
- while 1:
- line = stream.get()
- if line is None or len(string.strip(line)):
- return line
-
-COMMENT_LINE_REGEX = re.compile(R"\s*#")
-def isComment(line):
- return line is not None and COMMENT_LINE_REGEX.match(line)
-
-class CommentEater:
- def __init__(self, stream):
- self.stream = stream
- self.peeked = 1
- self.line = eatNewLines(stream)
- self.eatComments()
-
- def eatComments(self):
- while isComment(self.line):
- self.line = self.stream.get()
-
- def peek(self):
- if self.peeked:
- return self.line
- self.peeked = 1
- self.line = self.stream.get()
- self.eatComments()
- return self.line
-
- def lastLineRead(self):
- return self.stream.lastLineRead()
-
- def pop(self):
- data = self.peek()
- self.peeked = 0
- return data
-
-class NestedText:
- def __init__(self, stream):
- self.commentEater = CommentEater(stream)
- self.reset()
-
- def lastLineRead(self):
- return self.commentEater.lastLineRead()
-
- def reset(self):
- self.indentLevel = 0
- self.oldIndents = [0]
-
- def peek(self):
- nextLine = self.commentEater.peek()
- if nextLine is not None:
- if indentLevel(nextLine) >= self.indentLevel:
- return nextLine[self.indentLevel:]
- elif nextLine == '':
- return ''
-
- def pop(self):
- line = self.peek()
- if line is None:
- self.indentLevel = self.oldIndents.pop()
- return
- self.commentEater.pop()
- return line
-
- def popNestedLines(self):
- nextLine = self.peek()
- if nextLine is None or nextLine == '' or nextLine[0] != ' ':
- return []
- self.nestToNextLine()
- lines = []
- while 1:
- line = self.pop()
- if line is None:
- break
- lines.append(line)
- return lines
-
- def nestToNextLine(self):
- line = self.commentEater.peek()
- indentation = indentLevel(line)
- if len(self.oldIndents) > 1 and indentation <= self.indentLevel:
- self.error("Inadequate indentation", line)
- self.setNewIndent(indentation)
-
- def nestBySpecificAmount(self, adjust):
- self.setNewIndent(self.indentLevel + adjust)
-
- def setNewIndent(self, indentLevel):
- self.oldIndents.append(self.indentLevel)
- self.indentLevel = indentLevel
-
-class YamlLoaderException(Exception):
- def __init__(self, *args):
- (self.msg, self.lineNum, self.line, self.filename) = args
-
- def __str__(self):
- msg = """\
-%(msg)s:
-near line %(lineNum)d:
-%(line)s
-""" % self.__dict__
- if self.filename:
- msg += "file: " + self.filename
- return msg
-
-class NestedDocs(NestedText):
- def __init__(self, stream):
- self.filename = stream.filename
- NestedText.__init__(self,stream)
- line = NestedText.peek(self)
- self.sep = '---'
- if self.startsWithSep(line):
- self.eatenDocSep = NestedText.pop(self)
- else:
- self.eatenDocSep = self.sep
-
- def startsWithSep(self,line):
- if line and self.sep == line[:3]: return 1
- return 0
-
- def popDocSep(self):
- line = self.eatenDocSep
- self.eatenDocSep = None
- self.reset()
- return line
-
- def pop(self):
- if self.eatenDocSep is not None:
- raise "error"
- line = self.commentEater.peek()
- if line and self.startsWithSep(line):
- self.eatenDocSep = NestedText.pop(self)
- return None
- return NestedText.pop(self)
-
- def error(self, msg, line):
- raise YamlLoaderException(msg, self.lastLineRead(), line, self.filename)
-
-def noLineFeed(s):
- while s[-1:] in ('\n', '\r'):
- s = s[:-1]
- return s
diff --git a/cobbler/yaml/timestamp.py b/cobbler/yaml/timestamp.py deleted file mode 100644 index 5c522f6e..00000000 --- a/cobbler/yaml/timestamp.py +++ /dev/null @@ -1,152 +0,0 @@ -""" -pyyaml legacy -Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved -(see open source license information in docs/ directory) -""" - - -import time, re, string -from types import ListType, TupleType - -PRIVATE_NOTICE = """ - This module is considered to be private implementation - details and is subject to change. Please only use the - objects and methods exported to the top level yaml package. -""" - -# -# Time specific operations -# - -_splitTime = re.compile('\-|\s|T|t|:|\.|Z') -matchTime = re.compile(\ - '\d+-\d+-\d+([\s|T|t]\d+:\d+:\d+.\d+(Z|(\s?[\-|\+]\d+:\d+)))?') - -def _parseTime(val): - if not matchTime.match(val): raise ValueError(val) - tpl = _splitTime.split(val) - if not(tpl): raise ValueError(val) - siz = len(tpl) - sec = 0 - if 3 == siz: - tpl += [0,0,0,0,0,-1] - elif 7 == siz: - tpl.append(0) - tpl.append(-1) - elif 8 == siz: - if len(tpl.pop()) > 0: raise ValueError(val) - tpl.append(0) - tpl.append(-1) - elif 9 == siz or 10 == siz: - mn = int(tpl.pop()) - hr = int(tpl.pop()) - sec = (hr*60+mn)*60 - if val.find("+") > -1: sec = -sec - if 10 == siz: tpl.pop() - tpl.append(0) - tpl.append(-1) - else: - raise ValueError(val) - idx = 0 - while idx < 9: - tpl[idx] = int(tpl[idx]) - idx += 1 - if tpl[1] < 1 or tpl[1] > 12: raise ValueError(val) - if tpl[2] < 1 or tpl[2] > 31: raise ValueError(val) - if tpl[3] > 24: raise ValueError(val) - if tpl[4] > 61: raise ValueError(val) - if tpl[5] > 61: raise ValueError(val) - if tpl[0] > 2038: - #TODO: Truncation warning - tpl = (2038,1,18,0,0,0,0,0,-1) - tpl = tuple(tpl) - ret = time.mktime(tpl) - ret = time.localtime(ret+sec) - ret = ret[:8] + (0,) - return ret - - -class _timestamp: - def __init__(self,val=None): - if not val: - self.__tval = time.gmtime() - else: - typ = type(val) - if ListType == typ: - self.__tval = tuple(val) - elif TupleType == typ: - self.__tval = val - else: - self.__tval = _parseTime(val) - if 9 != len(self.__tval): raise ValueError - def __getitem__(self,idx): return self.__tval[idx] - def __len__(self): return 9 - def strftime(self,format): return time.strftime(format,self.__tval) - def mktime(self): return time.mktime(self.__tval) - def asctime(self): return time.asctime(self.__tval) - def isotime(self): - return "%04d-%02d-%02dT%02d:%02d:%02d.00Z" % self.__tval[:6] - def __repr__(self): return "yaml.timestamp('%s')" % self.isotime() - def __str__(self): return self.isotime() - def to_yaml_implicit(self): return self.isotime() - def __hash__(self): return hash(self.__tval[:6]) - def __cmp__(self,other): - try: - return cmp(self.__tval[:6],other.__tval[:6]) - except AttributeError: - return -1 - -try: # inherit from mx.DateTime functionality if available - from mx import DateTime - class timestamp(_timestamp): - def __init__(self,val=None): - _timestamp.__init__(self,val) - self.__mxdt = DateTime.mktime(self.__tval) - def __getattr__(self, name): - return getattr(self.__mxdt, name) -except: - class timestamp(_timestamp): pass - - - -def unquote(expr): - """ - summary: > - Simply returns the unquoted string, and the - length of the quoted string token at the - beginning of the expression. - """ - tok = expr[0] - if "'" == tok: - idx = 1 - odd = 0 - ret = "" - while idx < len(expr): - chr = expr[idx] - if "'" == chr: - if odd: ret += chr - odd = not odd - else: - if odd: - tok = expr[:idx] - break - ret += chr - idx += 1 - if "'" == tok: tok = expr - return (ret,len(tok)) - if '"' == tok: - idx = 1 - esc = 0 - while idx < len(expr): - chr = expr[idx] - if '"' == chr and not esc: - tok = expr[:idx] + '"' - break - if '\\' == chr and not esc: esc = 1 - else: esc = 0 - idx += 1 - if '"' == tok: - raise SyntaxError("unmatched quote: " + expr) - ret = eval(tok) #TODO: find better way to unquote - return (ret,len(tok)) - return (expr,len(expr)) diff --git a/cobbler/yaml/ypath.py b/cobbler/yaml/ypath.py deleted file mode 100644 index b183a231..00000000 --- a/cobbler/yaml/ypath.py +++ /dev/null @@ -1,469 +0,0 @@ -"""
-pyyaml legacy
-Copyright (c) 2001 Steve Howell and Friends; All Rights Reserved
-(see open source license information in docs/ directory)
-"""
-
-
-from types import ListType, StringType, IntType, DictType, InstanceType
-import re
-from urllib import quote
-from timestamp import unquote
-
-noTarget = object()
-
-def escape(node):
- """
- summary: >
- This function escapes a given key so that it
- may appear within a ypath. URI style escaping
- is used so that ypath expressions can be a
- valid URI expression.
- """
- typ = type(node)
- if typ is IntType: return str(node)
- if typ is StringType:
- return quote(node,'')
- raise ValueError("TODO: Support more than just string and integer keys.")
-
-class context:
- """
- summary: >
- A ypath visit context through a YAML rooted graph.
- This is implemented as a 3-tuple including the parent
- node, the current key/index and the value. This is
- an immutable object so it can be cached.
- properties:
- key: mapping key or index within the parent collection
- value: current value within the parent's range
- parent: the parent context
- root: the very top of the yaml graph
- path: a tuple of the domain keys
- notes: >
- The context class doesn't yet handle going down the
- domain side of the tree...
- """
- def __init__(self,parent,key,value):
- """
- args:
- parent: parent context (or None if this is the root)
- key: mapping key or index for this context
- value: value of current location...
- """
- self.parent = parent
- self.key = key
- self.value = value
- if parent:
- assert parent.__class__ is self.__class__
- self.path = parent.path + (escape(key),)
- self.root = parent.root
- else:
- assert not key
- self.path = tuple()
- self.root = self
- def __setattr__(self,attname,attval):
- if attname in ('parent','key','value'):
- if self.__dict__.get(attname):
- raise ValueError("context is read-only")
- self.__dict__[attname] = attval
- def __hash__(self): return hash(self.path)
- def __cmp__(self,other):
- try:
- return cmp(self.path,other.path)
- except AttributeError:
- return -1
- def __str__(self):
- if self.path:
- return "/".join(('',)+self.path)
- else:
- return '/'
-
-def to_context(target):
- if type(target) is InstanceType:
- if target.__class__ is context:
- return target
- return context(None,None,target)
-
-def context_test():
- lst = ['value']
- map = {'key':lst}
- x = context(None,None,map)
- y = context(x,'key',lst)
- z = context(y,0,'value')
- assert ('key',) == y.path
- assert 'key' == y.key
- assert lst == y.value
- assert x == y.parent
- assert x == y.root
- assert 0 == z.key
- assert 'value' == z.value
- assert y == z.parent
- assert x == z.root
- assert hash(x)
- assert hash(y)
- assert hash(z)
- assert '/' == str(x)
- assert '/key' == str(y)
- assert '/key/0' == str(z)
-
-class null_seg:
- """
- summary: >
- This is the simplest path segment, it
- doesn't return any results and doesn't
- depend upon its context. It also happens to
- be the base class which all segments derive.
- """
- def __iter__(self):
- return self
- def next_null(self):
- raise StopIteration
- def bind(self,cntx):
- """
- summary: >
- The bind function is called whenever
- the parent context has changed.
- """
- assert(cntx.__class__ is context)
- self.cntx = cntx
- def apply(self,target):
- self.bind(to_context(target))
- return iter(self)
- def exists(self,cntx):
- try:
- self.bind(cntx)
- self.next()
- return 1
- except StopIteration:
- return 0
- next = next_null
-
-class self_seg(null_seg):
- """
- summary: >
- This path segment returns the context
- node exactly once.
- """
- def __str__(self): return '.'
- def next_self(self):
- self.next = self.next_null
- return self.cntx
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.next = self.next_self
-
-class root_seg(self_seg):
- def __str__(self): return '/'
- def bind(self,cntx):
- self_seg.bind(self,cntx.root)
-
-class parent_seg(self_seg):
- def __str__(self): return '..'
- def bind(self,cntx):
- if cntx.parent: cntx = cntx.parent
- self_seg.bind(self,cntx)
-
-class wild_seg(null_seg):
- """
- summary: >
- The wild segment simply loops through
- all of the sub-contexts for a given object.
- If there aren't any children, this isn't an
- error it just doesn't return anything.
- """
- def __str__(self): return '*'
- def next_wild(self):
- key = self.keys.next()
- return context(self.cntx,key,self.values[key])
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- typ = type(cntx.value)
- if typ is ListType:
- self.keys = iter(xrange(0,len(cntx.value)))
- self.values = cntx.value
- self.next = self.next_wild
- return
- if typ is DictType:
- self.keys = iter(cntx.value)
- self.values = cntx.value
- self.next = self.next_wild
- return
- self.next = self.next_null
-
-class trav_seg(null_seg):
- """
- summary: >
- This is a recursive traversal of the range, preorder.
- It is a recursive combination of self and wild.
- """
- def __str__(self): return '/'
- def next(self):
- while 1:
- (cntx,seg) = self.stk[-1]
- if not seg:
- seg = wild_seg()
- seg.bind(cntx)
- self.stk[-1] = (cntx,seg)
- return cntx
- try:
- cntx = seg.next()
- self.stk.append((cntx,None))
- except StopIteration:
- self.stk.pop()
- if not(self.stk):
- self.next = self.next_null
- raise StopIteration
-
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.stk = [(cntx,None)]
-
-class match_seg(self_seg):
- """
- summary: >
- Matches a particular key within the
- current context. Kinda boring.
- """
- def __str__(self): return str(self.key)
- def __init__(self,key):
- #TODO: Do better implicit typing
- try:
- key = int(key)
- except: pass
- self.key = key
- def bind(self,cntx):
- try:
- mtch = cntx.value[self.key]
- cntx = context(cntx,self.key,mtch)
- self_seg.bind(self,cntx)
- except:
- null_seg.bind(self,cntx)
-
-class conn_seg(null_seg):
- """
- summary: >
- When two segments are connected via a slash,
- this is a composite. For each context of the
- parent, it binds the child, and returns each
- context of the child.
- """
- def __str__(self):
- if self.parent.__class__ == root_seg:
- return "/%s" % self.child
- return "%s/%s" % (self.parent, self.child)
- def __init__(self,parent,child):
- self.parent = parent
- self.child = child
- def next(self):
- while 1:
- try:
- return self.child.next()
- except StopIteration:
- cntx = self.parent.next()
- self.child.bind(cntx)
-
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.parent.bind(cntx)
- try:
- cntx = self.parent.next()
- except StopIteration:
- return
- self.child.bind(cntx)
-
-
-class pred_seg(null_seg):
- def __str__(self): return "%s[%s]" % (self.parent, self.filter)
- def __init__(self,parent,filter):
- self.parent = parent
- self.filter = filter
- def next(self):
- while 1:
- ret = self.parent.next()
- if self.filter.exists(ret):
- return ret
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.parent.bind(cntx)
-
-class or_seg(null_seg):
- def __str__(self): return "%s|%s" % (self.lhs,self.rhs)
- def __init__(self,lhs,rhs):
- self.rhs = rhs
- self.lhs = lhs
- self.unq = {}
- def next(self):
- seg = self.lhs
- try:
- nxt = seg.next()
- self.unq[nxt] = nxt
- return nxt
- except StopIteration: pass
- seg = self.rhs
- while 1:
- nxt = seg.next()
- if self.unq.get(nxt,None):
- continue
- return nxt
- def bind(self,cntx):
- null_seg.bind(self,cntx)
- self.lhs.bind(cntx)
- self.rhs.bind(cntx)
-
-class scalar:
- def __init__(self,val):
- self.val = val
- def __str__(self):
- return str(self.val)
- def value(self):
- return self.val
-
-class equal_pred:
- def exists_true(self,cntx): return 1
- def exists_false(self,cntx): return 0
- def exists_scalar(self,cntx):
- self.rhs.bind(cntx)
- try:
- while 1:
- cntx = self.rhs.next()
- if str(cntx.value) == self.lhs: #TODO: Remove type hack
- return 1
- except StopIteration: pass
- return 0
- def exists_segment(self,cntx):
- raise NotImplementedError()
- def __init__(self,lhs,rhs):
- if lhs.__class__ == scalar:
- if rhs.__class__ == scalar:
- if rhs.value() == lhs.value():
- self.exists = self.exists_true
- else:
- self.exists = self.exists_false
- else:
- self.exists = self.exists_scalar
- else:
- if rhs.__class__ == scalar:
- (lhs,rhs) = (rhs,lhs)
- self.exists = self.exists_scalar
- else:
- self.exists = self.exists_segment
- self.lhs = str(lhs.value()) #TODO: Remove type hack
- self.rhs = rhs
-
-matchSegment = re.compile(r"""^(\w+|/|\.|\*|\"|\')""")
-
-def parse_segment(expr):
- """
- Segments occur between the slashes...
- """
- mtch = matchSegment.search(expr)
- if not(mtch): return (None,expr)
- tok = mtch.group(); siz = len(tok)
- if '/' == tok: return (trav_seg(),expr)
- elif '.' == tok:
- if len(expr) > 1 and '.' == expr[1]:
- seg = parent_seg()
- siz = 2
- else:
- seg = self_seg()
- elif '*' == tok: seg = wild_seg()
- elif '"' == tok or "'" == tok:
- (cur,siz) = unquote(expr)
- seg = match_seg(cur)
- else:
- seg = match_seg(tok)
- return (seg,expr[siz:])
-
-matchTerm = re.compile(r"""^(\w+|/|\.|\(|\"|\')""")
-
-def parse_term(expr):
- mtch = matchTerm.search(expr)
- if not(mtch): return (None,expr)
- tok = mtch.group(); siz = len(tok)
- if '/' == tok or '.' == tok:
- return parse(expr)
- if '(' == tok:
- (term,expr) = parse_predicate(expr)
- assert ')' == expr[0]
- return (term,expr[1:])
- elif '"' == tok or "'" == tok:
- (val,siz) = unquote(expr)
- else:
- val = tok; siz = len(tok)
- return (scalar(val),expr[siz:])
-
-def parse_predicate(expr):
- (term,expr) = parse_term(expr)
- if not term: raise SyntaxError("term expected: '%s'" % expr)
- tok = expr[0]
- if '=' == tok:
- (rhs,expr) = parse_term(expr[1:])
- return (equal_pred(term,rhs),expr)
- if '(' == tok:
- raise "No functions allowed... yet!"
- if ']' == tok or ')' == tok:
- if term.__class__ is scalar:
- term = match_seg(str(term))
- return (term,expr)
- raise SyntaxError("ypath: expecting operator '%s'" % expr)
-
-def parse_start(expr):
- """
- Initial checking on the expression, and
- determine if it is relative or absolute.
- """
- if type(expr) != StringType or len(expr) < 1:
- raise TypeError("string required: " + repr(expr))
- if '/' == expr[0]:
- ypth = root_seg()
- else:
- ypth = self_seg()
- expr = '/' + expr
- return (ypth,expr)
-
-def parse(expr):
- """
- This the parser entry point, the top level node
- is always a root or self segment. The self isn't
- strictly necessary, but it keeps things simple.
- """
- (ypth,expr) = parse_start(expr)
- while expr:
- tok = expr[0]
- if '/' == tok:
- (child, expr) = parse_segment(expr[1:])
- if child: ypth = conn_seg(ypth,child)
- continue
- if '[' == tok:
- (filter, expr) = parse_predicate(expr[1:])
- assert ']' == expr[0]
- expr = expr[1:]
- ypth = pred_seg(ypth,filter)
- continue
- if '|' == tok:
- (rhs, expr) = parse(expr[1:])
- ypth = or_seg(ypth,rhs)
- continue
- if '(' == tok:
- (child,expr) = parse(expr[1:])
- assert ')' == expr[0]
- expr = expr[1:]
- ypth = conn_seg(ypth,child)
- continue
- break
- return (ypth,expr)
-
-class convert_to_value(null_seg):
- def __init__(self,itr):
- self.itr = itr
- def next(self):
- return self.itr.next().value
- def bind(self,cntx):
- self.itr.bind(cntx)
-
-def ypath(expr,target=noTarget,cntx=0):
- (ret,expr) = parse(expr)
- if expr: raise SyntaxError("ypath parse error `%s`" % expr)
- if not cntx: ret = convert_to_value(ret)
- if target is noTarget: return ret
- return ret.apply(target)
diff --git a/cobbler_register b/cobbler_register new file mode 100644 index 00000000..6a5dade5 --- /dev/null +++ b/cobbler_register @@ -0,0 +1,11 @@ +# begin cobbler registration for profiles only, if enabled +#if $getVar('system_name','') == '' +#set rnist = $getVar('register_new_installs','') +#if $str(rnist).lower() in [ "1", "y", "yes", "true" ] +if [ -f "/usr/bin/cobbler-register" ]; then + /usr/bin/cobbler-register --server=$server --profile=$profile_name --hostname='*FULLAUTO*' +fi +#end if +#end if +# end cobbler registration + diff --git a/config/cheetah_macros b/config/cheetah_macros index 338bbd4d..c2c64a03 100644 --- a/config/cheetah_macros +++ b/config/cheetah_macros @@ -1,253 +1,2 @@ - -## Comment every line containing the $pattern given -## Ex: preserve a record of an old value before changing it. -## -## $comment_lines('/etc/resolv.conf', 'nameserver') -## echo "nameserver 192.168.0.1" >> /etc/resolv.conf -## -#def comment_lines($filename, $pattern, $commentchar='#') -perl -npe 's/^(.*${pattern}.*)$/${commentchar}\${1}/' -i '$filename' -#end def - -## Comments every line which contains only the exact pattern. -## This one works like comment_lines(), except that a line cannot contain any -## additional text. -#def comment_lines_exact($filename, $pattern, $commentchar='#') -perl -npe 's/^(${pattern})$/${commentchar}\${1}/' -f '$filename' -#end def - -## Uncomments every (commented) line containing the pattern -## Patterns should not contain the # -## Ex: enable all the suggested values in the Samba configuration -## (This isn't the greatest example, but it makes a point) -## -## $uncomment_lines('/etc/samba/smb.conf', ';') -## -#def uncomment_lines($filename, $pattern, $commentchar='#') -perl -npe 's/^[ \t]*${commentchar}(.*${pattern}.*)$/\${1}/' -i '$filename' -#end def - -## Nullify (by changing to 'true') all instances of a given sh command. This -## does understand lines with multiple commands (separated by ';') and also -## knows to ignore comments. Consider other options before using this -## method. -## Ex: remove 'exit 0' commands from a shell script, so that we can append the -## script and be relatively certain that the new parts will be executed. -## -## $delete_command('etc/cron.daily/some_script.sh', 'exit[ \t]*0') -## echo '# More scipt' >> /etc/cron.daily/some_script.sh -## -#def delete_command($filename, $pattern) -sed -nr ' - h - s/^([^#]*)(#?.*)$/\1/ - s/((^|;)[ \t]*)${pattern}([ \t]*($|;))/\1true\3/g - s/((^|;)[ \t]*)${pattern}([ \t]*($|;))/\1true\3/g - x - s/^([^#]*)(#?.*)$/\2/ - H - x - s/\n// - p -' -i '$filename' -#end def - -## Replace a configuration parameter value, or add it if it doesn't exist. -## Assumes format is [param_name] [value] -## Ex: Change the maximum password age to 30 days -## -## $set_config_value('/etc/login.defs', 'PASS_MAX_DAYS', '30') -## -#def set_config_value($filename, $param_name, $value) -if [ -n \"\$(grep -Ee '^[ \t]*${param_name}[ \t]+' '$filename')\" ] -then - perl -npe 's/^([ \t]*${param_name}[ \t]+)[\x21-\x7E]*([ \t]*(#.*)?)$/\${1}${sedesc($value)}\${2}/' -i '$filename' -else - echo '$param_name $value' >> '$filename' -fi -#end def - -## Replace a configuration parameter value, or add it if it doesn't exist. -## Assues format is [param_name] [delimiter] [value], where [delimiter] is -## usually '='. -## This works the same way as set_config_value(), except that this version -## is used if a character separates a parameter from its value. -#def set_config_value_delim($filename, $param_name, $delim, $value) -if [ -n \"\$(grep -Ee '^[ \t]*${param_name}[ \t]*${delim}[ \t]*' '$filename')\" ] -then - perl -npe 's/^([ \t]*${param_name}[ \t]*${delim}[ \t]*)[\x21-\x7E]*([ \t]*(#.*)?)$/${1}${sedesc($value)}${2}/' -i '$filename' -else - echo '$param_name$delim$value' >> '$filename' -fi -#end def - -## Copy a file from the server to the client. -## Ex: Copy a template for samba configuration -## -## (once at the top of the kickstart template) -## #set files = $snippetsdir + '/files/' -## (when you need to copy a file) -## $copy_over_file('etc/samba/smb.conf', '/etc/samba/smb.conf') -## -## Additionally, copied files can be templated: -## ---------etc/samba/smb.conf------------- -## ... -## [global] -## server string = $profile_name -## ... -## ---------------------------------------- -#def copy_over_file($serverfile, $clientfile) -cat << 'EOF' > '$clientfile' -#include $files + $serverfile -EOF -#end def - -## Copy a file from the server and append the contents to a file on the -## client. -## This works the same as copy_over_file(), except it appends the file rather -## than replacing the file. -#def copy_append_file($serverfile, $clientfile) -cat << 'EOF' >> '$clientfile' -#include $files + $serverfile -EOF -#end def - -## Convenience function: Copy/append several files at once. This accepts a -## list of tuples. The first element indicates whether to overwrite ('w') or -## append ('a'). The second element is the file name on both the server and -## the client (a '/' is prepended on the client side). -## Ex: copy a template for samba and audit configuration -## -## $copy_files([ -## ('w', 'etc/samba/smb.conf'), -## ('w', 'etc/audit.rules'), -## ]) -## -#def copy_files($filelist) -#for $thisfile in $filelist -#if $thisfile[0] == 'a' -$copy_append_file($thisfile[1], '/' + $thisfile[1]) -#else -$copy_over_file($thisfile[1], '/' + $thisfile[1]) -#end if -#end for -#end def - -## Append some content to the todo file. NOTE: $todofile must be defined -## before using this (unless you want unexpected results). Be sure to end -## the content with 'EOF' -## Ex: Instruct the admin to set an appropriate nameserver. -## -## (once at the top of the kickstart template) -## #set global $todofile = '/root/kstodo' -## (as needed) -## $TODO() -## Edit /etc/resolv.conf to configure your local nameserver -## EOF -## -## This will prevent inconsistency and accidents. You should avoid using: -## -## echo "Edit /etc/resolv.conf..." >> /root/kstodo -## -## It's easy to forget to use >> to append instead of >, which will clobber all -## previous todo notices. It's also easy to forget the filename, was it kstodo -## or ks-todo? -#def TODO() -cat << 'EOF' >> '$todofile' -#end def - -## Set the owner, group, and permissions for several files. Assignment can -## be plain ('p') or recursive. If recursive you can assign everything ('r') -## or just files ('f'). This method takes a list of tuples. The first element -## of each indicates which style. The remaining elements are owner, group, -## and mode respectively. If 'f' is used, an additional element is a find -## pattern that can further restrict assignments (use '*' if no additional -## restrict is desired). -## NOTE: I used the word 'plain' instead of 'single', because wildcards can -## still be used in 'plain' mode. -## Ex: correct the permissions of serveral important files and directories: -## -## $set_permissions([ -## ('p', 'root', 'root', '700', '/root'), -## ('f', 'root', 'root', '600', '/root', '*'), -## ('r', 'root', 'root', '/etc/cron.*'), -## ('p', 'root', 'root', '644', '/etc/samba/smb.conf'), -## ]) -## -#def set_permissions($filelist) -#for $file in $filelist -#if $file[0] == 'p' -#if $file[1] != '' and $file[2] != '' -chown '$file[1]:$file[2]' '$file[4]' -#else -#if $file[1] != '' -chown '$file[1]' '$file[4]' -#end if -#if $file[2] != '' -chgrp '$file[2]' '$file[4]' -#end if -#end if -#if $file[3] != '' -chmod '$file[3]' '$file[4]' -#end if -#elif $file[0] == 'r' -#if $file[1] != '' and $file[2] != '' -chown -R '$file[1]:$file[2]' '$file[4]' -#else -#if $file[1] != '' -chown -R '$file[1]' '$file[4]' -#end if -#if $file[2] != '' -chgrp -R '$file[2]' '$file[4]' -#end if -#end if -#if $file[3] != '' -chmod -R '$file[3]' '$file[4]' -#end if -#elif $file[0] == 'f' -#if $file[1] != '' and $file[2] != '' -find $file[4] -name '$file[5]' -type f -exec chown -R '$file[1]:$file[2]' {} \; -#else -#if $file[1] != '' -find $file[4] -name '$file[5]' -type f -exec chown -R '$file[1]' {} \; -#end if -#if $file[2] != '' -find $file[4] -name '$file[5]' -type f -exec chgrp -R '$file[2]' {} \; -#end if -#end if -#if $file[3] != '' -find $file[4] -name '$file[5]' -type f -exec chmod -R '$file[3]' {} \; -#end if -#end if -#end for -#end def - -## Cheeseball an entire directory. -## This will include (in sequence) all file in a given directory into a -## kickstart template. -## Ex: include a 'misc' directory of templates -## -## $includeall('misc') -## -## Now in cobbler/snippets/misc: -## ---------------avinstall----------------- -## wget http://some.server.com/some-av-package.tar.gz -## tar -xzf some-av-package.tar.gz -## ./some-av-package/install.sh -## rm some-av-package.tar.gz -## rm -rf some-av-package -## ----------------------------------------- -## ---------------fwinstall----------------- -## wget http://some.server.com/fw-linux-installer.sh -## chmod +x fw-linux-installer.sh -## ./fw-linux-installer.sh -## rm fw-linux-installer.sh -## ----------------------------------------- -## -#def includeall($dir) -#import os -#for $file in $os.listdir($snippetsdir + '/' + $dir) -#include $snippetsdir + '/' + $dir + '/' + $file -#end for -#end def +## define Cheetah functions here and reuse them throughout your templates diff --git a/config/cobbler.conf b/config/cobbler.conf index 00e4913e..2dda27ec 100644 --- a/config/cobbler.conf +++ b/config/cobbler.conf @@ -16,11 +16,6 @@ ProxyRequests off ProxyPass /cobbler_api http://localhost:25151/ ProxyPassReverse /cobbler_api http://localhost:25151/ -# TO DO: SSL - -ProxyPass /cobbler_api_rw http://localhost:25152/ -ProxyPassReverse /cobbler_api_rw http://localhost:25152/ - BrowserMatch "MSIE" AuthDigestEnableQueryStringHack=On # For misc CGI scripts diff --git a/config/cobblerd_rotate b/config/cobblerd_rotate index e739c470..703faf26 100644 --- a/config/cobblerd_rotate +++ b/config/cobblerd_rotate @@ -10,7 +10,7 @@ endscript } -/var/log/cobbler/webui.log { +/var/log/cobbler/cobblerd.log { missingok notifempty rotate 4 diff --git a/contrib/cheetah_macros b/contrib/cheetah_macros new file mode 100644 index 00000000..338bbd4d --- /dev/null +++ b/contrib/cheetah_macros @@ -0,0 +1,253 @@ + +## Comment every line containing the $pattern given +## Ex: preserve a record of an old value before changing it. +## +## $comment_lines('/etc/resolv.conf', 'nameserver') +## echo "nameserver 192.168.0.1" >> /etc/resolv.conf +## +#def comment_lines($filename, $pattern, $commentchar='#') +perl -npe 's/^(.*${pattern}.*)$/${commentchar}\${1}/' -i '$filename' +#end def + +## Comments every line which contains only the exact pattern. +## This one works like comment_lines(), except that a line cannot contain any +## additional text. +#def comment_lines_exact($filename, $pattern, $commentchar='#') +perl -npe 's/^(${pattern})$/${commentchar}\${1}/' -f '$filename' +#end def + +## Uncomments every (commented) line containing the pattern +## Patterns should not contain the # +## Ex: enable all the suggested values in the Samba configuration +## (This isn't the greatest example, but it makes a point) +## +## $uncomment_lines('/etc/samba/smb.conf', ';') +## +#def uncomment_lines($filename, $pattern, $commentchar='#') +perl -npe 's/^[ \t]*${commentchar}(.*${pattern}.*)$/\${1}/' -i '$filename' +#end def + +## Nullify (by changing to 'true') all instances of a given sh command. This +## does understand lines with multiple commands (separated by ';') and also +## knows to ignore comments. Consider other options before using this +## method. +## Ex: remove 'exit 0' commands from a shell script, so that we can append the +## script and be relatively certain that the new parts will be executed. +## +## $delete_command('etc/cron.daily/some_script.sh', 'exit[ \t]*0') +## echo '# More scipt' >> /etc/cron.daily/some_script.sh +## +#def delete_command($filename, $pattern) +sed -nr ' + h + s/^([^#]*)(#?.*)$/\1/ + s/((^|;)[ \t]*)${pattern}([ \t]*($|;))/\1true\3/g + s/((^|;)[ \t]*)${pattern}([ \t]*($|;))/\1true\3/g + x + s/^([^#]*)(#?.*)$/\2/ + H + x + s/\n// + p +' -i '$filename' +#end def + +## Replace a configuration parameter value, or add it if it doesn't exist. +## Assumes format is [param_name] [value] +## Ex: Change the maximum password age to 30 days +## +## $set_config_value('/etc/login.defs', 'PASS_MAX_DAYS', '30') +## +#def set_config_value($filename, $param_name, $value) +if [ -n \"\$(grep -Ee '^[ \t]*${param_name}[ \t]+' '$filename')\" ] +then + perl -npe 's/^([ \t]*${param_name}[ \t]+)[\x21-\x7E]*([ \t]*(#.*)?)$/\${1}${sedesc($value)}\${2}/' -i '$filename' +else + echo '$param_name $value' >> '$filename' +fi +#end def + +## Replace a configuration parameter value, or add it if it doesn't exist. +## Assues format is [param_name] [delimiter] [value], where [delimiter] is +## usually '='. +## This works the same way as set_config_value(), except that this version +## is used if a character separates a parameter from its value. +#def set_config_value_delim($filename, $param_name, $delim, $value) +if [ -n \"\$(grep -Ee '^[ \t]*${param_name}[ \t]*${delim}[ \t]*' '$filename')\" ] +then + perl -npe 's/^([ \t]*${param_name}[ \t]*${delim}[ \t]*)[\x21-\x7E]*([ \t]*(#.*)?)$/${1}${sedesc($value)}${2}/' -i '$filename' +else + echo '$param_name$delim$value' >> '$filename' +fi +#end def + +## Copy a file from the server to the client. +## Ex: Copy a template for samba configuration +## +## (once at the top of the kickstart template) +## #set files = $snippetsdir + '/files/' +## (when you need to copy a file) +## $copy_over_file('etc/samba/smb.conf', '/etc/samba/smb.conf') +## +## Additionally, copied files can be templated: +## ---------etc/samba/smb.conf------------- +## ... +## [global] +## server string = $profile_name +## ... +## ---------------------------------------- +#def copy_over_file($serverfile, $clientfile) +cat << 'EOF' > '$clientfile' +#include $files + $serverfile +EOF +#end def + +## Copy a file from the server and append the contents to a file on the +## client. +## This works the same as copy_over_file(), except it appends the file rather +## than replacing the file. +#def copy_append_file($serverfile, $clientfile) +cat << 'EOF' >> '$clientfile' +#include $files + $serverfile +EOF +#end def + +## Convenience function: Copy/append several files at once. This accepts a +## list of tuples. The first element indicates whether to overwrite ('w') or +## append ('a'). The second element is the file name on both the server and +## the client (a '/' is prepended on the client side). +## Ex: copy a template for samba and audit configuration +## +## $copy_files([ +## ('w', 'etc/samba/smb.conf'), +## ('w', 'etc/audit.rules'), +## ]) +## +#def copy_files($filelist) +#for $thisfile in $filelist +#if $thisfile[0] == 'a' +$copy_append_file($thisfile[1], '/' + $thisfile[1]) +#else +$copy_over_file($thisfile[1], '/' + $thisfile[1]) +#end if +#end for +#end def + +## Append some content to the todo file. NOTE: $todofile must be defined +## before using this (unless you want unexpected results). Be sure to end +## the content with 'EOF' +## Ex: Instruct the admin to set an appropriate nameserver. +## +## (once at the top of the kickstart template) +## #set global $todofile = '/root/kstodo' +## (as needed) +## $TODO() +## Edit /etc/resolv.conf to configure your local nameserver +## EOF +## +## This will prevent inconsistency and accidents. You should avoid using: +## +## echo "Edit /etc/resolv.conf..." >> /root/kstodo +## +## It's easy to forget to use >> to append instead of >, which will clobber all +## previous todo notices. It's also easy to forget the filename, was it kstodo +## or ks-todo? +#def TODO() +cat << 'EOF' >> '$todofile' +#end def + +## Set the owner, group, and permissions for several files. Assignment can +## be plain ('p') or recursive. If recursive you can assign everything ('r') +## or just files ('f'). This method takes a list of tuples. The first element +## of each indicates which style. The remaining elements are owner, group, +## and mode respectively. If 'f' is used, an additional element is a find +## pattern that can further restrict assignments (use '*' if no additional +## restrict is desired). +## NOTE: I used the word 'plain' instead of 'single', because wildcards can +## still be used in 'plain' mode. +## Ex: correct the permissions of serveral important files and directories: +## +## $set_permissions([ +## ('p', 'root', 'root', '700', '/root'), +## ('f', 'root', 'root', '600', '/root', '*'), +## ('r', 'root', 'root', '/etc/cron.*'), +## ('p', 'root', 'root', '644', '/etc/samba/smb.conf'), +## ]) +## +#def set_permissions($filelist) +#for $file in $filelist +#if $file[0] == 'p' +#if $file[1] != '' and $file[2] != '' +chown '$file[1]:$file[2]' '$file[4]' +#else +#if $file[1] != '' +chown '$file[1]' '$file[4]' +#end if +#if $file[2] != '' +chgrp '$file[2]' '$file[4]' +#end if +#end if +#if $file[3] != '' +chmod '$file[3]' '$file[4]' +#end if +#elif $file[0] == 'r' +#if $file[1] != '' and $file[2] != '' +chown -R '$file[1]:$file[2]' '$file[4]' +#else +#if $file[1] != '' +chown -R '$file[1]' '$file[4]' +#end if +#if $file[2] != '' +chgrp -R '$file[2]' '$file[4]' +#end if +#end if +#if $file[3] != '' +chmod -R '$file[3]' '$file[4]' +#end if +#elif $file[0] == 'f' +#if $file[1] != '' and $file[2] != '' +find $file[4] -name '$file[5]' -type f -exec chown -R '$file[1]:$file[2]' {} \; +#else +#if $file[1] != '' +find $file[4] -name '$file[5]' -type f -exec chown -R '$file[1]' {} \; +#end if +#if $file[2] != '' +find $file[4] -name '$file[5]' -type f -exec chgrp -R '$file[2]' {} \; +#end if +#end if +#if $file[3] != '' +find $file[4] -name '$file[5]' -type f -exec chmod -R '$file[3]' {} \; +#end if +#end if +#end for +#end def + +## Cheeseball an entire directory. +## This will include (in sequence) all file in a given directory into a +## kickstart template. +## Ex: include a 'misc' directory of templates +## +## $includeall('misc') +## +## Now in cobbler/snippets/misc: +## ---------------avinstall----------------- +## wget http://some.server.com/some-av-package.tar.gz +## tar -xzf some-av-package.tar.gz +## ./some-av-package/install.sh +## rm some-av-package.tar.gz +## rm -rf some-av-package +## ----------------------------------------- +## ---------------fwinstall----------------- +## wget http://some.server.com/fw-linux-installer.sh +## chmod +x fw-linux-installer.sh +## ./fw-linux-installer.sh +## rm fw-linux-installer.sh +## ----------------------------------------- +## +#def includeall($dir) +#import os +#for $file in $os.listdir($snippetsdir + '/' + $dir) +#include $snippetsdir + '/' + $dir + '/' + $file +#end for +#end def + diff --git a/contrib/ruby/Rakefile b/contrib/ruby/Rakefile index 7828c0ad..722749cb 100644 --- a/contrib/ruby/Rakefile +++ b/contrib/ruby/Rakefile @@ -22,7 +22,7 @@ require 'rake/testtask' require 'rake/gempackagetask' PKG_NAME='rubygem-cobbler' -PKG_VERSION='0.1.3' +PKG_VERSION='0.1.9' PKG_FILES=FileList[ 'Rakefile', 'README', 'ChangeLog', 'COPYING', 'NEWS', 'TODO', 'lib/**/*.rb', diff --git a/contrib/ruby/examples/check_version.rb b/contrib/ruby/examples/check_version.rb new file mode 100755 index 00000000..1d5653e5 --- /dev/null +++ b/contrib/ruby/examples/check_version.rb @@ -0,0 +1,62 @@ +#!/usr/bin/ruby -w +# +# create_system.rb - example of using rubygem-cobbler to create a system. +# +# Copyright (C) 2008 Red Hat, Inc. +# Written by Darryl L. Pierce <dpierce@redhat.com> +# +# This file is part of rubygem-cobbler. +# +# rubygem-cobbleris free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# rubygem-cobbler 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 rubygem-cobbler. If not, see <http://www.gnu.org/licenses/>. +# + +base = File.expand_path(File.join(File.dirname(__FILE__), "..")) +$LOAD_PATH << File.join(base, "lib") +$LOAD_PATH << File.join(base, "examples") + +require 'getoptlong' + +require 'cobbler' + +include Cobbler + +opts = GetoptLong.new( + ['--hostname', '-s', GetoptLong::REQUIRED_ARGUMENT ], + ['--help', '-h', GetoptLong::NO_ARGUMENT]) + +hostname = nil + +def usage + puts "Usage: #{$0} --hostname hostname\n" + exit +end + +opts.each do |opt, arg| + case opt + when '--hostname' then hostname = arg + when '--help' then usage + end +end + +if hostname + Base.hostname = hostname + version = Base.remote_version + if version + puts "Remote version: #{version}" + else + puts "Unable to determine version." + end +else + usage +end diff --git a/contrib/ruby/examples/create_system.rb b/contrib/ruby/examples/create_system.rb index dd938fe1..b2d2138a 100755 --- a/contrib/ruby/examples/create_system.rb +++ b/contrib/ruby/examples/create_system.rb @@ -1,14 +1,14 @@ -#!/usr/bin/ruby -w +#!/usr/bin/ruby -w # # create_system.rb - example of using rubygem-cobbler to create a system. -# +# # Copyright (C) 2008 Red Hat, Inc. # Written by Darryl L. Pierce <dpierce@redhat.com> # # This file is part of rubygem-cobbler. # # rubygem-cobbleris free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published +# it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 2.1 of the License, or # (at your option) any later version. # @@ -20,7 +20,7 @@ # You should have received a copy of the GNU General Public License # along with rubygem-cobbler. If not, see <http://www.gnu.org/licenses/>. # - + base = File.expand_path(File.join(File.dirname(__FILE__), "..")) $LOAD_PATH << File.join(base, "lib") $LOAD_PATH << File.join(base, "examples") @@ -31,41 +31,49 @@ require 'cobbler' include Cobbler -opts = GetoptLong.new( - ['--hostname', '-s', GetoptLong::REQUIRED_ARGUMENT ], - ['--name', '-n', GetoptLong::REQUIRED_ARGUMENT ], - ['--profile', '-f', GetoptLong::REQUIRED_ARGUMENT ], - ['--username', '-u', GetoptLong::REQUIRED_ARGUMENT ], - ['--password', '-p', GetoptLong::REQUIRED_ARGUMENT ], - ['--help', '-h', GetoptLong::NO_ARGUMENT] -) +opts = GetoptLong.new(['--hostname', '-s', GetoptLong::REQUIRED_ARGUMENT ], + ['--name', '-n', GetoptLong::REQUIRED_ARGUMENT ], + ['--profile', '-f', GetoptLong::REQUIRED_ARGUMENT ], + ['--image', '-i', GetoptLong::REQUIRED_ARGUMENT ], + ['--username', '-u', GetoptLong::REQUIRED_ARGUMENT ], + ['--password', '-p', GetoptLong::REQUIRED_ARGUMENT ], + ['--help', '-h', GetoptLong::NO_ARGUMENT], + ['--debug', '-d', GetoptLong::NO_ARGUMENT]) -name = profile = hostname = username = password = nil +name = profile = image = hostname = username = password = debug = nil def usage - puts "Usage: #{$0} --name system-name --profile profile-name [--hostname hostname] [--username username] [--password password]\n" + puts "Usage: #{$0} --name system-name [--profile profile-name | --image image-name ] [--hostname hostname] [--username username] [--password password]\n" exit end - + opts.each do |opt, arg| case opt when '--hostname' then hostname = arg when '--name' then name = arg when '--profile' then profile = arg + when '--image' then image = arg when '--username' then username = arg when '--password' then password = arg when '--help' then usage + when '--debug' then debug = true end end -if name && profile - +if (profile && image) + puts "\nYou must specify either a profile or an image, but not both.\n\n" + usage +end + +if name && (profile || image) System.hostname = hostname if hostname System.username = username if username System.password = password if password - - system = System.new('name' => name,'profile' => profile) - + System.debug = true if debug + + system = System.new('name' => name,'profile' => profile) if profile + system = System.new('name' => name,'image' => image) if image + system.interfaces=[NetworkInterface.new({'mac_address' => '00:11:22:33:44:55:66:77'})] puts "Saving a new system with name #{system.name} based on the profile #{system.profile}." @@ -78,4 +86,4 @@ if name && profile end else usage -end
\ No newline at end of file +end diff --git a/contrib/ruby/examples/list_systems.rb b/contrib/ruby/examples/list_systems.rb index 61afe800..4474905a 100755 --- a/contrib/ruby/examples/list_systems.rb +++ b/contrib/ruby/examples/list_systems.rb @@ -1,14 +1,14 @@ -#!/usr/bin/ruby +#!/usr/bin/ruby # # list_systems.rb - example of using rubygem-cobbler to list system. -# -# Copyright (C) 2008 Red Hat, Inc. +# +# Copyright (C) 2008, 2009, Red Hat, Inc. # Written by Darryl L. Pierce <dpierce@redhat.com> # # This file is part of rubygem-cobbler. # # rubygem-cobbleris free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published +# it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 2.1 of the License, or # (at your option) any later version. # @@ -20,7 +20,7 @@ # You should have received a copy of the GNU General Public License # along with rubygem-cobbler. If not, see <http://www.gnu.org/licenses/>. # - + base = File.expand_path(File.join(File.dirname(__FILE__), "..")) $LOAD_PATH << File.join(base, "lib") $LOAD_PATH << File.join(base, "examples") @@ -31,14 +31,14 @@ require 'cobbler' include Cobbler -opts = GetoptLong.new( - ['--hostname', '-s', GetoptLong::REQUIRED_ARGUMENT ], - ['--details', '-d', GetoptLong::NO_ARGUMENT ], - ['--help', '-h', GetoptLong::NO_ARGUMENT ] -) +opts = GetoptLong.new(['--hostname', '-s', GetoptLong::REQUIRED_ARGUMENT ], + ['--details', '-v', GetoptLong::NO_ARGUMENT ], + ['--help', '-h', GetoptLong::NO_ARGUMENT ], + ['--debug', '-d', GetoptLong::NO_ARGUMENT ]) hostname = nil details = false +debug = false def usage puts "Usage: #{$0} [--hostname hostname] [--details]\n" @@ -50,18 +50,20 @@ opts.each do |opt, arg| when '--hostname' then hostname = arg when '--details' then details = true when '--help' then usage + when '--debug' then debug = true end end Base.hostname = hostname if hostname - +Base.debug = debug if debug + puts "Results:" -System.find do |system| - puts "\"#{system.name}\" is based on \"#{system.profile}\"." - +System.find do |system| + puts "\"#{system.name}\" is based on the \"#{system.profile}\" profile." unless system.profile.empty? + puts "\"#{system.name}\" is based on the \"#{system.image}\" image." unless system.image.empty? + if details puts "\tOwner: #{system.owners}" - system.interfaces.each_pair { |id,nic| puts "\tNIC[#{id}]: #{nic.mac_address}"} - end + system.interfaces.each_pair { |id,nic| puts "\tNIC[#{id}]: #{nic.mac_address}"} + end end - diff --git a/contrib/ruby/lib/cobbler.rb b/contrib/ruby/lib/cobbler.rb index e4fe0c57..28556e54 100644 --- a/contrib/ruby/lib/cobbler.rb +++ b/contrib/ruby/lib/cobbler.rb @@ -1,7 +1,7 @@ # # cobbler.rb - Cobbler module declaration. -# -# Copyright (C) 2008 Red Hat, Inc. +# +# Copyright (C) 2008,2009 Red Hat, Inc. # Written by Darryl L. Pierce <dpierce@redhat.com> # # This file is part of rubygem-cobbler. @@ -19,23 +19,23 @@ # You should have received a copy of the GNU General Public License # along with rubygem-cobbler. If not, see <http://www.gnu.org/licenses/>. # - + require 'cobbler/base' require 'cobbler/distro' require 'cobbler/image' require 'cobbler/network_interface' require 'cobbler/profile' require 'cobbler/system' - -module Cobbler + +module Cobbler config = (ENV['COBBLER_YML'] || File.expand_path("config/cobbler.yml")) - + yml = YAML::load(File.open(config)) if File.exist?(config) - + if yml Base.hostname = yml['hostname'] Base.username = yml['username'] Base.password = yml['password'] end - + end diff --git a/contrib/ruby/lib/cobbler/base.rb b/contrib/ruby/lib/cobbler/base.rb index ff5f565a..df84ac11 100644 --- a/contrib/ruby/lib/cobbler/base.rb +++ b/contrib/ruby/lib/cobbler/base.rb @@ -1,7 +1,6 @@ - # base.rb # -# Copyright (C) 2008 Red Hat, Inc. +# Copyright (C) 2008,2009 Red Hat, Inc. # Written by Darryl L. Pierce <dpierce@redhat.com> # # This file is part of rubygem-cobbler. @@ -67,6 +66,7 @@ module Cobbler @@hostname = nil @@connection = nil @@auth_token = nil + @@debug = false attr_accessor :definitions @@ -95,16 +95,43 @@ module Cobbler @@password = password end + # Enables debugging of communications. + # + def self.debug=(debug) + @@debug = debug + end + # Sets the connection. This method is only needed during unit testing. # def self.connection=(connection) @@connection = connection end + # Returns the version for the remote cobbler instance. + # + def self.remote_version + connect(false) unless @@connection + @@version ||= make_call("version") + end + # Returns a connection to the Cobbler server. # def self.connect(writable) - @@connection || XMLRPC::Client.new2("http://#{@@hostname}/cobbler_api#{writable ? '_rw' : ''}") + puts "Connection: writable=#{writable}" if @@debug + @@connection ||= XMLRPC::Client.new2("http://#{@@hostname}/cobbler_api") + + # in pre-1.5 versions, a separate path was used for writable calls + # TODO: remove this code in 1.6 (dlp) + version = remote_version + puts "Remote version: #{version}" if @@debug + if writable && + version < 1.5 && + !(@@connection.instance_variable_get('@path') =~ /rw$/) + puts "Older version detected: connecting to R/W endpoint" if @@debug + @@connection = XMLRPC::Client.new2("http://#{@@hostname}/cobbler_api_rw") + end + + return @@connection end # Establishes a connection with the Cobbler system. @@ -124,7 +151,10 @@ module Cobbler def self.make_call(*args) raise Exception.new('No connection established.') unless @@connection - @@connection.call(*args) + puts "Remote call: #{args.first}" if @@debug + result = @@connection.call(*args) + puts "Result: #{result}\n" if @@debug + return result end # Ends a transaction and disconnects. @@ -132,6 +162,7 @@ module Cobbler def self.end_transaction @@connection = nil @@auth_token = nil + @@version = nil end class << self diff --git a/contrib/ruby/lib/cobbler/system.rb b/contrib/ruby/lib/cobbler/system.rb index 398f9934..5751ede1 100644 --- a/contrib/ruby/lib/cobbler/system.rb +++ b/contrib/ruby/lib/cobbler/system.rb @@ -1,13 +1,13 @@ # -# system.rb -# -# Copyright (C) 2008 Red Hat, Inc. +# system.rb +# +# Copyright (C) 2008, 2009, Red Hat, Inc. # Written by Darryl L. Pierce <dpierce@redhat.com> # # This file is part of rubygem-cobbler. # # rubygem-cobbleris free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published +# it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 2.1 of the License, or # (at your option) any later version. # @@ -19,17 +19,17 @@ # You should have received a copy of the GNU General Public License # along with rubygem-cobbler. If not, see <http://www.gnu.org/licenses/>. # - + module Cobbler - + # +System+ represents a system within Cobbler. # class System < Base - - cobbler_lifecycle :find_all => 'get_systems', - :find_one => 'get_system', + + cobbler_lifecycle :find_all => 'get_systems', + :find_one => 'get_system', :remove => 'remove_system' - + cobbler_field :name cobbler_field :parent cobbler_field :profile @@ -47,48 +47,48 @@ module Cobbler cobbler_field :virt_path cobbler_field :virt_ram cobbler_field :virt_type - cobbler_field :virt_bridge + cobbler_field :virt_bridge def initialize(definitions = nil) super(definitions) end - + # Saves this instance. # def save Base.begin_transaction(true) - - token = Base.login - + + token = Base.login + raise Exception.new('Update failed prior to saving') unless Base.make_call('update') - + sysid = Base.make_call('new_system',token) - + Base.make_call('modify_system',sysid,'name', name, token) Base.make_call('modify_system',sysid,'profile',profile,token) if profile Base.make_call('modify_system',sysid,'image', image, token) if image - + if @interfaces count = 0 @interfaces.each do |interface| - - values = interface.bundle_for_saving(count) - - unless values.empty? - Base.make_call('modify_system',sysid,'modify-interface',values,token) + + values = interface.bundle_for_saving(count) + + unless values.empty? + Base.make_call('modify_system',sysid,'modify-interface',values,token) count = count + 1 end - + end end - + Base.make_call('save_system',sysid,token) - - Base.end_transaction + + Base.end_transaction end - + private - + # Creates a new instance of +System+ from a result received from Cobbler. # def self.create(attrs) diff --git a/contrib/ruby/test/test_base.rb b/contrib/ruby/test/test_base.rb index 36d0eacf..83b767d3 100644 --- a/contrib/ruby/test/test_base.rb +++ b/contrib/ruby/test/test_base.rb @@ -1,13 +1,13 @@ # # test_system.rb - Unit tests. -# -# Copyright (C) 2008 Red Hat, Inc. +# +# Copyright (C) 2008, 2009, Red Hat, Inc. # Written by Darryl L. Pierce <dpierce@redhat.com> # # This file is part of rubygem-cobbler. # # rubygem-cobbleris free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published +# it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 2.1 of the License, or # (at your option) any later version. # @@ -19,7 +19,7 @@ # You should have received a copy of the GNU General Public License # along with rubygem-cobbler. If not, see <http://www.gnu.org/licenses/>. # - + $:.unshift File.join(File.dirname(__FILE__),'..','lib') require 'test/unit' @@ -31,72 +31,52 @@ module Cobbler def setup @connection = flexmock('connection') Base.connection = @connection - Base.hostname = "localhost" + Base.hostname = "localhost" @username = 'dpierce' @password = 'farkle' Base.username = @username Base.password = @password end - - # Ensures that the default behavior for the base is to create a connection - # if one wasn't set. - # - def test_connection_without_mock - Base.connection = nil - - assert Base.connect(true), 'Should have created a new connection.' - Base.connection = nil - - assert Base.connect(false), 'Should have created a new connection.' - end - - # Ensures that setting a mock connection works (for unit tests). - # - def test_connect - assert_same @connection, Base.connect(true), 'Got the wrong connection object.' - assert_same @connection, Base.connect(false), 'Got the wrong connection object.' - end - # Ensures that beginning a transaction results in creating a connection. # def test_begin_transaction assert_same @connection, Base.begin_transaction, 'Did not create a connection.' end - + # Ensures that a login submits the username and password to the Cobbler server. # def test_login @connection.should_receive(:call).with('login',@username, @password).once.returns(true) - + Base.login end - + # Ensures that, if no connection exists, making a call throws an exception. # def test_make_call_without_connection Base.connection = nil - + assert_raises(Exception) {Base.make_call('test')} end - + # Ensures that making a call actually sends the data. # def test_make_call @connection.should_receive(:call).with('test').once.returns('farkle') - + result = Base.make_call('test') - + assert_equal result, 'farkle', 'Did not get the expected result.' end - + # Ensures that ending a transaction closes the connection. # def test_end_transaction Base.end_transaction - + # we can't just call Base.connect since that will create a new object, - # so we'll try to send data and, if that raises an exception, we'll + # so we'll try to send data and, if that raises an exception, we'll # know indirectly that the connection object is gone. assert_raises(Exception) {Base.make_call} end diff --git a/contrib/ruby/test/test_system.rb b/contrib/ruby/test/test_system.rb index a7856bf5..727ea7ec 100644 --- a/contrib/ruby/test/test_system.rb +++ b/contrib/ruby/test/test_system.rb @@ -117,6 +117,7 @@ module Cobbler # def test_save_and_update_fails @connection.should_receive(:call).with('login',@username,@password).once.returns(@auth_token) + @connection.should_receive(:call).with('version').once.returns("1.5") @connection.should_receive(:call).with('update').once.returns{ false } system = System.new('name' => @system_name, 'profile' => @profile_name) @@ -128,6 +129,7 @@ module Cobbler # def test_save_with_profile @connection.should_receive(:call).with('login',@username,@password).once.returns(@auth_token) + @connection.should_receive(:call).with('version').once.returns("1.5") @connection.should_receive(:call).with('update').once.returns { true } @connection.should_receive(:call).with('new_system',@auth_token).once.returns(@system_id) @connection.should_receive(:call).with('modify_system',@system_id,'name',@system_name,@auth_token).once.returns(true) @@ -159,6 +161,7 @@ module Cobbler # def test_save_with_new_nics @connection.should_receive(:call).with('login',@username,@password).once.returns(@auth_token) + @connection.should_receive(:call).with('version').once.returns("1.5") @connection.should_receive(:call).with('update').once.returns { true } @connection.should_receive(:call).with('new_system',@auth_token).once.returns(@system_id) @connection.should_receive(:call).with('modify_system',@system_id,'name',@system_name,@auth_token).once.returns(true) @@ -177,6 +180,7 @@ module Cobbler # def test_remove_system @connection.should_receive(:call).with('login',@username,@password).once.returns(@auth_token) + @connection.should_receive(:call).with('version').once.returns("1.5") @connection.should_receive(:call).with('remove_system',@system_name,@auth_token).once.returns(true) system = System.new('name' => @system_name, 'profile' => @profile_name) diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 00000000..dc6e2c6a --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,6 @@ +cobbler for Debian +------------------ + +<possible notes regarding this package - if none, delete this file> + + -- root <jasper@fedoraproject.org> Wed, 11 Feb 2009 22:50:37 +0100 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 00000000..7e4a7a19 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,12 @@ +cobbler (1.5.0-1) unstable; urgency=low + + * Release bump + + -- Jasper Capel <jasper@newnewyork.nl> Tue, 24 Feb 2009 19:25:21 +0100 +cobbler (1.4.1-1) unstable; urgency=low + + * Initial release + + -- Jasper Capel <jasper@newnewyork.nl> Wed, 11 Feb 2009 22:50:37 +0100 + + diff --git a/debian/cobbler.default b/debian/cobbler.default new file mode 100644 index 00000000..2d6e68e1 --- /dev/null +++ b/debian/cobbler.default @@ -0,0 +1,10 @@ +# Defaults for cobbler initscript +# sourced by /etc/init.d/cobbler +# installed at /etc/default/cobbler by the maintainer scripts + +# +# This is a POSIX shell fragment +# + +# Additional options that are passed to the Daemon. +DAEMON_OPTS="" diff --git a/debian/compat b/debian/compat new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 00000000..f92c5a3f --- /dev/null +++ b/debian/control @@ -0,0 +1,24 @@ +Source: cobbler +Section: unknown +Priority: extra +Maintainer: Jasper Capel <jasper@newnewyork.nl> +Depends: python, apache2, libapache2-mod-python +Build-Depends: debhelper (>= 7), python-cheetah, git-core +Standards-Version: 3.8.0 +Homepage: https://fedoraproject.org/cobbler + +Package: cobbler +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, python, apache2, libapache2-mod-python +Description: Install server + Cobbler is a network install server. Cobbler + supports PXE, virtualized installs, and + reinstalling existing Linux machines. The last two + modes use a helper tool, 'koan', that + integrates with cobbler. Cobbler's advanced features + include importing distributions from DVDs and rsync + mirrors, kickstart templating, integrated yum + mirroring, and built-in DHCP/DNS Management. Cobbler has + a Python and XMLRPC API for integration with other + applications. There is also a web interface. + diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 00000000..784283b0 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,20 @@ +This package was debianized by Jasper Capel <jasper@fedoraproject.org> on +Wed, 11 Feb 2009 22:50:37 +0100. + +It was downloaded from https://fedorahosted.org/cobbler + +Upstream Author(s): + + Michael DeHaan <mdehaan@redhat.com> + +Copyright: + + Copyright (C) 2009 Red Hat, Inc + +License: + + GPLv2 + +The Debian packaging is (C) 2009, Jasper Capel <jasper@fedoraproject.org> and +is licensed under the GPL, see `/usr/share/common-licenses/GPL'. + diff --git a/debian/dirs b/debian/dirs new file mode 100644 index 00000000..ca882bbb --- /dev/null +++ b/debian/dirs @@ -0,0 +1,2 @@ +usr/bin +usr/sbin diff --git a/debian/docs b/debian/docs new file mode 100644 index 00000000..e845566c --- /dev/null +++ b/debian/docs @@ -0,0 +1 @@ +README diff --git a/debian/init.d.ex b/debian/init.d.ex new file mode 100644 index 00000000..feefae99 --- /dev/null +++ b/debian/init.d.ex @@ -0,0 +1,157 @@ +#! /bin/sh +# +# skeleton example file to build /etc/init.d/ scripts. +# This file should be used to construct scripts for /etc/init.d. +# +# Written by Miquel van Smoorenburg <miquels@cistron.nl>. +# Modified for Debian +# by Ian Murdock <imurdock@gnu.ai.mit.edu>. +# Further changes by Javier Fernandez-Sanguino <jfs@debian.org> +# +# Version: @(#)skeleton 1.9 26-Feb-2001 miquels@cistron.nl +# + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/cobbler +NAME=cobbler +DESC=cobbler + +test -x $DAEMON || exit 0 + +LOGDIR=/var/log/cobbler +PIDFILE=/var/run/$NAME.pid +DODTIME=1 # Time to wait for the server to die, in seconds + # If this value is set too low you might not + # let some servers to die gracefully and + # 'restart' will not work + +# Include cobbler defaults if available +if [ -f /etc/default/cobbler ] ; then + . /etc/default/cobbler +fi + +set -e + +running_pid() +{ + # Check if a given process pid's cmdline matches a given name + pid=$1 + name=$2 + [ -z "$pid" ] && return 1 + [ ! -d /proc/$pid ] && return 1 + cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1` + # Is this the expected child? + [ "$cmd" != "$name" ] && return 1 + return 0 +} + +running() +{ +# Check if the process is running looking at /proc +# (works for all users) + + # No pidfile, probably no daemon present + [ ! -f "$PIDFILE" ] && return 1 + # Obtain the pid and check it against the binary name + pid=`cat $PIDFILE` + running_pid $pid $DAEMON || return 1 + return 0 +} + +force_stop() { +# Forcefully kill the process + [ ! -f "$PIDFILE" ] && return + if running ; then + kill -15 $pid + # Is it really dead? + [ -n "$DODTIME" ] && sleep "$DODTIME"s + if running ; then + kill -9 $pid + [ -n "$DODTIME" ] && sleep "$DODTIME"s + if running ; then + echo "Cannot kill $LABEL (pid=$pid)!" + exit 1 + fi + fi + fi + rm -f $PIDFILE + return 0 +} + +case "$1" in + start) + echo -n "Starting $DESC: " + start-stop-daemon --start --quiet --pidfile $PIDFILE \ + --exec $DAEMON -- $DAEMON_OPTS + if running ; then + echo "$NAME." + else + echo " ERROR." + fi + ;; + stop) + echo -n "Stopping $DESC: " + start-stop-daemon --stop --quiet --pidfile $PIDFILE \ + --exec $DAEMON + echo "$NAME." + ;; + force-stop) + echo -n "Forcefully stopping $DESC: " + force_stop + if ! running ; then + echo "$NAME." + else + echo " ERROR." + fi + ;; + #reload) + # + # If the daemon can reload its config files on the fly + # for example by sending it SIGHUP, do it here. + # + # If the daemon responds to changes in its config file + # directly anyway, make this a do-nothing entry. + # + # echo "Reloading $DESC configuration files." + # start-stop-daemon --stop --signal 1 --quiet --pidfile \ + # /var/run/$NAME.pid --exec $DAEMON + #;; + force-reload) + # + # If the "reload" option is implemented, move the "force-reload" + # option to the "reload" entry above. If not, "force-reload" is + # just the same as "restart" except that it does nothing if the + # daemon isn't already running. + # check wether $DAEMON is running. If so, restart + start-stop-daemon --stop --test --quiet --pidfile \ + /var/run/$NAME.pid --exec $DAEMON \ + && $0 restart \ + || exit 0 + ;; + restart) + echo -n "Restarting $DESC: " + start-stop-daemon --stop --quiet --pidfile \ + /var/run/$NAME.pid --exec $DAEMON + [ -n "$DODTIME" ] && sleep $DODTIME + start-stop-daemon --start --quiet --pidfile \ + /var/run/$NAME.pid --exec $DAEMON -- $DAEMON_OPTS + echo "$NAME." + ;; + status) + echo -n "$LABEL is " + if running ; then + echo "running" + else + echo " not running." + exit 1 + fi + ;; + *) + N=/etc/init.d/$NAME + # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $N {start|stop|restart|force-reload|status|force-stop}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/init.d.lsb b/debian/init.d.lsb new file mode 100644 index 00000000..bd880658 --- /dev/null +++ b/debian/init.d.lsb @@ -0,0 +1,296 @@ +#!/bin/sh +# +# Example init.d script with LSB support. +# +# Please read this init.d carefully and modify the sections to +# adjust it to the program you want to run. +# +# Copyright (c) 2007 Javier Fernandez-Sanguino <jfs@debian.org> +# +# This is free software; you may 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, +# or (at your option) any later version. +# +# This 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 with +# the Debian operating system, in /usr/share/common-licenses/GPL; if +# not, write to the Free Software Foundation, Inc., 59 Temple Place, +# Suite 330, Boston, MA 02111-1307 USA +# +### BEGIN INIT INFO +# Provides: cobbler +# Required-Start: $network $local_fs +# Required-Stop: +# Should-Start: $named +# Should-Stop: +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: <Enter a short description of the sortware> +# Description: <Enter a long description of the software> +# <...> +# <...> +### END INIT INFO + +PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + +DAEMON=/usr/bin/cobblerd # Introduce the server's location here +NAME=#PACKAGE # Introduce the short server's name here +DESC=#PACKAGE # Introduce a short description here +LOGDIR=/var/log/cobbler # Log directory to use + +PIDFILE=/var/run/$NAME.pid + +test -x $DAEMON || exit 0 + +. /lib/lsb/init-functions + +# Default options, these can be overriden by the information +# at /etc/default/$NAME +DAEMON_OPTS="" # Additional options given to the server + +DIETIME=10 # Time to wait for the server to die, in seconds + # If this value is set too low you might not + # let some servers to die gracefully and + # 'restart' will not work + +#STARTTIME=2 # Time to wait for the server to start, in seconds + # If this value is set each time the server is + # started (on start or restart) the script will + # stall to try to determine if it is running + # If it is not set and the server takes time + # to setup a pid file the log message might + # be a false positive (says it did not start + # when it actually did) + +LOGFILE=$LOGDIR/$NAME.log # Server logfile +#DAEMONUSER=cobbler # Users to run the daemons as. If this value + # is set start-stop-daemon will chuid the server + +# Include defaults if available +if [ -f /etc/default/$NAME ] ; then + . /etc/default/$NAME +fi + +# Use this if you want the user to explicitly set 'RUN' in +# /etc/default/ +#if [ "x$RUN" != "xyes" ] ; then +# log_failure_msg "$NAME disabled, please adjust the configuration to your needs " +# log_failure_msg "and then set RUN to 'yes' in /etc/default/$NAME to enable it." +# exit 1 +#fi + +# Check that the user exists (if we set a user) +# Does the user exist? +if [ -n "$DAEMONUSER" ] ; then + if getent passwd | grep -q "^$DAEMONUSER:"; then + # Obtain the uid and gid + DAEMONUID=`getent passwd |grep "^$DAEMONUSER:" | awk -F : '{print $3}'` + DAEMONGID=`getent passwd |grep "^$DAEMONUSER:" | awk -F : '{print $4}'` + else + log_failure_msg "The user $DAEMONUSER, required to run $NAME does not exist." + exit 1 + fi +fi + + +set -e + +running_pid() { +# Check if a given process pid's cmdline matches a given name + pid=$1 + name=$2 + [ -z "$pid" ] && return 1 + [ ! -d /proc/$pid ] && return 1 + cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1` + # Is this the expected server + [ "$cmd" != "$name" ] && return 1 + return 0 +} + +running() { +# Check if the process is running looking at /proc +# (works for all users) + + # No pidfile, probably no daemon present + [ ! -f "$PIDFILE" ] && return 1 + pid=`cat $PIDFILE` + running_pid $pid $DAEMON || return 1 + return 0 +} + +start_server() { +# Start the process using the wrapper + if [ -z "$DAEMONUSER" ] ; then + start_daemon -p $PIDFILE $DAEMON -- $DAEMON_OPTS + errcode=$? + else +# if we are using a daemonuser then change the user id + start-stop-daemon --start --quiet --pidfile $PIDFILE \ + --chuid $DAEMONUSER \ + --exec $DAEMON -- $DAEMON_OPTS + errcode=$? + fi + return $errcode +} + +stop_server() { +# Stop the process using the wrapper + if [ -z "$DAEMONUSER" ] ; then + killproc -p $PIDFILE $DAEMON + errcode=$? + else +# if we are using a daemonuser then look for process that match + start-stop-daemon --stop --quiet --pidfile $PIDFILE \ + --user $DAEMONUSER \ + --exec $DAEMON + errcode=$? + fi + + return $errcode +} + +reload_server() { + [ ! -f "$PIDFILE" ] && return 1 + pid=pidofproc $PIDFILE # This is the daemon's pid + # Send a SIGHUP + kill -1 $pid + return $? +} + +force_stop() { +# Force the process to die killing it manually + [ ! -e "$PIDFILE" ] && return + if running ; then + kill -15 $pid + # Is it really dead? + sleep "$DIETIME"s + if running ; then + kill -9 $pid + sleep "$DIETIME"s + if running ; then + echo "Cannot kill $NAME (pid=$pid)!" + exit 1 + fi + fi + fi + rm -f $PIDFILE +} + + +case "$1" in + start) + log_daemon_msg "Starting $DESC " "$NAME" + # Check if it's running first + if running ; then + log_progress_msg "apparently already running" + log_end_msg 0 + exit 0 + fi + if start_server ; then + # NOTE: Some servers might die some time after they start, + # this code will detect this issue if STARTTIME is set + # to a reasonable value + [ -n "$STARTTIME" ] && sleep $STARTTIME # Wait some time + if running ; then + # It's ok, the server started and is running + log_end_msg 0 + else + # It is not running after we did start + log_end_msg 1 + fi + else + # Either we could not start it + log_end_msg 1 + fi + ;; + stop) + log_daemon_msg "Stopping $DESC" "$NAME" + if running ; then + # Only stop the server if we see it running + errcode=0 + stop_server || errcode=$? + log_end_msg $errcode + else + # If it's not running don't do anything + log_progress_msg "apparently not running" + log_end_msg 0 + exit 0 + fi + ;; + force-stop) + # First try to stop gracefully the program + $0 stop + if running; then + # If it's still running try to kill it more forcefully + log_daemon_msg "Stopping (force) $DESC" "$NAME" + errcode=0 + force_stop || errcode=$? + log_end_msg $errcode + fi + ;; + restart|force-reload) + log_daemon_msg "Restarting $DESC" "$NAME" + errcode=0 + stop_server || errcode=$? + # Wait some sensible amount, some server need this + [ -n "$DIETIME" ] && sleep $DIETIME + start_server || errcode=$? + [ -n "$STARTTIME" ] && sleep $STARTTIME + running || errcode=$? + log_end_msg $errcode + ;; + status) + + log_daemon_msg "Checking status of $DESC" "$NAME" + if running ; then + log_progress_msg "running" + log_end_msg 0 + else + log_progress_msg "apparently not running" + log_end_msg 1 + exit 1 + fi + ;; + # Use this if the daemon cannot reload + reload) + log_warning_msg "Reloading $NAME daemon: not implemented, as the daemon" + log_warning_msg "cannot re-read the config file (use restart)." + ;; + # And this if it cann + #reload) + # + # If the daemon can reload its config files on the fly + # for example by sending it SIGHUP, do it here. + # + # If the daemon responds to changes in its config file + # directly anyway, make this a do-nothing entry. + # + # log_daemon_msg "Reloading $DESC configuration files" "$NAME" + # if running ; then + # reload_server + # if ! running ; then + # Process died after we tried to reload + # log_progress_msg "died on reload" + # log_end_msg 1 + # exit 1 + # fi + # else + # log_progress_msg "server is not running" + # log_end_msg 1 + # exit 1 + # fi + #;; + + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|force-stop|restart|force-reload|status}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/debian/rules b/debian/rules new file mode 100755 index 00000000..c2a4750e --- /dev/null +++ b/debian/rules @@ -0,0 +1,92 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + + + + + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + + # Add here commands to compile the package. + $(MAKE) + #docbook-to-man debian/cobbler.sgml > cobbler.1 + + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + + # Add here commands to clean up after the build process. + $(MAKE) clean + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Add here commands to install the package into debian/cobbler. + $(MAKE) debinstall DESTDIR=$(CURDIR)/debian/cobbler + + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + dh_testdir + dh_testroot + dh_installchangelogs CHANGELOG + dh_installdocs + dh_installexamples +# dh_install +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime + dh_pysupport + dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +# Build architecture-dependent files here. +binary-arch: build install + + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/debian/watch.ex b/debian/watch.ex new file mode 100644 index 00000000..efec7b44 --- /dev/null +++ b/debian/watch.ex @@ -0,0 +1,23 @@ +# Example watch control file for uscan +# Rename this file to "watch" and then you can run the "uscan" command +# to check for upstream updates and more. +# See uscan(1) for format + +# Compulsory line, this is a version 3 file +version=3 + +# Uncomment to examine a Webpage +# <Webpage URL> <string match> +#http://www.example.com/downloads.php cobbler-(.*)\.tar\.gz + +# Uncomment to examine a Webserver directory +#http://www.example.com/pub/cobbler-(.*)\.tar\.gz + +# Uncommment to examine a FTP server +#ftp://ftp.example.com/pub/cobbler-(.*)\.tar\.gz debian uupdate + +# Uncomment to find new files on sourceforge, for devscripts >= 2.9 +# http://sf.net/cobbler/cobbler-(.*)\.tar\.gz + +# Uncomment to find new files on GooglePages +# http://example.googlepages.com/foo.html cobbler-(.*)\.tar\.gz diff --git a/diagrams/code.dia b/diagrams/code.dia Binary files differnew file mode 100644 index 00000000..2cfe5a42 --- /dev/null +++ b/diagrams/code.dia diff --git a/diagrams/object_tree.dia b/diagrams/object_tree.dia Binary files differnew file mode 100644 index 00000000..b057cf8c --- /dev/null +++ b/diagrams/object_tree.dia diff --git a/diagrams/replication.png b/diagrams/replication.png Binary files differnew file mode 100644 index 00000000..930a9050 --- /dev/null +++ b/diagrams/replication.png diff --git a/docs/cobbler.pod b/docs/cobbler.pod index b7361c25..a46ef1b9 100644 --- a/docs/cobbler.pod +++ b/docs/cobbler.pod @@ -127,7 +127,7 @@ provisioning guests with koan. The valid options for --os-version vary dependin =item owners -Users with small sites and a limited number of admins can probably ignore this option. All cobbler objects (distros, profiles, systems, and repos) can take a --owners parameter to specify what cobbler users can edit particular objects. This only applies to the Cobbler WebUI and XMLRPC interface, not the "cobbler" command line tool run from the shell. Furthermore, this is only respected by the "authz_ownership" module which must be enabled in /etc/cobbler/modules.conf. The value for --owners is a comma seperated list of users and groups as specified in /etc/cobbler/users.conf. For more information see the users.conf file as well as the Cobbler Wiki. In the default Cobbler configuration, this value is completely ignored, as is users.conf. +Users with small sites and a limited number of admins can probably ignore this option. All cobbler objects (distros, profiles, systems, and repos) can take a --owners parameter to specify what cobbler users can edit particular objects. This only applies to the Cobbler WebUI and XMLRPC interface, not the "cobbler" command line tool run from the shell. Furthermore, this is only respected by the "authz_ownership" module which must be enabled in /etc/cobbler/modules.conf. The value for --owners is a space seperated list of users and groups as specified in /etc/cobbler/users.conf. For more information see the users.conf file as well as the Cobbler Wiki. In the default Cobbler configuration, this value is completely ignored, as is users.conf. =item template-files @@ -167,12 +167,12 @@ When using kickstart files, they can be placed anywhere on the filesystem, but t =item nameservers -If your nameservers are not provided by DHCP, you can specify a comma-seperated list of addresses here to configure each of the installed nodes to use them (provided the kickstarts used are installed on a per-system basis). Users with DHCP setups should not need to use this option. This is available to set in profiles to avoid having to set it repeatedly for each system record. +If your nameservers are not provided by DHCP, you can specify a space seperated list of addresses here to configure each of the installed nodes to use them (provided the kickstarts used are installed on a per-system basis). Users with DHCP setups should not need to use this option. This is available to set in profiles to avoid having to set it repeatedly for each system record. =item virt-file-size (Virt-only) How large the disk image should be in Gigabytes. The default is "5". -This can be a comma seperated list (ex: "5,6,7") to allow for multiple disks of different sizes +This can be a space seperated list (ex: "5,6,7") to allow for multiple disks of different sizes depending on what is given to --virt-path. This should be input as a integer or decimal value without units. =item virt-ram @@ -368,7 +368,7 @@ Example: cobbler system report --name=foo -=end +=back =head2 REPOSITORIES @@ -708,7 +708,7 @@ If the avahi-tools package is installed, cobblerd will broadcast it's presence o =head2 IMPORTING TREES Cobbler can auto-add distributions and profiles from remote sources, whether this is a filesystem path or an rsync mirror. This can save a lot of time when setting up a new provisioning environment. Import is a feature that many users will want to take advantage of, and is very simple to use. - + After an import is run, cobbler will try to detect the distribution type and automatically assign kickstarts. By default, it will provision the system by erasing the hard drive, setting up eth0 for dhcp, and using a default password of "cobbler". If this is undesirable, edit the kickstart files in /etc/cobbler to do something else or change the kickstart setting after cobbler creates the profile. Mirrored content is saved automatically in /var/www/cobbler/ks_mirror. diff --git a/installer_templates/defaults b/installer_templates/defaults index 8a5eebad..3da44762 100644 --- a/installer_templates/defaults +++ b/installer_templates/defaults @@ -1,13 +1,13 @@ -authn_module:'authn_denyall' -authz_module:'authz_allowall' -dns_module:'manage_bind' -dhcp_module:'manage_isc' -enable_dhcp:0 -enable_dns:0 -next_server:'127.0.0.1' -pxe_once:0 +authn_module: 'authn_denyall' +authz_module: 'authz_allowall' +dns_module: 'manage_bind' +dhcp_module: 'manage_isc' +enable_dhcp: 0 +enable_dns: 0 +next_server: '127.0.0.1' +pxe_once: 0 redhat_management_type: "off" redhat_management_server: "xmlrpc.rhn.redhat.com" redhat_management_key: "" -server:'127.0.0.1' -yum_post_install_mirror:1 +server: '127.0.0.1' +yum_post_install_mirror: 1 diff --git a/installer_templates/settings.template b/installer_templates/settings.template index 9b27dcc0..d97f32c8 100644 --- a/installer_templates/settings.template +++ b/installer_templates/settings.template @@ -25,6 +25,19 @@ allow_duplicate_macs: 0 # the path to BIND's executable for this distribution. bind_bin: /usr/sbin/named +# Email out a report when cobbler finishes installing a system. +# enabled: set to 1 to turn this feature on +# sender: optional +# email: which addresses to email +# smtp_server: used to specify another server for an MTA +# subject: use the default subject unless overridden + +build_reporting_enabled: 0 +build_reporting_sender: "" +build_reporting_email: [ 'root@localhost' ] +build_reporting_smtp_server: "localhost" +build_reporting_subject: "" + # Cheetah-language kickstart templates can import Python modules. # while this is a useful feature, it is not safe to allow them to # import anything they want. This whitelists which modules can be @@ -125,6 +138,17 @@ kernel_options: lang: ' ' text: ~ +# s390 systems require additional kernel options in addition to the +# above defaults + +kernel_options_s390x: + RUNKS: 1 + ramdisk_size: 40000 + root: /dev/ram0 + ro: ~ + ip: off + vnc: ~ + # configuration options if using the authn_ldap module. See the # the Wiki for details. This can be ignored if you are not using # LDAP for WebUI/XMLRPC authentication. @@ -221,11 +245,18 @@ pxe_template_dir: "/etc/cobbler/pxe" # "site" : I'm using Red Hat Satellite Server or Spacewalk # # You will also want to read: https://fedorahosted.org/cobbler/wiki/TipsForRhn + redhat_management_type: "$redhat_management_type" # if redhat_management_type is enabled, choose your server # "management.example.org" : For Satellite or Spacewalk # "xmlrpc.rhn.redhat.com" : For Red Hat Network +# +# This setting is also used by the code that supports using Spacewalk/Satellite users/passwords +# within Cobbler Web and Cobbler XMLRPC. Using RHN Hosted for this is not supported. +# This feature can be used even if redhat_management_type is off, you just have +# to have authn_spacewalk selected in modules.conf + redhat_management_server: "$redhat_management_server" # specify the default Red Hat authorization key to use to register @@ -234,6 +265,22 @@ redhat_management_server: "$redhat_management_server" # keep it from trying to register. redhat_management_key: "" +# if using authn_spacewalk in modules.conf to let cobbler authenticate +# against Satellite/Spacewalk's auth system, by default it will not allow per user +# access into Cobbler Web and Cobbler XMLRPC. +# +# in order to permit this, the following setting must be enabled HOWEVER +# doing so will permit all Spacewalk/Satellite users of certain types to edit all +# of cobbler's configuration. +# +# these roles are: config_admin and org_admin +# +# users should turn this on only if they want this behavior and +# do not have a cross-multi-org seperation concern. If you have +# a single org in your satellite, it's probably safe to turn this +# on and then you can use CobblerWeb alongside a Satellite install. +redhat_management_permissive: 0 + # when DHCP and DNS management are enabled, cobbler sync can automatically # restart those services to apply changes. The exception for this is # if using ISC for DHCP, then omapi eliminates the need for a restart. @@ -249,12 +296,10 @@ redhat_management_key: "" restart_dns: 1 restart_dhcp: 1 -# if set to 1, new systems doing profile based installations will -# contact cobbler to have system records created for them containing -# the mac address information that they have requested for install. -# this effectively allows for registration of new hardware via PXE -# without having to manually enter in all of the mac addresses for -# every machine on your network +# if set to 1, allows /usr/bin/cobbler-register (part of the koan package) +# to be used to remotely add new cobbler system records to cobbler. +# this effectively allows for registration of new hardware from system +# records. register_new_installs: 0 # install triggers are scripts in /var/lib/cobbler/triggers/install @@ -279,12 +324,14 @@ server: $server # this directory should not be required. snippetsdir: /var/lib/cobbler/snippets -# by default, installs are set to send syslog traffic on this port -# and cobblerd will listen on this port. syslog data (for installs -# that support it... RHEL 5 and later, etc) is logged in /var/log/cobbler -# and can be used to help debug problematic installations. Syslog -# is UDP and may not be available depending on network/firewall configuration. -syslog_port: 25150 +# by default, installs are *not* set to send installation logs to the cobbler +# server. With 'anamon_enabled', kickstart templates may use the pre_anamon +# snippet to allow remote live monitoring of their installations from the +# cobbler server. Installation logs will be stored under +# /var/log/cobbler/anamon/. NOTE: This does allow an xmlrpc call to send logs +# to this directory, without authentication, so enable only if you are +# ok with this limitation. +anamon_enabled: 0 # locations of the TFTP binary and config file tftpd_bin: /usr/sbin/in.tftpd @@ -300,16 +347,6 @@ webdir: /var/www/cobbler # port option to koan if it is not the default. xmlrpc_port: 25151 -# cobbler's read write XMLRPC is the version of XMLRPC -# used by the WebUI and some features like system registration. -# XMLRPC connections here require login information to access. -# this feature can be disabled to gain increased security but -# will disable the WebUI, registration, and potentially other -# cobbler features. Most users should leave XMLRPC RW -# enabled. The port can be relocated if needed. -xmlrpc_rw_enabled: 1 -xmlrpc_rw_port: 25152 - # "cobbler repo add" commands set cobbler up with repository # information that can be used during kickstart and is automatically # set up in the cobbler kickstart templates. By default, these diff --git a/kickstarts/legacy.ks b/kickstarts/legacy.ks index d9c5d847..70918985 100644 --- a/kickstarts/legacy.ks +++ b/kickstarts/legacy.ks @@ -40,15 +40,21 @@ autopart %pre $kickstart_start $SNIPPET('pre_install_network_config') +$SNIPPET('pre_anamon') %packages %post +# Begin yum configuration $yum_config_stanza +# End yum configuration $SNIPPET('post_install_kernel_options') $SNIPPET('post_install_network_config') $SNIPPET('download_config_files') $SNIPPET('koan_environment') $SNIPPET('redhat_register') +$SNIPPET('cobbler_register') +# Begin final steps $kickstart_done +# End final steps diff --git a/kickstarts/sample.ks b/kickstarts/sample.ks index 83fa4695..f41470d9 100644 --- a/kickstarts/sample.ks +++ b/kickstarts/sample.ks @@ -43,18 +43,26 @@ autopart %pre $kickstart_start $SNIPPET('pre_install_network_config') +# Enable installation monitoring +$SNIPPET('pre_anamon') %packages $SNIPPET('func_install_if_enabled') %post +# Start yum configuration $yum_config_stanza +# End yum configuration $SNIPPET('post_install_kernel_options') $SNIPPET('post_install_network_config') $SNIPPET('func_register_if_enabled') $SNIPPET('download_config_files') $SNIPPET('koan_environment') $SNIPPET('redhat_register') +$SNIPPET('cobbler_register') +# Enable post-install boot notification +$SNIPPET('post_anamon') +# Start final steps $kickstart_done - +# End final steps diff --git a/kickstarts/sample_end.ks b/kickstarts/sample_end.ks index 7c1d4353..21148366 100644 --- a/kickstarts/sample_end.ks +++ b/kickstarts/sample_end.ks @@ -44,8 +44,10 @@ zerombr autopart %pre -$SNIPPET('pre_install_network_config') $kickstart_start +$SNIPPET('pre_install_network_config') +# Enable installation monitoring +$SNIPPET('pre_anamon') %end %packages @@ -53,12 +55,19 @@ $SNIPPET('func_install_if_enabled') %end %post +# Start yum configuration $yum_config_stanza +# End yum configuration $SNIPPET('post_install_kernel_options') $SNIPPET('post_install_network_config') $SNIPPET('func_register_if_enabled') $SNIPPET('download_config_files') $SNIPPET('koan_environment') -$kickstart_done $SNIPPET('redhat_register') +$SNIPPET('cobbler_register') +# Enable post-install boot notification +$SNIPPET('post_anamon') +# Start final steps +$kickstart_done +# End final steps %end diff --git a/loaders/COPYING.yaboot b/loaders/COPYING.yaboot new file mode 100644 index 00000000..7a072bb1 --- /dev/null +++ b/loaders/COPYING.yaboot @@ -0,0 +1,15 @@ +Cobbler deploys a yaboot binary over TFTP to enable network booting of PowerPC +machines. + +Yaboot is GPL and is being distributed in binary form persuant to this section +of the GPL license: + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +Source code to yaboot is available on http://yaboot.ozlabs.org. + +A full copy of the GPL is available in "COPYING". diff --git a/loaders/README.yaboot b/loaders/README.yaboot new file mode 100644 index 00000000..e619eb5f --- /dev/null +++ b/loaders/README.yaboot @@ -0,0 +1,271 @@ +Yaboot -- PowerPC GNU/Linux OpenFirmware bootloader +------------------------------------------------------------------- + +Please read the "COPYING" file for licence informations. + +------------------------------------------------------------------- + +Yaboot is an OpenFirmware bootloader for Open Firmware based +machines. It is known to work on "NewWorld" class powermacs (iMac and +all machines released after it), RS/6000, and possibly other OF based +CHRP machines. "OldWorld" PowerMacs (with the built-in MacOS ROM) +are not supported. + +This README serves as a quick-start introduction to yaboot and its +installation utilities. For a more complete usage and installation +guide, see yaboot-howto.html/index.en.html in the same folder where +this README is located, or else inside the doc folder in this +directory. For documentation about all the options available, see the +man pages (bootstrap, yaboot, yaboot.conf, ybin, mkofboot, ofpath, +yabootconfig). + +Mailing lists +------------------------------------------------------------------- +The following mailing lists exist for yaboot: + +yaboot-users User related questions/discussion regarding yaboot +yaboot-devel Technical and development discussion regarding yaboot + +Please see https://ozlabs.org/mailman/listinfo/LISTNAME where LISTNAME is one +of the above named lists. + +http://yaboot.ozlabs.org/ + +------------------------------------------------------------------- +Bootloader Installation Utilities +ybin, mkofboot, ofpath, yabootconfig +Written by Ethan Benson <erbenson@alaska.net> +------------------------------------------------------------------- + +ybin (YaBoot INstaller) and mkofboot +------------------------------------------------------------------- + +ybin is a lilo/quik style bootloader installer for PowerPC based +machines which require a bootstrap partition rather than a traditional +bootblock (i.e. all `NewWorld' Macintoshes). When ybin is configured +correctly you can simply type ybin at the command line as root, and +the bootloader and its configuration file will be installed or updated +on the bootstrap partition without any further user intervention. + +ybin also supports IBM PowerPC hardware which requires a slightly different +bootstrap partition setup. For these machines, ybin uses dd to write yaboot +directly to the partition instead of copying it to a filesystem on the +partition. ybin will add the requisite .note section to yaboot prior to +installation (IBM CHRP only). + +mkofboot is a companion script (actually a symlink to ybin) which +initializes the bootstrap partition prior to running ybin to install +the bootloader on it. mkofboot will confirm you want to continue +before proceeding unless called with the -f or --force switch. This +is in contrast to ybin, which is non destructive except that it +overwrites any existing yaboot files (yaboot and yaboot.conf) at the +root level of the bootstrap filesystem. + +mkofboot and ybin both refer to the same configuration file (normally +yaboot.conf) to determine where the bootstrap files will be placed. + +IMPORTANT: The bootstrap partition should never be mounted anywhere on +your filesystem, ybin and mkofboot will check if it is and refuse to +operate on it if it is mounted. It is not necessary to keep anything +but the boot loader on the bootstrap partition, yaboot will load the +kernel from your root partition (which can be an ext2/3, XFS, or +ReiserFS filesystem). Do not mount the bootstrap partition on top of +/boot. + +ybin can update a bootstrap filesystem either on a block device or in +an ordinary file (as in an image of a filesystem.) + +NOTE: You must have a secure mktemp program otherwise ybin will be +vulnerable to race conditions. Debian's mktemp qualifies I don't know +about the other distributions, you have been warned. The temp file is +created in /tmp by default but ybin will respect the $TMPDIR +environment variable. + + +ofpath +------------------------------------------------------------------- + +The included ofpath utility can usually determine the OpenFirmware +device path that corresponds with a unix device node in /dev/. Ybin +uses this utility to find the path to the bootstrap partition and to +any defined macos/macosx partitions. ofpath is based on the utility +`show_of_path.sh' written by Olaf Hering. + + +NOTE: ofpath may not work with all SCSI cards/drivers. + +IMPORTANT: ofpath will NOT work on NewWorld Powermacs if the machine +was booted with BootX. + + +yabootconfig +------------------------------------------------------------------- + +Yabootconfig creates a default configuration file and then runs mkofboot to +complete the bootloader installation. yabootconfig reads the running +system's /etc/fstab to determine the kernel location, and detects the +location of the 800k Apple_Bootstrap partition. It will also find IBM +CHRP bootstrap partitions (type 0x41 PReP Boot). + + + +Yaboot.conf Configuration File +------------------------------------------------------------------- + +yaboot.conf configuration file settings are covered in detail in `man +yaboot.conf'. Avoid the use of spaces in the config file, except in +comment lines or inside quotes. Here are the essential settings. + + +boot= +Example: boot=/dev/hda2 + +This required setting defines the bootstrap partition device. It can +also be a regular file if you are creating a filesystem image for some +reason. The default config file has this set to ``unconfigured'' which +will cause ybin to complain about you not reading the docs, it is the +only option you should need to change for ybin to work. Be sure you +include the partition number at the end, it should never be something +like /dev/hda. + + +install= +Example: install=/usr/lib/yaboot/yaboot or /usr/local/lib/yaboot/yaboot + +The full pathname to the yaboot OpenFirmware executable file. The +default is shown above. This file will be copied to the root level of +the bootstrap partition. Note: If you are installing an alternative +bootloader, its filename will be changed to yaboot when it is copied +(to match what the first stage bootloader expects to be loading). + + +partition= +Example: partition=3 + +The partition number on which the kernel image is located. By default, +this partition is assumed to be on the same device from which yaboot +was loaded. If needed, device= can be used to explicitly specify the +device. + + +image= +Examples: image=/vmlinux or image=/boot/vmlinux-2.2.19-pmac + +The path to the image from the root level of the partition (remember +that yaboot is unaware of mountpoints). Don't forget to include the +leading slash when specifying the image path (image=vmlinux will +probably fail). + + +root= +Example: root=/dev/hda3 + +The Linux device name for the root partition. This parameter is +passed to the kernel when it starts up to let it know where its root +filesystem is located. Refer to the yaboot.conf man page for details +on kernel image options such as append=, initrd=, and initrd-size=. + + +magicboot= (same as -m or --magicboot) +Example: /usr/local/lib/yaboot/ofboot + +Identifies the first stage loader. The example shown is the default. The +ofboot script it refers to is included with and configured automatically by +ybin from options in /etc/yaboot.conf. The magicboot= can be the full +pathname to any OF CHRP script file. Since some newer OpenFirmware +implementations appear to require a CHRP script, the magicboot= setting is +highly recommended. If you don't include magicboot=, then yaboot itself will +be the active bootfile. magicboot= cannot be used on IBM CHRP. + + +Boot Menu Options +------------------------------------------------------------------- + +The following options work only if you have specified the magicboot= option. +Each option specifies an OpenFirmware or unix device path to another +operating system's boot partition. When you define one of these options you +will be presented with a simple menu at bootup allowing you to hit `l' to +boot GNU/Linux or another letter to boot the other OS (see below). These +letters are typed without a shift key (the boot menu is case sensitive). +When set to a unix device node such as /dev/hda11 then ybin will use the +ofpath utility to determine the OpenFirmware device path. + + Option Letter Operating System Type + --------- ------ --------------------------------------------- + bsd= b NetBSD or OpenBSD root partition (you must + have the BSD ofwboot bootloader + installed at /usr/local/lib/yaboot/ofwboot) + + macos= m MacOS 8.* or 9.* bootable partition + + macosx= x MacOS X boot partition (also see brokenosx) + + darwin= d Darwin boot partition + + enablecdboot c Boot from the CDROM drive + + enablenetboot n Boot from the network + + enableofboot o Display an OpenFirmware prompt + + defaultos= The default OS to load (linux, bsd, macos, + darwin or macosx. + +brokenosx + +This option causes the menu entry for MacOSX to execute +\System\Library\CoreServices\BootX from the macosx=device instead of +the usual \\:tbxi. This is necessary if OSX is installed onto an HFS+ +filesystem instead of UFS. When OSX is installed on an HFS+ filesystem +MacOS will mount and debless the OSX partition. Add this option if +the OSX menu entry breaks after booting MacOS. You should not use +this option if OSX is installed on a UFS filesystem, for UFS installs +you specify the OSX bootstrap partition which is protected against +MacOS. + +delay= +Example: delay=5 + +The time in seconds that the first stage ofboot loader will wait for +you to choose a letter before booting the default OS defined in +defaultos=. If not set, the value of timeout= (converted to seconds) +will be used. + + +Colors +------------------------------------------------------------------- + +fgcolor=string + +Specifies the foreground (text) color used by yaboot and the +multiboot menu. Available colors are: black, blue, light-blue, green, +light-green, cyan, light-cyan, red, light-red, purple, light- purple, +brown, light-gray, dark-gray, yellow, and white. The default is +white. + +bgcolor=string + +Specifies the background color used by yaboot and the multiboot +menu. Available colors are the same as fgcolor. The default is +black. + + +=========================================================================== + +Copyright (C) 2000, 2001, 2002, 2003 Ethan Benson + +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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +=========================================================================== diff --git a/loaders/zpxe.rexx b/loaders/zpxe.rexx new file mode 100644 index 00000000..b187b2e2 --- /dev/null +++ b/loaders/zpxe.rexx @@ -0,0 +1,291 @@ +/* zPXE: REXX PXE Client for System z + +zPXE is a PXE client used with Cobbler. It must be run under +z/VM. zPXE uses TFTP to first download a list of profiles, +then a specific kernel, initial RAMdisk, and PARM file. These +files are then punched to start the install process. + +zPXE does not require a writeable 191 A disk. Files are +downloaded to a temporary disk (VDISK). + +zPXE can also IPL a DASD disk by default. You can specify the +default dasd in ZPXE CONF, as well as the hostname of the Cobbler +server. +--- + +Copyright 2006-2009, Red Hat, Inc +Brad Hinson <bhinson@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 +*/ + + +/* Defaults */ + +server = '' /* define server in ZPXE CONF */ +iplDisk = 100 /* overridden by value in ZPXE CONF */ +profilelist = PROFILE LIST T /* VDISK will be defined as T later */ +profiledetail = PROFILE DETAIL T +zpxeparm = ZPXE PARM T +zpxeconf = ZPXE CONF T +config = ZPXE CONF + +/* For translating strings to lowercase */ +upper = xrange('A', 'Z') +lower = xrange('a', 'z') + +/* Useful settings normally found in PROFILE EXEC */ +'cp set run on' +'cp set pf11 retrieve forward' +'cp set pf12 retrieve' + +/* Check for config file */ +if lines(config) > 0 then do + inputline = linein(config) /* first line is server hostname/IP */ + parse var inputline . server . + inputline = linein(config) /* second line is DASD disk to IPL */ + parse var inputline . iplDisk . +end + +/* Define temporary disk (VDISK) to store files */ +'detach ffff' /* detach ffff if present */ +'define vfb-512 as ffff blk 100000' /* 512 byte block size =~ 50 MB */ +queue '1' +queue 'tmpdsk' +'format ffff t' /* format VDISK as file mode t */ + +/* Link TCPMAINT disk for access to TFTP */ +'link tcpmaint 592 592 rr' +'access 592 e' + +/* Query user ID. This is used later to determine: + 1. Whether a user-specific PXE profile exists. + 2. Whether user is disconnected. If so, IPL the default disk. +*/ +'pipe cp query' userid() '| var user' +parse value user with id . dsc . +userid = translate(id, lower, upper) + +/* Check whether a user-specific PXE profile exists. + If so, proceed with this. Otherwise, continue and + show the system-wide profile menu. +*/ +call GetTFTP '/s390x/s_'userid 'profile.detail.t' + +if lines(profiledetail) > 0 then do + + /* Get user PARM and CONF containing network info */ + call GetTFTP '/s390x/s_'userid'_parm' 'zpxe.parm.t' + call GetTFTP '/s390x/s_'userid'_conf' 'zpxe.conf.t' + + vmfclear /* clear screen */ + call CheckServer /* print server name */ + say 'Profile 'userid' found' + say '' + + bootRc = ParseSystemRecord() /* parse file for boot action */ + if bootRc = 0 then + 'cp ipl' iplDisk /* boot default DASD */ + else do + call DownloadBinaries /* download kernel and initrd */ + say 'Starting install...' + say '' + call PunchFiles /* punch files to begin install */ + exit + end /* if bootRc = 0 */ + +end /* if user-specific profile found */ + + +/* Download initial profile list */ +call GetTFTP '/s390x/profile_list' 'profile.list.t' + +vmfclear /* clear screen */ +call CheckServer /* print server name */ + +say 'zPXE MENU' /* show menu */ +say '---------' + +count = 0 +do while lines(profilelist) > 0 /* display one profile per line */ + count = count + 1 + inputline = linein(profilelist) + parse var inputline profile.count + say count'. 'profile.count +end + +if (count = 0) then + say '** Error connecting to server: no profiles found **' + +count = count + 1 +say count'. Exit to CMS shell [IPL CMS]' +say '' +say '' +say 'Enter Choice -->' +say 'or press <Enter> to boot from disk [DASD 'iplDisk']' + +/* Check if user is disconnected, indicating + logon by XAUTOLOG. In this case, IPL the + default disk. +*/ +if (dsc = 'DSC') then do /* user is disconnected */ + say 'User disconnected. Booting from DASD 'iplDisk'...' + 'cp ipl' iplDisk + end +else do /* user is interactive -> prompt */ + parse upper pull answer . + select + when (answer = count) + then do + say 'Exiting to CMS shell...' + exit + end + when (answer = '') /* IPL by default */ + then do + say 'Booting from DASD 'iplDisk'...' + 'cp ipl' iplDisk + end + when (answer < 0) | (answer > count) /* invalid respone */ + then do + say 'Invalid choice, exiting to CMS shell.' + exit + end + when (answer > 0) & (answer < count) /* valid response */ + then do + call GetTFTP '/s390x/p_'profile.answer 'profile.detail.t' + + /* get profile-based PARM and CONF files */ + call GetTFTP '/s390x/p_'profile.answer'_parm' 'zpxe.parm.t' + call GetTFTP '/s390x/p_'profile.answer'_conf' 'zpxe.conf.t' + + vmfclear /* clear screen */ + say 'Using profile 'answer' ['profile.answer']' + say '' + call DownloadBinaries /* download kernel and initrd */ + + say 'Starting install...' + say '' + + call PunchFiles + + end /* valid answer */ + otherwise + say 'Invalid choice, exiting to CMS shell.' + exit + end /* Select */ +end +exit + + +/* Procedure CheckServer + Print error message if server is not defined. Otherwise + show server name +*/ +CheckServer: + + if server = '' then + say '** Error: No host defined in ZPXE.CONF **' + else say 'Connected to server 'server + say '' + +return 0 /* CheckServer */ + + +/* Procedure GetTFTP + Use CMS TFTP client to download files + path: remote file location + filename: local file name + transfermode [optional]: 'ascii' or 'octet' +*/ +GetTFTP: + + parse arg path filename transfermode + + if transfermode <> '' then + queue 'mode' transfermode + queue 'get 'path filename + queue 'quit' + + 'set cmstype ht' /* suppress tftp output */ + tftp server + 'set cmstype rt' + +return 0 /* GetTFTP */ + + +/* Procedure DownloadBinaries + Download kernel and initial RAMdisk. Convert both + to fixed record length 80. +*/ +DownloadBinaries: + + inputline = linein(profiledetail) /* first line is kernel */ + parse var inputline kernelpath + say 'Downloading kernel ['kernelpath']...' + call GetTFTP kernelpath 'kernel.img.t' octet + + inputline = linein(profiledetail) /* second line is initrd */ + parse var inputline initrdpath + say 'Downloading initrd ['initrdpath']...' + call GetTFTP initrdpath 'initrd.img.t' octet + + inputline = linein(profiledetail) /* third line is ks kernel arg */ + parse var inputline ksline + call lineout zpxeparm, ksline /* add ks line to end of parm */ + call lineout zpxeparm /* close file */ + + /* convert to fixed record length */ + 'pipe < KERNEL IMG T | fblock 80 00 | > KERNEL IMG T' + 'pipe < INITRD IMG T | fblock 80 00 | > INITRD IMG T' + +return 0 /* DownloadBinaries */ + + +/* Procedure PunchFiles + Punch the kernel, initial RAMdisk, and PARM file. + Then IPL to start the install process. +*/ +PunchFiles: + + 'spool punch *' + 'close reader' + 'purge reader all' /* clear reader contents */ + 'punch kernel img t (noh' /* punch kernel */ + 'punch zpxe parm t (noh' /* punch PARM file */ + 'punch initrd img t (noh' /* punch initrd */ + 'change reader all keep' /* keep files in reader */ + 'ipl 00c clear' /* IPL the reader */ + +return 0 /* PunchFiles */ + + +/* Procedure ParseSystemRecord + Open system record file to look for local boot flag. + Return 0 if local flag found (guest will IPL default DASD). + Return 1 otherwise (guest will download kernel/initrd and install). +*/ +ParseSystemRecord: + + inputline = linein(profiledetail) /* get first line */ + parse var inputline systemaction . + call lineout profiledetail /* close file */ + + if systemaction = 'local' then + return 0 + else + return 1 + +/* End ParseSystemRecord */ diff --git a/pres/Slidy/bullet-fold-dim.gif b/pres/Slidy/bullet-fold-dim.gif Binary files differnew file mode 100644 index 00000000..bce1a2a1 --- /dev/null +++ b/pres/Slidy/bullet-fold-dim.gif diff --git a/pres/Slidy/bullet-fold.gif b/pres/Slidy/bullet-fold.gif Binary files differnew file mode 100644 index 00000000..d4b063c9 --- /dev/null +++ b/pres/Slidy/bullet-fold.gif diff --git a/pres/Slidy/bullet-nofold-dim.gif b/pres/Slidy/bullet-nofold-dim.gif Binary files differnew file mode 100644 index 00000000..98a4c39f --- /dev/null +++ b/pres/Slidy/bullet-nofold-dim.gif diff --git a/pres/Slidy/bullet-nofold.gif b/pres/Slidy/bullet-nofold.gif Binary files differnew file mode 100644 index 00000000..76102a37 --- /dev/null +++ b/pres/Slidy/bullet-nofold.gif diff --git a/pres/Slidy/bullet-unfold-dim.gif b/pres/Slidy/bullet-unfold-dim.gif Binary files differnew file mode 100644 index 00000000..b758cbed --- /dev/null +++ b/pres/Slidy/bullet-unfold-dim.gif diff --git a/pres/Slidy/bullet-unfold.gif b/pres/Slidy/bullet-unfold.gif Binary files differnew file mode 100644 index 00000000..e5ecd5ba --- /dev/null +++ b/pres/Slidy/bullet-unfold.gif diff --git a/pres/Slidy/bullet.png b/pres/Slidy/bullet.png Binary files differnew file mode 100644 index 00000000..14ebd951 --- /dev/null +++ b/pres/Slidy/bullet.png diff --git a/pres/Slidy/cobbler-logo.png b/pres/Slidy/cobbler-logo.png Binary files differnew file mode 100644 index 00000000..f8fe53ca --- /dev/null +++ b/pres/Slidy/cobbler-logo.png diff --git a/pres/Slidy/code.png b/pres/Slidy/code.png Binary files differnew file mode 100644 index 00000000..e6a7e1e6 --- /dev/null +++ b/pres/Slidy/code.png diff --git a/pres/Slidy/example.png b/pres/Slidy/example.png Binary files differnew file mode 100644 index 00000000..7ce9b3ff --- /dev/null +++ b/pres/Slidy/example.png diff --git a/pres/Slidy/example.svg b/pres/Slidy/example.svg new file mode 100644 index 00000000..581358e4 --- /dev/null +++ b/pres/Slidy/example.svg @@ -0,0 +1,223 @@ +<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 11.0, SVG Export Plug-In -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
+ viewBox="-0.724 -0.46 279 52" xml:space="preserve"> + <desc>W3C Indian Office logo</desc>
+ <defs>
+ </defs>
+ <g>
+ <rect x="107.669" y="15.986" style="fill:#0C479D" width="163.338" height="13.73"/>
+ <path style="fill:#FFFFFF" d="M117.042,25.062c-0.6,0.853-1.279,1.812-2.692,1.812c-0.879,0-1.985-0.574-1.985-2.052
+ c0-2.039,1.919-4.277,3.758-4.277c1.053,0,1.532,0.586,1.532,1.172c0,0.6-0.333,0.96-0.839,0.96c-0.36,0-0.759-0.239-0.759-0.693
+ c0-0.372,0.28-0.586,0.28-0.772c0-0.174-0.187-0.227-0.307-0.227c-1.16,0-1.959,2.799-1.959,3.745
+ c0,1.065,0.587,1.293,0.973,1.293c0.6,0,1.093-0.428,1.626-1.201L117.042,25.062z"/>
+ <path style="fill:#FFFFFF" d="M134.227,22.596c0,1.985-1.652,4.278-3.784,4.278c-1.093,0-2.132-0.705-2.132-1.986
+ c0-2.053,1.719-4.344,3.825-4.344C132.641,20.544,134.227,20.81,134.227,22.596z M129.896,25.554c0,0.268,0,0.934,0.693,0.934
+ c1.279,0,2.052-3.705,2.052-4.705c0-0.666-0.293-0.852-0.64-0.852C130.562,20.931,129.896,24.702,129.896,25.554z"/>
+ <path style="fill:#FFFFFF" d="M151.587,25.076c-0.426,0.718-1.053,1.746-2.092,1.746c-0.213,0-0.934,0-0.934-0.867
+ c0-0.946,1.174-3.864,1.174-4.157c0-0.161-0.16-0.294-0.334-0.294c-0.333,0-1.679,0.666-2.852,5.197h-1.612
+ c0.679-2.544,1.359-4.744,1.359-5.077c0-0.347-0.36-0.347-0.72-0.347v-0.36c0.293,0,1.612-0.119,2.665-0.359l-0.8,2.479
+ l0.04,0.027c0.96-1.412,1.679-2.519,2.839-2.519c0.32,0,1.013,0.025,1.013,1.052c0,0.866-1.159,3.692-1.159,4.079
+ c0,0.119,0.053,0.24,0.199,0.24c0.268,0,0.574-0.48,0.92-1.013L151.587,25.076z"/>
+ <path style="fill:#FFFFFF" d="M166.562,22.596l-0.36-0.026c-0.106-0.666-0.387-1.586-1.173-1.586c-0.706,0-0.706,0.652-0.706,0.72
+ c0,0.839,1.825,2.013,1.825,3.385c0,1.292-1.146,1.786-1.998,1.786c-0.587,0-0.92-0.266-1.227-0.266
+ c-0.053,0-0.32,0.039-0.387,0.266h-0.359l0.293-2.211l0.36,0.039c0.199,1.772,1.159,1.772,1.278,1.772
+ c0.507,0,0.76-0.398,0.76-0.732c0-0.346-0.319-0.879-0.746-1.373c-0.693-0.799-1.066-1.346-1.066-2.158
+ c0-1.187,0.92-1.666,1.786-1.666c0.666,0,0.826,0.293,1.267,0.293c0.199,0,0.239-0.054,0.373-0.28h0.386L166.562,22.596z"/>
+ <path style="fill:#FFFFFF" d="M183.493,22.596c0,1.985-1.653,4.278-3.785,4.278c-1.093,0-2.132-0.705-2.132-1.986
+ c0-2.053,1.719-4.344,3.824-4.344C181.907,20.544,183.493,20.81,183.493,22.596z M179.161,25.554c0,0.268,0,0.934,0.694,0.934
+ c1.278,0,2.052-3.705,2.052-4.705c0-0.666-0.293-0.852-0.64-0.852C179.828,20.931,179.161,24.702,179.161,25.554z"/>
+ <path style="fill:#FFFFFF" d="M194.643,20.917c1.359-0.119,1.772-0.146,2.665-0.372l-0.746,2.372l0.066,0.026
+ c0.4-0.8,1.188-2.398,2.066-2.398c0.053,0,0.772,0,0.772,0.879c0,0.613-0.373,0.973-0.786,0.973c-0.466,0-0.613-0.506-0.8-0.506
+ c-0.373,0-1.065,1.333-1.359,2.039c-0.347,0.893-0.479,1.532-0.906,2.771h-1.612c0.707-2.398,1.359-4.636,1.359-5.064
+ c0-0.333-0.24-0.346-0.72-0.359V20.917z"/>
+ <path style="fill:#FFFFFF" d="M213.214,21.277h-1.105c-0.44,1.626-1.16,3.958-1.16,4.412c0,0.16,0.187,0.199,0.2,0.199
+ c0.333,0,0.853-0.786,1.026-1.066l0.293,0.187c-0.494,0.774-1.146,1.813-2.187,1.813c-0.959,0-0.959-0.799-0.959-0.893
+ c0-0.547,0.64-2.546,1.227-4.652h-0.681V20.81c0.521-0.199,1.533-0.612,2.439-2.024h0.467l-0.52,1.932h0.959V21.277z"/>
+ <path style="fill:#FFFFFF" d="M226.346,25.008c-0.68,1.04-1.239,1.813-2.226,1.813c-0.773,0-0.92-0.574-0.92-0.893
+ c0-0.586,1.04-3.918,1.04-4.225c0-0.427-0.4-0.439-0.787-0.427v-0.36c0.534-0.039,1.786-0.159,2.692-0.372
+ c-0.52,1.905-1.333,4.717-1.333,5.144c0,0.133,0.121,0.199,0.201,0.199c0.319,0,0.771-0.666,1.039-1.066L226.346,25.008z
+ M225.759,17.585c0.507,0,0.92,0.413,0.92,0.906c0,0.479-0.399,0.88-0.92,0.88c-0.626,0-0.879-0.56-0.879-0.906
+ C224.88,18.145,225.159,17.585,225.759,17.585z"/>
+ <path style="fill:#FFFFFF" d="M243.436,25.102c-0.254,0.452-0.999,1.719-2.119,1.719c-0.2,0-0.893,0-0.893-0.746
+ c0-0.574,0.16-1.027,0.293-1.427l-0.026-0.013c-1.014,1.413-1.572,2.186-2.586,2.186c-1.025,0-1.025-0.732-1.025-1.039
+ c0-0.92,0.999-3.345,0.999-4.119c0-0.347-0.293-0.36-0.746-0.387v-0.36c1.186-0.053,2.599-0.359,2.785-0.372l-1.253,4.104
+ c-0.16,0.507-0.199,0.654-0.199,0.894c0,0.252,0.133,0.318,0.307,0.318c0.532,0,1.345-1.265,1.519-1.584
+ c0.6-1.08,0.906-2.133,1.319-3.559h1.573c-0.268,0.934-1.374,4.518-1.374,4.944c0,0.227,0.134,0.254,0.2,0.254
+ c0.32,0,0.826-0.8,0.946-0.987L243.436,25.102z"/>
+ <path style="fill:#FFFFFF" d="M254.745,20.917c1.412-0.119,1.745-0.146,2.665-0.359l-0.8,2.479l0.04,0.027
+ c0.772-1.187,1.64-2.519,2.825-2.519c0.066,0,0.879,0,0.879,0.893c0,0.52-0.187,0.973-0.307,1.279l0.014,0.027
+ c0.68-1.08,1.466-2.199,2.559-2.199c0.733,0,1.054,0.466,1.054,1.052c0,0.88-1.187,3.599-1.187,4.118
+ c0,0.16,0.146,0.201,0.227,0.201c0.254,0,0.68-0.667,0.906-1.013l0.293,0.174c-0.413,0.707-1.026,1.746-2.092,1.746
+ c-0.227,0-0.934,0-0.934-0.867c0-0.972,1.173-3.811,1.173-4.13c0-0.16-0.106-0.307-0.319-0.307c-0.561,0-1.266,1.159-1.479,1.546
+ c-0.493,0.853-0.68,1.453-1.346,3.637h-1.6c1.026-3.318,1.466-4.49,1.466-4.864c0-0.253-0.159-0.319-0.253-0.319
+ c-0.093,0-1.572,0.333-2.812,5.183h-1.612c0.759-2.785,1.359-4.636,1.359-5.077c0-0.386-0.521-0.359-0.72-0.347V20.917z"/>
+ <path d="M123.286,1.771h2.012l-4.572,15.43h-0.206l-2.858-9.555l-2.857,9.555h-0.183l-4.572-15.43h2.012l2.743,9.396l1.874-6.332
+ l-0.914-3.063h2.012l2.743,9.396L123.286,1.771z"/>
+ <path d="M189.955,1.453h2.012l-4.572,15.43h-0.206l-2.856-9.555l-2.857,9.555h-0.184l-4.572-15.43h2.013l2.743,9.396l1.874-6.333
+ l-0.914-3.063h2.012l2.742,9.396L189.955,1.453z"/>
+ <path d="M244.878,1.453h2.012l-4.572,15.43h-0.205l-2.858-9.555l-2.857,9.555h-0.183l-4.572-15.43h2.012l2.743,9.396l1.874-6.333
+ l-0.914-3.063h2.012l2.744,9.396L244.878,1.453z"/>
+ <path d="M248.27,3.238h6.096v2.024h-4.048v2.438h4.048v2.048h-4.048v3.658h4.048v2.024h-6.096V3.238z"/>
+ <path d="M257.815,3.238h4.268c1.805,0,3.268,1.463,3.268,3.243c0,0.829-0.268,1.463-0.707,2.024
+ c0.927,0.707,1.512,1.731,1.512,3.072c0,2.122-1.73,3.853-3.853,3.853c-0.22,0-4.487,0-4.487,0V3.238z M259.863,7.701h2.22
+ c0.683,0,1.219-0.537,1.219-1.22c0-0.658-0.536-1.219-1.219-1.219c-0.171,0-2.22,0-2.22,0V7.701z M259.863,13.407h2.439
+ c0.999,0,1.829-0.829,1.829-1.829c0-1.024-0.83-1.829-1.829-1.829c-0.391,0-2.439,0-2.439,0V13.407z"/>
+ <path d="M130.116,3.042c2.756,0,4.268,2.829,4.268,6.292s-1.512,6.291-4.268,6.291c-2.731,0-4.268-2.828-4.268-6.291
+ S127.385,3.042,130.116,3.042z M130.116,13.602c1.17,0,2.122-1.926,2.122-4.267c0-2.341-0.951-4.268-2.122-4.268
+ c-1.146,0-2.122,1.926-2.122,4.268C127.994,11.675,128.97,13.602,130.116,13.602z"/>
+ <path d="M146.575,15.431h-2.487l-2.927-4.072c-0.487,0-1.487,0-1.487,0v4.072h-2.049V3.238h3.878c2.219,0,4.048,1.829,4.048,4.072
+ c0,1.561-0.878,2.926-2.194,3.609L146.575,15.431z M143.526,7.31c0-1.122-0.902-2.048-2.023-2.048c-0.22,0-1.829,0-1.829,0v4.072
+ h1.829C142.624,9.334,143.526,8.407,143.526,7.31z"/>
+ <path d="M151.256,13.407h4.048v2.024h-6.096V3.238h2.048V13.407z"/>
+ <path d="M156.724,3.238h2.853c3.024,0,5.487,2.731,5.487,6.097c0,3.365-2.463,6.096-5.487,6.096c-0.269,0-2.853,0-2.853,0V3.238z
+ M158.771,13.407h0.805c1.78,0,3.243-1.829,3.243-4.072c0-2.244-1.463-4.072-3.243-4.072c-0.316,0-0.805,0-0.805,0V13.407z"/>
+ <path d="M200.331,3.238h2.854c3.023,0,5.487,2.731,5.487,6.097c0,3.365-2.464,6.096-5.487,6.096c-0.269,0-2.854,0-2.854,0V3.238z
+ M202.38,13.407h0.805c1.78,0,3.243-1.829,3.243-4.072c0-2.244-1.463-4.072-3.243-4.072c-0.316,0-0.805,0-0.805,0V13.407z"/>
+ <path d="M211.913,3.238h6.097v2.024h-4.048v2.438h4.048v2.048h-4.048v3.658h4.048v2.024h-6.097V3.238z"/>
+ <rect x="194.518" y="3.188" width="1.786" height="12.203"/>
+ <path style="fill-rule:evenodd;clip-rule:evenodd" d="M274.845,25.169c-0.659,0-1.251,0.238-1.699,0.693c-0.477,0.482-0.741,1.109-0.741,1.754
+ c0,0.646,0.252,1.244,0.714,1.713c0.469,0.477,1.074,0.734,1.727,0.734c0.639,0,1.258-0.258,1.74-0.729
+ c0.463-0.447,0.714-1.045,0.714-1.719c0-0.652-0.259-1.264-0.707-1.719C276.122,25.42,275.511,25.169,274.845,25.169z
+ M276.973,27.638c0,0.564-0.218,1.086-0.619,1.475c-0.421,0.408-0.944,0.625-1.522,0.625c-0.544,0-1.081-0.225-1.481-0.633
+ c-0.401-0.408-0.626-0.932-0.626-1.488s0.231-1.102,0.646-1.521c0.389-0.395,0.911-0.606,1.482-0.606
+ c0.585,0,1.107,0.217,1.517,0.633C276.762,26.515,276.973,27.046,276.973,27.638z M274.913,26.183h-1.047v2.773h0.522v-1.184
+ h0.518l0.563,1.184h0.585l-0.618-1.264c0.4-0.082,0.632-0.355,0.632-0.75C276.068,26.44,275.688,26.183,274.913,26.183z
+ M274.817,26.522c0.49,0,0.713,0.135,0.713,0.475c0,0.326-0.223,0.443-0.699,0.443h-0.442v-0.918H274.817z"/>
+ <path d="M93.451,0l1.056,6.42l-3.738,7.152c0,0-1.436-3.034-3.82-4.714c-2.009-1.416-3.318-1.723-5.364-1.301
+ c-2.628,0.542-5.608,3.685-6.908,7.559c-1.556,4.636-1.571,6.879-1.625,8.94c-0.087,3.304,0.434,5.256,0.434,5.256
+ s-2.27-4.199-2.249-10.349c0.015-4.389,0.704-8.371,2.736-12.299c1.787-3.454,4.443-5.526,6.8-5.77
+ c2.437-0.252,4.363,0.923,5.852,2.194c1.562,1.334,3.143,4.253,3.143,4.253L93.451,0z"/>
+ <path d="M93.911,36.329c0,0-1.653,2.953-2.682,4.091c-1.03,1.138-2.872,3.143-5.147,4.146c-2.275,1.001-3.468,1.191-5.716,0.975
+ c-2.246-0.216-4.334-1.517-5.066-2.06c-0.731-0.541-2.601-2.14-3.657-3.629c-1.057-1.49-2.709-4.471-2.709-4.471
+ s0.921,2.986,1.497,4.254c0.332,0.729,1.351,2.96,2.797,4.902c1.349,1.813,3.969,4.932,7.951,5.635
+ c3.982,0.705,6.719-1.083,7.396-1.517c0.677-0.433,2.104-1.628,3.007-2.594c0.943-1.009,1.835-2.296,2.33-3.067
+ c0.361-0.564,0.948-1.707,0.948-1.707L93.911,36.329z"/>
+ <path style="fill:#0C479D" d="M25.146,0.284l9.003,30.611l9.003-30.611h6.519L34.771,50.576h-0.621l-9.313-31.168l-9.314,31.168h-0.621
+ L0,0.284h6.519l9.003,30.611l6.085-20.614l-2.98-9.997H25.146z"/>
+ <path style="fill:#0C479D" d="M68.184,34.434c0,4.554-1.211,8.383-3.632,11.487c-2.422,3.104-5.558,4.655-9.407,4.655
+ c-2.898,0-5.423-0.921-7.576-2.763c-2.152-1.842-3.746-4.335-4.781-7.481l5.091-2.11c0.746,1.904,1.729,3.405,2.95,4.501
+ c1.221,1.097,2.659,1.645,4.315,1.645c1.738,0,3.208-0.972,4.409-2.917s1.801-4.284,1.801-7.017c0-3.022-0.642-5.36-1.925-7.017
+ c-1.491-1.945-3.83-2.918-7.017-2.918h-2.483v-2.98l8.693-15.026H48.128l-2.918,4.967h-1.862V0.284h24.215v3.042l-9.19,15.833
+ c3.229,1.036,5.671,2.919,7.326,5.652C67.354,27.542,68.184,30.749,68.184,34.434z"/>
+ <g>
+ <g>
+ <g>
+ <path d="M135.359,42.137h-4.523v1.736c0.018,0.145,0.018,0.249,0,0.313c-0.075,0.21-0.261,0.314-0.558,0.314
+ c-0.299,0-0.909-0.407-1.831-1.219c-0.922-0.814-1.382-1.357-1.382-1.631c0-0.146,0.093-0.272,0.278-0.385
+ c0.186-0.113,0.324-0.17,0.418-0.17h1.671v-3.802c0-0.337-0.105-0.602-0.318-0.794c-0.212-0.192-0.567-0.29-1.064-0.29
+ c-0.388,0-0.692,0.104-0.914,0.313s-0.332,0.503-0.332,0.886c0,0.271,0.067,0.552,0.202,0.838
+ c0.039,0.095,0.115,0.217,0.231,0.361h-1.27c-0.094-0.163-0.16-0.293-0.197-0.389c-0.151-0.323-0.226-0.59-0.226-0.799
+ c0-0.728,0.246-1.267,0.738-1.621c0.491-0.355,1.118-0.534,1.879-0.534c0.743,0,1.375,0.194,1.894,0.582
+ c0.52,0.387,0.78,0.887,0.78,1.499v3.75h4.523V42.137z M133.104,36.527h-1.116v-0.945h1.116V36.527z"/>
+ </g>
+ <g>
+ <path d="M139.753,36.527h-3.214v11.124h-1.404V36.527h-2.376v-0.945h6.995V36.527z"/>
+ </g>
+ <g>
+ <path d="M145.448,36.527h-3.215v11.124h-1.403V36.527h-2.376v-0.945h6.995V36.527z"/>
+ </g>
+ <g>
+ <path d="M156.204,36.527h-3.776v3.074c0,0.388-0.101,0.75-0.303,1.09c-0.205,0.34-0.556,0.614-1.056,0.822
+ c-0.259,0.114-0.514,0.198-0.763,0.253c-0.25,0.058-0.522,0.088-0.819,0.088c-0.352,0-0.646-0.042-0.888-0.128
+ c0.166,0.211,0.296,0.381,0.388,0.509c0.59,0.774,1.097,1.413,1.521,1.913c0.369,0.403,0.94,0.977,1.714,1.719
+ c0.24,0.227,0.572,0.541,0.996,0.945l-1.01,0.849c-0.628-0.6-1.307-1.285-2.036-2.061c-0.729-0.776-1.399-1.535-2.008-2.28
+ c-0.776-0.984-1.451-1.904-2.023-2.763c-0.203-0.289-0.434-0.653-0.692-1.092l1.082-0.701l1.082,1.649
+ c0.092,0.145,0.287,0.273,0.583,0.388c0.295,0.112,0.619,0.17,0.971,0.17c0.61,0,1.072-0.138,1.388-0.412
+ c0.313-0.273,0.471-0.634,0.471-1.087v-2.945h-6.875v-0.945h12.055V36.527z"/>
+ </g>
+ <g>
+ <path d="M163.116,36.527h-7.771v-0.945h7.771V36.527z M165.354,40.09h-5.216c-0.663,0-1.141,0.116-1.436,0.348
+ c-0.295,0.234-0.441,0.552-0.441,0.954c0,1.125,0.615,2.34,1.848,3.643c0.401,0.434,1.028,0.98,1.873,1.64l-0.886,0.796
+ c-0.939-0.708-1.642-1.304-2.101-1.789c-1.401-1.464-2.102-2.857-2.102-4.179c0-0.742,0.233-1.309,0.705-1.705
+ c0.47-0.393,1.092-0.592,1.866-0.592h5.889V40.09z"/>
+ </g>
+ <g>
+ <path d="M169.777,36.527h-3.214v11.124h-1.404V36.527h-2.377v-0.945h6.995V36.527z"/>
+ </g>
+ <g>
+ <path d="M175.486,36.527h-3.192v11.124h-1.404V36.527h-2.411v-0.945h2.391c-0.35-0.916-0.671-1.6-0.964-2.049
+ c-0.921-1.365-2.052-2.05-3.395-2.05c-0.625,0-1.09,0.17-1.393,0.507c-0.304,0.337-0.456,0.785-0.456,1.347
+ c0,0.546,0.145,1.099,0.432,1.66c0.091,0.177,0.235,0.4,0.433,0.674h-1.267c-0.221-0.291-0.378-0.533-0.47-0.727
+ c-0.33-0.612-0.495-1.154-0.495-1.622c0-0.921,0.284-1.613,0.856-2.08c0.571-0.468,1.345-0.704,2.321-0.704
+ c1.825,0,3.32,0.841,4.479,2.522c0.369,0.549,0.765,1.389,1.19,2.521h3.344V36.527z"/>
+ </g>
+ <g>
+ <path d="M184.403,44.03c-0.333,0.241-0.628,0.419-0.885,0.533c-0.776,0.357-1.635,0.537-2.574,0.537
+ c-1.551,0-2.755-0.447-3.613-1.342c-0.857-0.895-1.287-1.896-1.287-3.008c0.075,0.029,0.133,0.045,0.171,0.045
+ c0.316,0.074,0.57,0.112,0.758,0.112c0.729,0,1.29-0.145,1.684-0.438c0.466-0.337,0.701-0.861,0.701-1.571
+ c0-0.485-0.223-1.025-0.669-1.621c-0.148-0.194-0.371-0.444-0.669-0.751h-3.846v-0.945h8.172v0.945h-2.672
+ c0.242,0.322,0.419,0.589,0.531,0.798c0.371,0.646,0.555,1.196,0.555,1.646c0,1.066-0.514,1.856-1.545,2.372
+ c-0.334,0.161-0.858,0.322-1.577,0.484c0.185,0.531,0.378,0.926,0.579,1.182c0.627,0.806,1.539,1.208,2.737,1.208
+ c0.755,0,1.444-0.18,2.07-0.534c0.753-0.436,1.212-0.864,1.379-1.284V44.03z"/>
+ </g>
+ <g>
+ <path d="M188.993,36.527h-3.216v11.124h-1.404V36.527h-2.375v-0.945h6.995V36.527z"/>
+ </g>
+ <g>
+ <path d="M215.396,36.527h-8.921v3.529c0.221-0.279,0.424-0.483,0.607-0.614c0.57-0.41,1.252-0.614,2.041-0.614
+ c1.087,0,1.96,0.281,2.622,0.847c0.662,0.566,0.994,1.285,0.994,2.155c0,0.791-0.451,1.729-1.352,2.81
+ c-0.293,0.356-0.753,0.824-1.379,1.405l-1.158-0.73c0.549-0.468,0.95-0.858,1.206-1.166c0.804-0.924,1.207-1.742,1.207-2.456
+ c0-0.582-0.171-1.049-0.509-1.397c-0.341-0.349-0.831-0.521-1.473-0.521c-0.753,0-1.408,0.324-1.967,0.972
+ c-0.562,0.649-0.841,1.249-0.841,1.801v5.105h-1.403v-3.497c-0.238,0.191-0.442,0.333-0.605,0.429
+ c-0.57,0.303-1.179,0.453-1.818,0.453c-1.049,0-1.944-0.318-2.688-0.956c-0.744-0.634-1.115-1.405-1.115-2.308
+ s0.361-1.654,1.083-2.259c0.724-0.604,1.597-0.905,2.622-0.905c0.273,0,0.566,0.034,0.878,0.109
+ c0.092,0.015,0.22,0.051,0.385,0.11v1.07c-0.129-0.07-0.231-0.123-0.305-0.159c-0.294-0.124-0.57-0.187-0.829-0.187
+ c-0.664,0-1.232,0.21-1.713,0.632c-0.479,0.421-0.718,0.93-0.718,1.527c0,0.648,0.213,1.206,0.637,1.674
+ c0.426,0.472,0.98,0.704,1.665,0.704c0.628,0,1.164-0.168,1.606-0.507c0.61-0.469,0.916-0.886,0.916-1.259v-5.797h-8.172
+ v-0.945h18.496V36.527z"/>
+ </g>
+ <g>
+ <path d="M220.87,36.527h-3.216v11.124h-1.404V36.527h-2.375v-0.945h6.995V36.527z"/>
+ </g>
+ <g>
+ <path d="M229.798,44.03c-0.33,0.241-0.626,0.419-0.885,0.533c-0.773,0.357-1.633,0.537-2.572,0.537
+ c-1.551,0-2.755-0.447-3.611-1.342c-0.858-0.895-1.287-1.896-1.287-3.008c0.075,0.029,0.13,0.045,0.167,0.045
+ c0.32,0.074,0.572,0.112,0.76,0.112c0.729,0,1.29-0.145,1.684-0.438c0.468-0.337,0.701-0.861,0.701-1.571
+ c0-0.485-0.223-1.025-0.669-1.621c-0.15-0.194-0.371-0.444-0.668-0.751h-3.847v-0.945h8.172v0.945h-2.67
+ c0.242,0.322,0.417,0.589,0.527,0.798c0.371,0.646,0.559,1.196,0.559,1.646c0,1.066-0.518,1.856-1.549,2.372
+ c-0.33,0.161-0.856,0.322-1.577,0.484c0.186,0.531,0.378,0.926,0.583,1.182c0.626,0.806,1.539,1.208,2.735,1.208
+ c0.755,0,1.444-0.18,2.068-0.534c0.756-0.436,1.216-0.864,1.379-1.284V44.03z"/>
+ </g>
+ <g>
+ <path d="M234.388,36.527h-3.214v11.124h-1.404V36.527h-2.375v-0.945h6.993V36.527z"/>
+ </g>
+ <g>
+ <path d="M240.085,36.527h-3.216v11.124h-1.405V36.527h-2.375v-0.945h6.996V36.527z"/>
+ </g>
+ <g>
+ <path d="M238.137,31.033c-0.146-0.065-0.275-0.104-0.386-0.122c-0.331-0.081-0.68-0.119-1.048-0.119
+ c-0.514,0-0.931,0.135-1.253,0.402c-0.321,0.268-0.483,0.671-0.483,1.208c0,0.668,0.307,1.418,0.916,2.246
+ c0.203,0.263,0.508,0.604,0.913,1.028h-1.078c-0.148-0.05-0.497-0.455-1.051-1.215c-0.664-0.925-0.994-1.742-0.994-2.454
+ c0-0.794,0.304-1.368,0.909-1.727c0.496-0.291,1.158-0.436,1.983-0.436c0.403,0,0.79,0.046,1.156,0.141
+ c0.112,0.034,0.248,0.08,0.415,0.145V31.033z"/>
+ </g>
+ <g>
+ <path d="M251.558,36.527H239.78v-0.945h11.777V36.527z M252.608,40.539c-0.313,0.048-0.572,0.097-0.773,0.146
+ c-0.593,0.176-1.104,0.457-1.537,0.844c-0.435,0.384-0.807,0.842-1.122,1.37c-0.238,0.403-0.367,0.684-0.385,0.844
+ l-1.164-0.533l0.395-0.896l0.508-0.752c-0.13-0.274-0.518-0.572-1.161-0.897c-0.518-0.258-1.053-0.388-1.605-0.388
+ c-0.756,0-1.378,0.206-1.866,0.619c-0.488,0.411-0.733,0.965-0.733,1.659c0,1.099,0.756,2.115,2.263,3.052
+ c0.495,0.307,1.249,0.662,2.261,1.067l-0.664,1.04c-0.867,0-2.065-0.692-3.594-2.08c-1.09-0.984-1.633-2.033-1.633-3.146
+ c0-1.017,0.349-1.808,1.05-2.371c0.702-0.565,1.658-0.847,2.878-0.847c0.699,0,1.389,0.157,2.073,0.474
+ c0.681,0.319,1.171,0.665,1.466,1.037c0.184-0.145,0.332-0.25,0.44-0.313c0.37-0.243,0.664-0.411,0.886-0.51
+ c0.388-0.161,0.839-0.289,1.355-0.388c0.167-0.031,0.388-0.063,0.664-0.096V40.539z"/>
+ </g>
+ <g>
+ <path d="M257.114,36.527h-3.216v11.124h-1.402V36.527h-2.377v-0.945h6.995V36.527z"/>
+ </g>
+ <g>
+ <path d="M266.044,44.03c-0.332,0.241-0.627,0.419-0.886,0.533c-0.774,0.357-1.634,0.537-2.573,0.537
+ c-1.55,0-2.755-0.447-3.611-1.342c-0.858-0.895-1.287-1.896-1.287-3.008c0.075,0.029,0.131,0.045,0.169,0.045
+ c0.318,0.074,0.571,0.112,0.759,0.112c0.729,0,1.291-0.145,1.683-0.438c0.468-0.337,0.701-0.861,0.701-1.571
+ c0-0.485-0.222-1.025-0.668-1.621c-0.149-0.194-0.371-0.444-0.669-0.751h-3.846v-0.945h8.172v0.945h-2.669
+ c0.239,0.322,0.417,0.589,0.527,0.798c0.371,0.646,0.559,1.196,0.559,1.646c0,1.066-0.518,1.856-1.55,2.372
+ c-0.332,0.161-0.857,0.322-1.576,0.484c0.184,0.531,0.378,0.926,0.58,1.182c0.629,0.806,1.539,1.208,2.736,1.208
+ c0.756,0,1.443-0.18,2.07-0.534c0.754-0.436,1.214-0.864,1.379-1.284V44.03z"/>
+ </g>
+ <g>
+ <path d="M270.634,36.527h-3.214v11.124h-1.405V36.527h-2.376v-0.945h6.995V36.527z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/pres/Slidy/face1.gif b/pres/Slidy/face1.gif Binary files differnew file mode 100644 index 00000000..04e50cd7 --- /dev/null +++ b/pres/Slidy/face1.gif diff --git a/pres/Slidy/face2.gif b/pres/Slidy/face2.gif Binary files differnew file mode 100644 index 00000000..12d82400 --- /dev/null +++ b/pres/Slidy/face2.gif diff --git a/pres/Slidy/face3.gif b/pres/Slidy/face3.gif Binary files differnew file mode 100644 index 00000000..ac6e5e44 --- /dev/null +++ b/pres/Slidy/face3.gif diff --git a/pres/Slidy/face4.gif b/pres/Slidy/face4.gif Binary files differnew file mode 100644 index 00000000..3f687402 --- /dev/null +++ b/pres/Slidy/face4.gif diff --git a/pres/Slidy/fold-bright.gif b/pres/Slidy/fold-bright.gif Binary files differnew file mode 100644 index 00000000..7e38faa8 --- /dev/null +++ b/pres/Slidy/fold-bright.gif diff --git a/pres/Slidy/fold-dim.bmp b/pres/Slidy/fold-dim.bmp Binary files differnew file mode 100644 index 00000000..117f91a9 --- /dev/null +++ b/pres/Slidy/fold-dim.bmp diff --git a/pres/Slidy/fold-dim.gif b/pres/Slidy/fold-dim.gif Binary files differnew file mode 100644 index 00000000..a6f240e7 --- /dev/null +++ b/pres/Slidy/fold-dim.gif diff --git a/pres/Slidy/fold.bmp b/pres/Slidy/fold.bmp Binary files differnew file mode 100644 index 00000000..6ba9e562 --- /dev/null +++ b/pres/Slidy/fold.bmp diff --git a/pres/Slidy/help.html b/pres/Slidy/help.html new file mode 100644 index 00000000..ccdc676d --- /dev/null +++ b/pres/Slidy/help.html @@ -0,0 +1,79 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head>
+
+ <title>Slide Show Help</title><style type="text/css">
+ body {
+ font-family: sans-serif;
+ margin: 10%;
+ }
+ .copyright { font-size: smaller }
+ </style> + <script type="text/javascript"> + window.onload = load; + function load() + { + var back = document.getElementById("back"); + back.focus(); + } + </script> +</head>
+<body>
+<h1>Slide Show Help</h1>
+
+<p>This slide show can be driven in the same way as Power Point.
+To advance to the next slide click anywhere on the page with the
+mouse, or press the space bar. You can move forwards or backwards
+through the slides with the Cursor left, Cursor right, Pg Up and +Pg Dn keys. The font size is automatically adjusted to match the +browser's window width, but you can also adjust it manually using +the "S" key for smaller and the "B" key for bigger. You can also +use the "<" and ">" keys. Before printing, use the "A" key +to toggle between current slide and all slides. Use the "F" key to +switch off/on the bottom status line. The "K" key toggles the use +of mouse click to advance to the next slide. You can use "C" to +show the table of contents and any other key to hide it. Use the +"F11" key to toggle the browser's full screen mode. Note that not +all keys are supported in all browsers, as browsers may reserve +some keys for browser control and this varies from one browser to +the next.</p> + +<p>Firefox users may want the <a +href="http://extensionroom.mozdev.org/more-info/autohide">autohide</a> +extension to hide the toolbars when entering full screen with F11. +Newer versions of Firefox have built-in support for SVG, but on older +versions for Microsoft Widows, you should consider installing the <a +href="http://plugindoc.mozdev.org/windows.html">Adobe SVG Viewer +6.0</a>.</p>
+
+<p>If you would like to see how Slidy works, use View Source to view
+the XHTML markup, or see this <a +href="http://www.w3.org/Talks/Tools/Slidy/">longer explanation</a>,
+which also explains additional features. Each slide is marked up as
+a div element with class="slide". CSS positioning and percentage
+widths on images can be used to ensure your image rich slides scale
+to match the window size. Content to be revealed incrementally can +be marked up with class="incremental". The linked style sheet and +scripts were developed as a Web-based alternative to proprietary +presentation tools and have been tested on a variety of recent +browsers. Integrated editing support is under development. Please +send your comments to <a href="http://www.w3.org/People/Raggett/">Dave +Raggett</a> <<a href="mailto:dsr@w3.org">dsr@w3.org</a>>.</p>
+
+<p><em>You are welcome to make use of the slide show style sheets,
+scripts and help file under W3C's <a href="http://www.w3.org/Consortium/Legal/copyright-documents">document use</a>
+and <a href="http://www.w3.org/Consortium/Legal/copyright-software">software
+licensing</a> rules.</em></p>
+
+<button id="back" onclick="history.go(-1)">Return to slide show</button>
+
+<hr>
+
+<p class="copyright"><a rel="Copyright" href="http://www.w3.org/Consortium/Legal/ipr-notice#Copyright">Copyright</a> © 2005
+<a href="/" shape="rect"><acronym title="World Wide Web Consortium">W3C</acronym></a><sup>®</sup> (<a href="http://www.csail.mit.edu/"><acronym title="Massachusetts Institute of Technology">MIT</acronym></a>,
+<a href="http://www.ercim.org/"><acronym title="European Research Consortium for Informatics and Mathematics">ERCIM</acronym></a>,
+<a href="http://www.keio.ac.jp/">Keio</a>), All Rights Reserved. W3C
+<a href="http://www.w3.org/Consortium/Legal/ipr-notice#Legal_Disclaimer">liability</a>,
+<a href="http://www.w3.org/Consortium/Legal/ipr-notice#W3C_Trademarks">trademark</a>,
+<a rel="Copyright" href="http://www.w3.org/Consortium/Legal/copyright-documents">document use</a> and <a rel="Copyright" href="http://www.w3.org/Consortium/Legal/copyright-software">software
+licensing</a> rules apply.</p>
+</body></html> diff --git a/pres/Slidy/icon-blue.png b/pres/Slidy/icon-blue.png Binary files differnew file mode 100644 index 00000000..58bf969e --- /dev/null +++ b/pres/Slidy/icon-blue.png diff --git a/pres/Slidy/index.html b/pres/Slidy/index.html new file mode 100644 index 00000000..e221f9c6 --- /dev/null +++ b/pres/Slidy/index.html @@ -0,0 +1,324 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en-US"> + +<!-- *************************** --> +<!-- BOILERPLATE STUFF, SKIP DOWN --> + +<head> +<meta name="generator" content= +"HTML Tidy for Linux/x86 (vers 1st November 2003), see www.w3.org" /> +<title>Cobbler</title> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="copyright" content= +"(CC) Creative Commons Share Alike 3.0 | 2009</A>"/> +<meta name="font-size-adjustment" content="-2" /> +<link rel="stylesheet" href="slidy.css" type="text/css" +media="screen, projection, print" /> +<link rel="stylesheet" href="slidy.css" type="text/css" +media="screen, projection, print" /> +<script src="slidy.js" type="text/javascript"> +</script> +</head> +<body> + +<div class="background"> +</div> + +<!-- *************************** --> +<!-- MORE BOILERPLATE, SKIP DOWN --> + +<div class="background slanty"> +<!-- slanty logo removed --> +</div> +<div class="slide cover title"> + <!-- hidden style graphics to ensure they are saved with other content --> + <img class="hidden" src="../Slidy/bullet.png" alt="" /> + <img class="hidden" src="../Slidy/fold.gif" alt="" /> + <img class="hidden" src="../Slidy/unfold.gif" alt="" /> + <img class="hidden" src="../Slidy/fold-dim.gif" alt="" /> + <img class="hidden" src="../Slidy/nofold-dim.gif" alt="" /> + <img class="hidden" src="../Slidy/unfold-dim.gif" alt="" /> + <img class="hidden" src="../Slidy/bullet-fold.gif" alt="" /> + <img class="hidden" src="../Slidy/bullet-unfold.gif" alt="" /> + <img class="hidden" src="../Slidy/bullet-fold-dim.gif" alt="" /> + <img class="hidden" src="../Slidy/bullet-nofold-dim.gif" alt="" /> + <img class="hidden" src="../Slidy/bullet-unfold-dim.gif" alt="" /> + +<!-- *********************** --> +<!-- TITLE SCREEN --> + +Cobbler</br> + +<p><a href="http://mdehaan.fedorapeople.org/">Michael DeHaan</a>, +<<a href="mailto:mdehaan@redhat.com">mdehaan@redhat.com</a>><br /> +<br /> +<br /> +<br /> +<br /><em>Press space to continue...</em></p> +</div> + +<!-- ********************** --> +<!-- BEGIN CONTENT --> + +<div class="slide"> +About Cobbler +<ul> +<li>Project started ~2005</li> +<li>Goal #1: bring sysadmins together to create and share superior deployment technology</li> +<li>Goal #2: provide tools for both physical AND virtual deployments</li> +<li>Goal #3: embrace the unix philosophy; simple tools working together</li> +<li>it's an application</li> +<li>it's also a web service for applications</li> +</ul> +</div> + +<div class="slide"> +Who uses it? +<ul> +<li>A few examples: <A HREF="https://fedorahosted.org/cobbler/wiki/WhoUsesCobbler">Who Uses Cobbler</A></li> +<li>Hosting companies</li> +<li>University departments</li> +<li>Research grids/clusters</li> +<li>Government agencies</li> +<li>Applications: + <ul> + <li><A HREF="http://fedorahosted.org/spacewalk">Spacewalk/Satellite</A></li> + <li><A HREF="http://ovirt.org">Ovirt.org</A></li> + <li><A HREF="http://fedorahosted.org/beaker">Beaker</A></li> + <li><A HREF="http://fedorahosted.org/genome">Genome</A></li> + <li>Various in-house software</li> + </ul> +</li> +</div> + +<div class="slide"> +Types of Installs That Cobbler Performs: +<ul> +<li>Netboot/PXE by MAC, IP, or menu</li> +<li>re-installs / upgrades of an existing Linux systems</li> +<li>network install ISO generation (PXE not required)</li> +<li>virtual machines -- kickstarts or images</li> +</ul> +</div> + +<div class="slide"> +Architectures supported (reinstalls & netboot): +<ul> +<li>x86 / x86_64</li> +<li>ppc / ppc64</li> +<li>ia64</li> +<li>s390 / s390x (requires Cobbler 1.5)</li> +</ul> +</div> + +<div class="slide"> +What Works? +<ul> +<li> + Installation targets: + <ul> + <li>Red Hat Enterprise Linux</li> + <li>Fedora</li> + <li>CentOS, Scientific Linux</li> + <li>OpenSuSE</li> + <li>Debian (90% there)</li> + <li>Ubuntu (90% there)</li> + <li>Windows (virt, evolving)</li> +</ul> +<li> + Server platforms: + <ul> + <li>Red Hat Enterprise Linux 4+</li> + <li>any Fedora</li> + <li>Debian (partial; packaging pending)</li> + <li>OpenSuSE (partial; packaging pending)</li> + </ul> +</li> +</ul> +</div> + +<div class="slide"> +Conceptual overview: +<ul> +<br/> +<IMG SRC="object_tree.png"/> +</div> + +<div class="slide"> +Details about Bare Metal Installs: +<ul> +<li>Cobbler manages your PXE tree for you</li> +<li>Built-in PXE boot loop prevention</li> +<li>Auto-generated PXE menus for "off the truck" deployment</li> +<li>"cobbler buildiso" for situations that can't do PXE (static/policy-driven)</li> +</ul> +</div> + +<div class="slide"> +Details about Reinstalls: +<ul> +<li>Suppose you don't have a PXE environment</li> +<li>You can reinstall a host over SSH</li> +<li>You can also perform upgrades this way</li> +<li>Works well with <A HREF="http://fedorahosted.org/func">Func</A></li> +</ul> +</div> + +<div class="slide"> +Details about Virtual Installs: +<ul> +<li>Uses a helper tool called "koan"</li> +<li>Virtualization parameters are centrally managed</li> +<li>Control over RAM, CPU, disk</li> +<li>Built in support for LVM volume groups</li> +<li> + Choose your own adventure: + <ul> + <li>libvirt guests: Xen PV, FV, KVM</li> + <li>non-libvirt: VMware server, workstation, ESX (?)</li> + </ul> +</li> +</div> + +<div class="slide"> +Need A Picture? +<br/> +<IMG SRC="code.png"/> +</div> + + +<div class="slide"> +Power Features: +<ul> +<li>Cheetah Powered -- Open Ended Templating Engine On Steroids</li> +<li>Snippets</li> +<li>Integrated Yum Mirroring</li> +<li>Advanced Networking Configuration -- bonding, vlans</li> +<li>DHCP Management</li> +<li>DNS Management</li> +<li>Power Management</li> +<li>Configuration Management Hooks (puppet)</li> +<li>Built-in Lightweight CMS</li> +<li>BIOS/Firmware upgrades (ex: Dell, HP)</li> +<li>Remotely erase machines (DBAN)</li> +<li>Extensive anaconda monitoring features</li> +<li>Multi-server replication (picture: next slide)</li> +</ul> +</div> + +<div class="slide"> +<IMG SRC="replication.png"/> +</div> + +<div class="slide"> +Easy setup process: +<pre> +yum install cobbler +vim /etc/cobbler/settings +cobbler check +vim /etc/cobbler/settings # maybe +</pre> +</div> + + +<div class="slide"> +Command line access is simple: +Setup: +<pre> +cobbler import --name=RHEL5 --mirror=/mnt/dvd +cobbler import --name=Fedora --path=rsync://foo +</pre> +Create and edit distros: +<pre> +cobbler distro edit --name=RHEL5-i386 --kopts="noapic x=y z=4" +</pre> +Define some roles for your systems to fill: +<pre> +cobbler profile edit --name=RHEL5-i386 --kickstart=/path/to/template.ks --ksmeta="somevar=1 othervar=2" +cobbler profile copy --name=RHEL5-i386 --newname=webservers +cobbler profile edit --name=webservers --virt-ram=2048 +</pre> +Map hardware to those roles: +<pre> +cobbler system add --name=foosball --mac=AA:BB:CC:DD:EE:FF --profile=RHEL5-i386 +cobbler system edit --name=foosball --ip=192.168.10.50 # DHCP reservations! +cobbler system edit --name=foosball --dns-name=foosball # DNS setup +</pre> +Reinstall systems you have defined: +<pre> +cobbler system edit --name=foosball --netboot-enabled=1 +cobbler system reboot --name=foosball +</pre> +Also help manage updates as they relate to installation and post-installation: +<pre> +cobbler repo add --name=f10-updates --mirror=http://foo +cobbler reposync +cobbler profile edit --name=desktops --repos="f10-updates" +</pre> + +</div> + +<div class="slide"> +Want to install a VM? Everything is easy, since it's centrally managed +<pre> +koan --list-profiles [--server=cobbler.example.org] +koan --virt --profile=foo [...] +koan --virt --system=bar [...] +</pre> +Reinstall a physical box? +<pre> +koan --replace-self --system=bar +</pre> +Users don't have to remember 15 different virt parameters! +</div> + +<div class="slide"> +We have a Web UI...</br> +<IMG SRC="screenshot.png"/> +</div> + +<div class="slide"> +For Developers: +<ul> +<li>Python API</li> +<li>XMLRPC API</li> +<li>Triggers system for hooking in with other software</li> +<li>User extensible authn/authz</li> +</ul> +</div> + +<div class="slide"> +Release cycle: +<ul> +<li>Relatively fast moving due to lots of contributors</li> +<li>All code kept in git</li> +<li>Kernel versioning scheme, odd=development, even=stable</li> +<li>Current stable is 1.4.X</li> +<li>Stable dot releases on current branch as needed</li> +<li>New major releases every 2-3 months, ideally 1-2 months</li> +</ul> + +Future roadmap and plans: +<ul> +<li>Better cross-distro support for non-homogenous environments</li> +<li>Keeping up with virtualization developments</li> +<li>Continual web app improvements</li> +<li>Integration with other config management systems?</li> +<li>All the great ideas people have shared in Trac.</li> +<li>World domination -- be THE go to install server for any OS</li> +</ul> + +</div> + +<div class="slide"> +Want to Learn More? +<ul> +<li>Lots of documentation: <A HREF="http://fedorahosted.org/cobbler">fedorahosted.org/cobbler</A></li> +<li>Active mailing list: <A HREF="https://fedorahosted.org/mailman/listinfo/cobbler">mailing list</A></li> +<li>IRC channel: #cobbler, irc.freenode.net</li> +</ul> + +</body> +</html> diff --git a/pres/Slidy/keys.jpg b/pres/Slidy/keys.jpg Binary files differnew file mode 100644 index 00000000..8fb4ab9c --- /dev/null +++ b/pres/Slidy/keys.jpg diff --git a/pres/Slidy/logo-cobbler.png b/pres/Slidy/logo-cobbler.png Binary files differnew file mode 100644 index 00000000..f8fe53ca --- /dev/null +++ b/pres/Slidy/logo-cobbler.png diff --git a/pres/Slidy/nofold-dim.bmp b/pres/Slidy/nofold-dim.bmp Binary files differnew file mode 100644 index 00000000..8a12826b --- /dev/null +++ b/pres/Slidy/nofold-dim.bmp diff --git a/pres/Slidy/nofold.bmp b/pres/Slidy/nofold.bmp Binary files differnew file mode 100644 index 00000000..0937d324 --- /dev/null +++ b/pres/Slidy/nofold.bmp diff --git a/pres/Slidy/object_tree.png b/pres/Slidy/object_tree.png Binary files differnew file mode 100644 index 00000000..354d5702 --- /dev/null +++ b/pres/Slidy/object_tree.png diff --git a/pres/Slidy/replication.png b/pres/Slidy/replication.png Binary files differnew file mode 100644 index 00000000..930a9050 --- /dev/null +++ b/pres/Slidy/replication.png diff --git a/pres/Slidy/screenshot.png b/pres/Slidy/screenshot.png Binary files differnew file mode 100644 index 00000000..906240a3 --- /dev/null +++ b/pres/Slidy/screenshot.png diff --git a/pres/Slidy/slidy.css b/pres/Slidy/slidy.css new file mode 100644 index 00000000..4e2ae90c --- /dev/null +++ b/pres/Slidy/slidy.css @@ -0,0 +1,316 @@ +/* slidy.css + + Copyright (c) 2005 W3C (MIT, ERCIM, Keio), All Rights Reserved. + W3C liability, trademark, document use and software licensing + rules apply, see: + + http://www.w3.org/Consortium/Legal/copyright-documents + http://www.w3.org/Consortium/Legal/copyright-software +*/ +body +{ + margin: 0 0 0 0; + padding: 0 0 0 0; + width: 100%; + height: 100%; + color: black; + background-color: white; + font-family: "Gill Sans MT", "Gill Sans", GillSans, sans-serif; + font-size: 14pt; +} + +.hidden { display: none; visibility: hidden } + +div.toolbar { + position: fixed; z-index: 200; + top: auto; bottom: 0; left: 0; right: 0; + height: 1.2em; text-align: right; + padding-left: 1em; + padding-right: 1em; + font-size: 60%; + color: red; background: rgb(240,240,240); +} + +div.background { + display: none; +} + +div.handout { + margin-left: 20px; + margin-right: 20px; +} + +div.slide.titlepage { + text-align: center; +} + +div.slide.titlepage.h1 { + padding-top: 40%; +} + +div.slide { + z-index: 20; + margin: 0 0 0 0; + padding-top: 0; + padding-bottom: 0; + padding-left: 20px; + padding-right: 20px; + border-width: 0; + top: 0; + bottom: 0; + left: 0; + right: 0; + line-height: 120%; + background-color: transparent; +} + +/* this rule is hidden from IE 6 and below which don't support + selector */ +div.slide + div[class].slide { page-break-before: always;} + +div.slide h1 { + padding-left: 0; + padding-right: 20pt; + padding-top: 4pt; + padding-bottom: 4pt; + margin-top: 0; + margin-left: 0; + margin-right: 60pt; + margin-bottom: 0.5em; + display: block; + font-size: 160%; + line-height: 1.2em; + background: transparent; +} + +div.toc { + position: absolute; + top: auto; + bottom: 4em; + left: 4em; + right: auto; + width: 60%; + max-width: 30em; + height: 30em; + border: solid thin black; + padding: 1em; + background: rgb(240,240,240); + color: black; + z-index: 300; + overflow: auto; + display: block; + visibility: visible; +} + +div.toc-heading { + width: 100%; + border-bottom: solid 1px rgb(180,180,180); + margin-bottom: 1em; + text-align: center; +} + +pre { + font-size: 80%; + font-weight: bold; + line-height: 120%; + padding-top: 0.2em; + padding-bottom: 0.2em; + padding-left: 1em; + padding-right: 1em; + border-style: solid; + border-left-width: 1em; + border-top-width: thin; + border-right-width: thin; + border-bottom-width: thin; + border-color: #95ABD0; + color: #00428C; + background-color: #E4E5E7; +} + +li pre { margin-left: 0; } + +@media print { + div.slide { + display: block; + visibility: visible; + position: relative; + border-top-style: solid; + border-top-width: thin; + border-top-color: black; + } + div.slide pre { font-size: 60%; padding-left: 0.5em; } + div.handout { display: block; visibility: visible; } +} + +blockquote { font-style: italic } + +img { background-color: transparent } + +p.copyright { font-size: smaller } + +.center { text-align: center } +.footnote { font-size: smaller; margin-left: 2em; } + +a img { border-width: 0; border-style: none } + +a:visited { color: navy } +a:link { color: navy } +a:hover { color: red; text-decoration: underline } +a:active { color: red; text-decoration: underline } + +a {text-decoration: none} +.navbar a:link {color: white} +.navbar a:visited {color: yellow} +.navbar a:active {color: red} +.navbar a:hover {color: red} + +ul { list-style-type: square; } +ul ul { list-style-type: disc; } +ul ul ul { list-style-type: circle; } +ul ul ul ul { list-style-type: disc; } +li { margin-left: 0.5em; margin-top: 0.5em; } +li li { font-size: 85%; font-style: italic } +li li li { font-size: 85%; font-style: normal } + +div dt +{ + margin-left: 0; + margin-top: 1em; + margin-bottom: 0.5em; + font-weight: bold; +} +div dd +{ + margin-left: 2em; + margin-bottom: 0.5em; +} + + +p,pre,ul,ol,blockquote,h2,h3,h4,h5,h6,dl,table { + margin-left: 1em; + margin-right: 1em; +} + +p.subhead { font-weight: bold; margin-top: 2em; } + +p.smaller { font-size: smaller } + +td,th { padding: 0.2em } + +ul { + margin: 0.5em 1.5em 0.5em 1.5em; + padding: 0; +} + +ol { + margin: 0.5em 1.5em 0.5em 1.5em; + padding: 0; +} + +ul { list-style-type: square; } +ul ul { list-style-type: disc; } +ul ul ul { list-style-type: circle; } +ul ul ul ul { list-style-type: disc; } + +ul li { + list-style: square; + margin: 0.1em 0em 0.6em 0; + padding: 0 0 0 0; + line-height: 140%; +} + +ol li { + margin: 0.1em 0em 0.6em 1.5em; + padding: 0 0 0 0px; + line-height: 140%; + list-style-type: decimal; +} + +li ul li { + font-size: 85%; + font-style: italic; + list-style-type: disc; + background: transparent; + padding: 0 0 0 0; +} +li li ul li { + font-size: 85%; + font-style: normal; + list-style-type: circle; + background: transparent; + padding: 0 0 0 0; +} +li li li ul li { + list-style-type: disc; + background: transparent; + padding: 0 0 0 0; +} + +li ol li { + list-style-type: decimal; +} + + +li li ol li { + list-style-type: decimal; +} + +/* + setting class="outline on ol or ul makes it behave as an + ouline list where blocklevel content in li elements is + hidden by default and can be expanded or collapsed with + mouse click. Set class="expand" on li to override default +*/ + +ol.outline li:hover { cursor: pointer } +ol.outline li.nofold:hover { cursor: default } + +ul.outline li:hover { cursor: pointer } +ul.outline li.nofold:hover { cursor: default } + +ol.outline { list-style:decimal; } +ol.outline ol { list-style-type:lower-alpha } + +ol.outline li.nofold { + padding: 0 0 0 20px; + background: transparent url(nofold-dim.gif) no-repeat 0px 0.5em; +} +ol.outline li.unfolded { + padding: 0 0 0 20px; + background: transparent url(fold-dim.gif) no-repeat 0px 0.5em; +} +ol.outline li.folded { + padding: 0 0 0 20px; + background: transparent url(unfold-dim.gif) no-repeat 0px 0.5em; +} +ol.outline li.unfolded:hover { + padding: 0 0 0 20px; + background: transparent url(fold.gif) no-repeat 0px 0.5em; +} +ol.outline li.folded:hover { + padding: 0 0 0 20px; + background: transparent url(unfold.gif) no-repeat 0px 0.5em; +} + +ul.outline li.nofold { + padding: 0 0 0 20px; + background: transparent url(nofold-dim.gif) no-repeat 0px 0.5em; +} +ul.outline li.unfolded { + padding: 0 0 0 20px; + background: transparent url(fold-dim.gif) no-repeat 0px 0.5em; +} +ul.outline li.folded { + padding: 0 0 0 20px; + background: transparent url(unfold-dim.gif) no-repeat 0px 0.5em; +} +ul.outline li.unfolded:hover { + padding: 0 0 0 20px; + background: transparent url(fold.gif) no-repeat 0px 0.5em; +} +ul.outline li.folded:hover { + padding: 0 0 0 20px; + background: transparent url(unfold.gif) no-repeat 0px 0.5em; +} + +/* for slides with class "title" in table of contents */ +a.titleslide { font-weight: bold; font-style: italic } diff --git a/pres/Slidy/slidy.js b/pres/Slidy/slidy.js new file mode 100644 index 00000000..20a2751d --- /dev/null +++ b/pres/Slidy/slidy.js @@ -0,0 +1,2789 @@ +/* slidy.js + + Copyright (c) 2005 W3C (MIT, ERCIM, Keio), All Rights Reserved. + W3C liability, trademark, document use and software licensing + rules apply, see: + + http://www.w3.org/Consortium/Legal/copyright-documents + http://www.w3.org/Consortium/Legal/copyright-software +*/ + +var ns_pos = (typeof window.pageYOffset!='undefined'); +var khtml = ((navigator.userAgent).indexOf("KHTML") >= 0 ? true : false); +var opera = ((navigator.userAgent).indexOf("Opera") >= 0 ? true : false); +var ie7 = (!ns_pos && navigator.userAgent.indexOf("MSIE 7") != -1); + +window.onload = startup; // equivalent to onload on body element + +// IE only event handlers to ensure all slides are printed +// I don't yet know how to emulate these for other browsers +window.onbeforeprint = beforePrint; +window.onafterprint = afterPrint; + +// hack to hide slides while loading +setTimeout(hideAll, 50); + +function hideAll() +{ + if (document.body) + document.body.style.visibility = "hidden"; + else + setTimeout(hideAll, 50); +} + +var slidenum = 0; // integer slide count: 0, 1, 2, ... +var slides; // set to array of slide div's +var slideNumElement; // element containing slide number +var notes; // set to array of handout div's +var backgrounds; // set to array of background div's +var toolbar; // element containing toolbar +var title; // document title +var lastShown = null; // last incrementally shown item +var eos = null; // span element for end of slide indicator +var toc = null; // table of contents +var outline = null; // outline element with the focus +var selectedTextLen; // length of drag selection on document + +var viewAll = 0; // 1 to view all slides + handouts +var wantToolbar = 1; // 0 if toolbar isn't wanted +var mouseClickEnabled = true; // enables left click for next slide +var scrollhack = 0; // IE work around for position: fixed + +var helpAnchor; // used for keyboard focus hack in showToolbar() +var helpPage = "http://www.w3.org/Talks/Tools/Slidy/help.html"; +var helpText = "Navigate with mouse click, space bar, Cursor Left/Right, " + + "or Pg Up and Pg Dn. Use S and B to change font size."; + +var sizeIndex = 0; +var sizeAdjustment = 0; +var sizes = new Array("10pt", "12pt", "14pt", "16pt", "18pt", "20pt", + "22pt", "24pt", "26pt", "28pt", "30pt", "32pt"); + +var okayForIncremental = incrementalElementList(); + +// needed for efficient resizing +var lastWidth = 0; +var lastHeight = 0; + +// Needed for cross browser support for relative width/height on +// object elements. The work around is to save width/height attributes +// and then to recompute absolute width/height dimensions on resizing +var objects; + +// updated to language specified by html file +var lang = "en"; + +//var localize = {}; + +// for each language there is an associative array +var strings_es = { + "slide":"pág.", + "help?":"Ayuda", + "contents?":"Ãndice", + "table of contents":"tabla de contenidos", + "Table of Contents":"Tabla de Contenidos", + "restart presentation":"Reiniciar presentación", + "restart?":"Inicio" + }; + +strings_es[helpText] = + "Utilice el ratón, barra espaciadora, teclas Izda/Dcha, " + + "o Re pág y Av pág. Use S y B para cambiar el tamaño de fuente."; + +var strings_ca = { + "slide":"pà g..", + "help?":"Ajuda", + "contents?":"Ãndex", + "table of contents":"taula de continguts", + "Table of Contents":"Taula de Continguts", + "restart presentation":"Reiniciar presentació", + "restart?":"Inici" + }; + +strings_ca[helpText] = + "Utilitzi el ratolÃ, barra espaiadora, tecles Esq./Dta. " + + "o Re pà g y Av pà g. Usi S i B per canviar grandà ria de font."; + +var strings_nl = { + "slide":"pagina", + "help?":"Help?", + "contents?":"Inhoud?", + "table of contents":"inhoudsopgave", + "Table of Contents":"Inhoudsopgave", + "restart presentation":"herstart presentatie", + "restart?":"Herstart?" + }; + +strings_nl[helpText] = + "Navigeer d.m.v. het muis, spatiebar, Links/Rechts toetsen, " + + "of PgUp en PgDn. Gebruik S en B om de karaktergrootte te veranderen."; + +var strings_de = { + "slide":"Seite", + "help?":"Hilfe", + "contents?":"Übersicht", + "table of contents":"Inhaltsverzeichnis", + "Table of Contents":"Inhaltsverzeichnis", + "restart presentation":"Präsentation neu starten", + "restart?":"Neustart" + }; + +strings_de[helpText] = + "Benutzen Sie die Maus, Leerschlag, die Cursortasten links/rechts oder " + + "Page up/Page Down zum Wechseln der Seiten und S und B für die Schriftgrösse."; + +var strings_pl = { + "slide":"slajd", + "help?":"pomoc?", + "contents?":"spis treÅ›ci?", + "table of contents":"spis treÅ›ci", + "Table of Contents":"Spis TreÅ›ci", + "restart presentation":"Restartuj prezentacjÄ™", + "restart?":"restart?" + }; + +strings_pl[helpText] = + "Zmieniaj slajdy klikajÄ…c myszÄ…, naciskajÄ…c spacjÄ™, strzaÅ‚ki lewo/prawo" + + "lub PgUp / PgDn. Użyj klawiszy S i B, aby zmienić rozmiar czczionki."; + +var strings_fr = { + "slide":"page", + "help?":"Aide", + "contents?":"Index", + "table of contents":"table des matières", + "Table of Contents":"Table des matières", + "restart presentation":"Recommencer l'exposé", + "restart?":"Début" + }; + +strings_fr[helpText] = + "Naviguez avec la souris, la barre d'espace, les flèches " + + "gauche/droite ou les touches Pg Up, Pg Dn. Utilisez " + + "les touches S et B pour modifier la taille de la police."; + +var strings_hu = { + "slide":"oldal", + "help?":"segÃtség", + "contents?":"tartalom", + "table of contents":"tartalomjegyzék", + "Table of Contents":"Tartalomjegyzék", + "restart presentation":"bemutató újraindÃtása", + "restart?":"újraindÃtás" + }; + +strings_hu[helpText] = + "Az oldalak közti lépkedéshez kattintson az egérrel, vagy " + + "használja a szóköz, a bal, vagy a jobb nyÃl, illetve a Page Down, " + + "Page Up billentyűket. Az S és a B billentyűkkel változtathatja " + + "a szöveg méretét."; + +var strings_it = { + "slide":"pag.", + "help?":"Aiuto", + "contents?":"Indice", + "table of contents":"indice", + "Table of Contents":"Indice", + "restart presentation":"Ricominciare la presentazione", + "restart?":"Inizio" + }; + +strings_it[helpText] = + "Navigare con mouse, barra spazio, frecce sinistra/destra o " + + "PgUp e PgDn. Usare S e B per cambiare la dimensione dei caratteri."; + +var strings_el = { + "slide":"σελίδα", + "help?":"βοήθεια;", + "contents?":"πεÏιεχόμενα;", + "table of contents":"πίνακας πεÏιεχομÎνων", + "Table of Contents":"Πίνακας ΠεÏιεχομÎνων", + "restart presentation":"επανεκκίνηση παÏουσίασης", + "restart?":"επανεκκίνηση;" + }; + +strings_el[helpText] = + "Πλοηγηθείτε με το κλίκ του ποντικιοÏ, το space, τα βÎλη αÏιστεÏά/δεξιά, " + + "ή Page Up και Page Down. ΧÏησιμοποιήστε τα πλήκτÏα S και B για να αλλάξετε " + + "το μÎγεθος της γÏαμματοσειÏάς."; + +var strings_ja = { + "slide":"スライド", + "help?":"ヘルプ", + "contents?":"目次", + "table of contents":"目次を表示", + "Table of Contents":"目次", + "restart presentation":"最åˆã‹ã‚‰å†ç”Ÿ", + "restart?":"最åˆã‹ã‚‰" +}; + +strings_ja[helpText] = + "マウス左クリック ・ スペース ・ å·¦å³ã‚ー " + + "ã¾ãŸã¯ Page Up ・ Page Downã§æ“作, S ・ Bã§ãƒ•ォントサイズ変更"; + + +// each such language array is declared in the localize array +// used indirectly as in help.innerHTML = "help".localize(); +var localize = { + "es":strings_es, + "ca":strings_ca, + "nl":strings_nl, + "de":strings_de, + "pl":strings_pl, + "fr":strings_fr, + "hu":strings_hu, + "it":strings_it, + "el":strings_el, + "jp":strings_ja + }; + +/* general initialization */ +function startup() +{ + // find human language from html element + // for use in localizing strings + lang = document.body.parentNode.getAttribute("lang"); + + if (!lang) + lang = document.body.parentNode.getAttribute("xml:lang"); + + if (!lang) + lang = "en"; + + document.body.style.visibility = "visible"; + title = document.title; + toolbar = addToolbar(); + wrapImplicitSlides(); + slides = collectSlides(); + notes = collectNotes(); + objects = document.body.getElementsByTagName("object"); + backgrounds = collectBackgrounds(); + patchAnchors(); + + slidenum = findSlideNumber(location.href); + window.offscreenbuffering = true; + sizeAdjustment = findSizeAdjust(); + hideImageToolbar(); // suppress IE image toolbar popup + initOutliner(); // activate fold/unfold support + + if (slides.length > 0) + { + var slide = slides[slidenum]; + slide.style.position = "absolute"; + + if (slidenum > 0) + { + setVisibilityAllIncremental("visible"); + lastShown = previousIncrementalItem(null); + setEosStatus(true); + } + else + { + lastShown = null; + setVisibilityAllIncremental("hidden"); + setEosStatus(!nextIncrementalItem(lastShown)); + } + + setLocation(); + } + + toc = tableOfContents(); + hideTableOfContents(); + + // bind event handlers + document.onclick = mouseButtonClick; + document.onmouseup = mouseButtonUp; + document.onkeydown = keyDown; + window.onresize = resized; + window.onscroll = scrolled; + singleSlideView(); + + setLocation(); + resized(); + + if (ie7) + setTimeout("ieHack()", 100); + + showToolbar(); +} + +// add localize method to all strings for use +// as in help.innerHTML = "help".localize(); +String.prototype.localize = function() +{ + if (this == "") + return this; + + // try full language code, e.g. en-US + var s, lookup = localize[lang]; + + if (lookup) + { + s = lookup[this]; + + if (s) + return s; + } + + // try en if undefined for en-US + var lg = lang.split("-"); + + if (lg.length > 1) + { + lookup = localize[lg[0]]; + + if (lookup) + { + s = lookup[this]; + + if (s) + return s; + } + } + + // otherwise string as is + return this; +} + +// suppress IE's image toolbar pop up +function hideImageToolbar() +{ + if (!ns_pos) + { + var images = document.getElementsByTagName("IMG"); + + for (var i = 0; i < images.length; ++i) + images[i].setAttribute("galleryimg", "no"); + } +} + +// hack to persuade IE to compute correct document height +// as needed for simulating fixed positioning of toolbar +function ieHack() +{ + window.resizeBy(0,-1); + window.resizeBy(0, 1); +} + +// Firefox reload SVG bug work around +function reload(e) +{ + if (!e) + var e = window.event; + + hideBackgrounds(); + setTimeout("document.reload();", 100); + + stopPropagation(e); + e.cancel = true; + e.returnValue = false; + + return false; +} + +// Safari and Konqueror don't yet support getComputedStyle() +// and they always reload page when location.href is updated +function isKHTML() +{ + var agent = navigator.userAgent; + return (agent.indexOf("KHTML") >= 0 ? true : false); +} + +function resized() +{ + var width = 0; + + if ( typeof( window.innerWidth ) == 'number' ) + width = window.innerWidth; // Non IE browser + else if (document.documentElement && document.documentElement.clientWidth) + width = document.documentElement.clientWidth; // IE6 + else if (document.body && document.body.clientWidth) + width = document.body.clientWidth; // IE4 + + var height = 0; + + if ( typeof( window.innerHeight ) == 'number' ) + height = window.innerHeight; // Non IE browser + else if (document.documentElement && document.documentElement.clientHeight) + height = document.documentElement.clientHeight; // IE6 + else if (document.body && document.body.clientHeight) + height = document.body.clientHeight; // IE4 + + if (height && (width/height > 1.05*1024/768)) + { + width = height * 1024.0/768; + } + + // IE fires onresize even when only font size is changed! + // so we do a check to avoid blocking < and > actions + if (width != lastWidth || height != lastHeight) + { + if (width >= 1100) + sizeIndex = 5; // 4 + else if (width >= 1000) + sizeIndex = 4; // 3 + else if (width >= 800) + sizeIndex = 3; // 2 + else if (width >= 600) + sizeIndex = 2; // 1 + else if (width) + sizeIndex = 0; + + // add in font size adjustment from meta element e.g. + // <meta name="font-size-adjustment" content="-2" /> + // useful when slides have too much content ;-) + + if (0 <= sizeIndex + sizeAdjustment && + sizeIndex + sizeAdjustment < sizes.length) + sizeIndex = sizeIndex + sizeAdjustment; + + // enables cross browser use of relative width/height + // on object elements for use with SVG and Flash media + adjustObjectDimensions(width, height); + + document.body.style.fontSize = sizes[sizeIndex]; + + lastWidth = width; + lastHeight = height; + + // force reflow to work around Mozilla bug + //if (ns_pos) + { + var slide = slides[slidenum]; + hideSlide(slide); + showSlide(slide); + } + + // force correct positioning of toolbar + refreshToolbar(200); + } +} + +function scrolled() +{ + if (toolbar && !ns_pos && !ie7) + { + hackoffset = scrollXOffset(); + // hide toolbar + toolbar.style.display = "none"; + + // make it reappear later + if (scrollhack == 0 && !viewAll) + { + setTimeout(showToolbar, 1000); + scrollhack = 1; + } + } +} + +// used to ensure IE refreshes toolbar in correct position +function refreshToolbar(interval) +{ + if (!ns_pos && !ie7) + { + hideToolbar(); + setTimeout(showToolbar, interval); + } +} + +// restores toolbar after short delay +function showToolbar() +{ + if (wantToolbar) + { + if (!ns_pos) + { + // adjust position to allow for scrolling + var xoffset = scrollXOffset(); + toolbar.style.left = xoffset; + toolbar.style.right = xoffset; + + // determine vertical scroll offset + //var yoffset = scrollYOffset(); + + // bottom is doc height - window height - scroll offset + //var bottom = documentHeight() - lastHeight - yoffset + + //if (yoffset > 0 || documentHeight() > lastHeight) + // bottom += 16; // allow for height of scrollbar + + toolbar.style.bottom = 0; //bottom; + } + + toolbar.style.display = "block"; + toolbar.style.visibility = "visible"; + } + + scrollhack = 0; + + + // set the keyboard focus to the help link on the + // toolbar to ensure that document has the focus + // IE doesn't always work with window.focus() + // and this hack has benefit of Enter for help + + try + { + if (!opera) + helpAnchor.focus(); + } + catch (e) + { + } +} + +function test() +{ + var s = "docH: " + documentHeight() + + " winH: " + lastHeight + + " yoffset: " + scrollYOffset() + + " toolbot: " + (documentHeight() - lastHeight - scrollYOffset()); + + //alert(s); + + var slide = slides[slidenum]; + // IE getAttribute requires "class" to be "className" + var name = ns_pos ? "class" : "className"; + var style = (slide.currentStyle ? slide.currentStyle["backgroundColor"] : + document.defaultView.getComputedStyle(slide, '').getPropertyValue("background-color")); + alert("class='" + slide.getAttribute(name) + "' backgroundColor: " + style); +} + +function hideToolbar() +{ + toolbar.style.display = "none"; + toolbar.style.visibility = "hidden"; + window.focus(); +} + +// invoked via F key +function toggleToolbar() +{ + if (!viewAll) + { + if (toolbar.style.display == "none") + { + toolbar.style.display = "block"; + toolbar.style.visibility = "visible"; + wantToolbar = 1; + } + else + { + toolbar.style.display = "none"; + toolbar.style.visibility = "hidden"; + wantToolbar = 0; + } + } +} + +function scrollXOffset() +{ + if (window.pageXOffset) + return self.pageXOffset; + + if (document.documentElement && + document.documentElement.scrollLeft) + return document.documentElement.scrollLeft; + + if (document.body) + return document.body.scrollLeft; + + return 0; +} + + +function scrollYOffset() +{ + if (window.pageYOffset) + return self.pageYOffset; + + if (document.documentElement && + document.documentElement.scrollTop) + return document.documentElement.scrollTop; + + if (document.body) + return document.body.scrollTop; + + return 0; +} + +// looking for a way to determine height of slide content +// the slide itself is set to the height of the window +function optimizeFontSize() +{ + var slide = slides[slidenum]; + + //var dh = documentHeight(); //getDocHeight(document); + var dh = slide.scrollHeight; + var wh = getWindowHeight(); + var u = 100 * dh / wh; + + alert("window utilization = " + u + "% (doc " + + dh + " win " + wh + ")"); +} + +function getDocHeight(doc) // from document object +{ + if (!doc) + doc = document; + + if (doc && doc.body && doc.body.offsetHeight) + return doc.body.offsetHeight; // ns/gecko syntax + + if (doc && doc.body && doc.body.scrollHeight) + return doc.body.scrollHeight; + + alert("couldn't determine document height"); +} + +function getWindowHeight() +{ + if ( typeof( window.innerHeight ) == 'number' ) + return window.innerHeight; // Non IE browser + + if (document.documentElement && document.documentElement.clientHeight) + return document.documentElement.clientHeight; // IE6 + + if (document.body && document.body.clientHeight) + return document.body.clientHeight; // IE4 +} + + + +function documentHeight() +{ + var sh, oh; + + sh = document.body.scrollHeight; + oh = document.body.offsetHeight; + + if (sh && oh) + { + return (sh > oh ? sh : oh); + } + + // no idea! + return 0; +} + +function smaller() +{ + if (sizeIndex > 0) + { + --sizeIndex; + } + + toolbar.style.display = "none"; + document.body.style.fontSize = sizes[sizeIndex]; + var slide = slides[slidenum]; + hideSlide(slide); + showSlide(slide); + setTimeout(showToolbar, 300); +} + +function bigger() +{ + if (sizeIndex < sizes.length - 1) + { + ++sizeIndex; + } + + toolbar.style.display = "none"; + document.body.style.fontSize = sizes[sizeIndex]; + var slide = slides[slidenum]; + hideSlide(slide); + showSlide(slide); + setTimeout(showToolbar, 300); +} + +// enables cross browser use of relative width/height +// on object elements for use with SVG and Flash media +// with thanks to Ivan Herman for the suggestion +function adjustObjectDimensions(width, height) +{ + for( var i = 0; i < objects.length; i++ ) + { + var obj = objects[i]; + var mimeType = obj.getAttribute("type"); + + if (mimeType == "image/svg+xml" || mimeType == "application/x-shockwave-flash") + { + if ( !obj.initialWidth ) + obj.initialWidth = obj.getAttribute("width"); + + if ( !obj.initialHeight ) + obj.initialHeight = obj.getAttribute("height"); + + if ( obj.initialWidth && obj.initialWidth.charAt(obj.initialWidth.length-1) == "%" ) + { + var w = parseInt(obj.initialWidth.slice(0, obj.initialWidth.length-1)); + var newW = width * (w/100.0); + obj.setAttribute("width",newW); + } + + if ( obj.initialHeight && obj.initialHeight.charAt(obj.initialHeight.length-1) == "%" ) + { + var h = parseInt(obj.initialHeight.slice(0, obj.initialHeight.length-1)); + var newH = height * (h/100.0); + obj.setAttribute("height", newH); + } + } + } +} + +function cancel(event) +{ + if (event) + { + event.cancel = true; + event.returnValue = false; + + if (event.preventDefault) + event.preventDefault(); + } + + return false; +} + +// See e.g. http://www.quirksmode.org/js/events/keys.html for keycodes +function keyDown(event) +{ + var key; + + if (!event) + var event = window.event; + + // kludge around NS/IE differences + if (window.event) + key = window.event.keyCode; + else if (event.which) + key = event.which; + else + return true; // Yikes! unknown browser + + // ignore event if key value is zero + // as for alt on Opera and Konqueror + if (!key) + return true; + + // check for concurrent control/command/alt key + // but are these only present on mouse events? + + if (event.ctrlKey || event.altKey || event.metaKey) + return true; + + // dismiss table of contents if visible + if (isShownToc() && key != 9 && key != 16 && key != 38 && key != 40) + { + hideTableOfContents(); + + if (key == 27 || key == 84 || key == 67) + return cancel(event); + } + + if (key == 34) // Page Down + { + nextSlide(false); + return cancel(event); + } + else if (key == 33) // Page Up + { + previousSlide(false); + return cancel(event); + } + else if (key == 32) // space bar + { + nextSlide(true); + return cancel(event); + } + else if (key == 37) // Left arrow + { + previousSlide(!event.shiftKey); + return cancel(event); + } + else if (key == 36) // Home + { + firstSlide(); + return cancel(event); + } + else if (key == 35) // End + { + lastSlide(); + return cancel(event); + } + else if (key == 39) // Right arrow + { + nextSlide(!event.shiftKey); + return cancel(event); + } + else if (key == 13) // Enter + { + if (outline) + { + if (outline.visible) + fold(outline); + else + unfold(outline); + + return cancel(event); + } + } + else if (key == 188) // < for smaller fonts + { + smaller(); + return cancel(event); + } + else if (key == 190) // > for larger fonts + { + bigger(); + return cancel(event); + } + else if (key == 189 || key == 109) // - for smaller fonts + { + smaller(); + return cancel(event); + } + else if (key == 187 || key == 191 || key == 107) // = + for larger fonts + { + bigger(); + return cancel(event); + } + else if (key == 83) // S for smaller fonts + { + smaller(); + return cancel(event); + } + else if (key == 66) // B for larger fonts + { + bigger(); + return cancel(event); + } + else if (key == 90) // Z for last slide + { + lastSlide(); + return cancel(event); + } + else if (key == 70) // F for toggle toolbar + { + toggleToolbar(); + return cancel(event); + } + else if (key == 65) // A for toggle view single/all slides + { + toggleView(); + return cancel(event); + } + else if (key == 75) // toggle action of left click for next page + { + mouseClickEnabled = !mouseClickEnabled; + alert((mouseClickEnabled ? "enabled" : "disabled") + " mouse click advance"); + return cancel(event); + } + else if (key == 84 || key == 67) // T or C for table of contents + { + if (toc) + showTableOfContents(); + + return cancel(event); + } + else if (key == 72) // H for help + { + window.location = helpPage; + return cancel(event); + } + + //else if (key == 93) // Windows menu key + //alert("lastShown is " + lastShown); + //else alert("key code is "+ key); + + + return true; +} + +// make note of length of selected text +// as this evaluates to zero in click event +function mouseButtonUp(e) +{ + selectedTextLen = getSelectedText().length; +} + +// right mouse button click is reserved for context menus +// it is more reliable to detect rightclick than leftclick +function mouseButtonClick(e) +{ + var rightclick = false; + var leftclick = false; + var middleclick = false; + var target; + + if (!e) + var e = window.event; + + if (e.target) + target = e.target; + else if (e.srcElement) + target = e.srcElement; + + // work around Safari bug + if (target.nodeType == 3) + target = target.parentNode; + + if (e.which) // all browsers except IE + { + leftclick = (e.which == 1); + middleclick = (e.which == 2); + rightclick = (e.which == 3); + } + else if (e.button) + { + // Konqueror gives 1 for left, 4 for middle + // IE6 gives 0 for left and not 1 as I expected + + if (e.button == 4) + middleclick = true; + + // all browsers agree on 2 for right button + rightclick = (e.button == 2); + } + else leftclick = true; + + // dismiss table of contents + hideTableOfContents(); + + if (selectedTextLen > 0) + { + stopPropagation(e); + e.cancel = true; + e.returnValue = false; + return false; + } + + // check if target is something that probably want's clicks + // e.g. embed, object, input, textarea, select, option + + if (mouseClickEnabled && leftclick && + target.nodeName != "EMBED" && + target.nodeName != "OBJECT" && + target.nodeName != "INPUT" && + target.nodeName != "TEXTAREA" && + target.nodeName != "SELECT" && + target.nodeName != "OPTION") + { + nextSlide(true); + stopPropagation(e); + e.cancel = true; + e.returnValue = false; + } +} + +function previousSlide(incremental) +{ + if (!viewAll) + { + var slide; + + if ((incremental || slidenum == 0) && lastShown != null) + { + lastShown = hidePreviousItem(lastShown); + setEosStatus(false); + } + else if (slidenum > 0) + { + slide = slides[slidenum]; + hideSlide(slide); + + slidenum = slidenum - 1; + slide = slides[slidenum]; + setVisibilityAllIncremental("visible"); + lastShown = previousIncrementalItem(null); + setEosStatus(true); + showSlide(slide); + } + + setLocation(); + + if (!ns_pos) + refreshToolbar(200); + } +} + +function nextSlide(incremental) +{ + if (!viewAll) + { + var slide, last = lastShown; + + if (incremental || slidenum == slides.length - 1) + lastShown = revealNextItem(lastShown); + + if ((!incremental || lastShown == null) && slidenum < slides.length - 1) + { + slide = slides[slidenum]; + hideSlide(slide); + + slidenum = slidenum + 1; + slide = slides[slidenum]; + lastShown = null; + setVisibilityAllIncremental("hidden"); + showSlide(slide); + } + else if (!lastShown) + { + if (last && incremental) + lastShown = last; + } + + setLocation(); + + setEosStatus(!nextIncrementalItem(lastShown)); + + if (!ns_pos) + refreshToolbar(200); + } +} + +// to first slide with nothing revealed +// i.e. state at start of presentation +function firstSlide() +{ + if (!viewAll) + { + var slide; + + if (slidenum != 0) + { + slide = slides[slidenum]; + hideSlide(slide); + + slidenum = 0; + slide = slides[slidenum]; + lastShown = null; + setVisibilityAllIncremental("hidden"); + showSlide(slide); + } + + setEosStatus(!nextIncrementalItem(lastShown)); + setLocation(); + } +} + + +// to last slide with everything revealed +// i.e. state at end of presentation +function lastSlide() +{ + if (!viewAll) + { + var slide; + + lastShown = null; //revealNextItem(lastShown); + + if (lastShown == null && slidenum < slides.length - 1) + { + slide = slides[slidenum]; + hideSlide(slide); + slidenum = slides.length - 1; + slide = slides[slidenum]; + setVisibilityAllIncremental("visible"); + lastShown = previousIncrementalItem(null); + + showSlide(slide); + } + else + { + setVisibilityAllIncremental("visible"); + lastShown = previousIncrementalItem(null); + } + + setEosStatus(true); + setLocation(); + } +} + +function setEosStatus(state) +{ + if (eos) + eos.style.color = (state ? "rgb(240,240,240)" : "red"); +} + +function showSlide(slide) +{ + syncBackground(slide); + window.scrollTo(0,0); + slide.style.visibility = "visible"; + slide.style.display = "block"; +} + +function hideSlide(slide) +{ + slide.style.visibility = "hidden"; + slide.style.display = "none"; +} + +function beforePrint() +{ + showAllSlides(); + hideToolbar(); +} + +function afterPrint() +{ + if (!viewAll) + { + singleSlideView(); + showToolbar(); + } +} + +function printSlides() +{ + beforePrint(); + window.print(); + afterPrint(); +} + +function toggleView() +{ + if (viewAll) + { + singleSlideView(); + showToolbar(); + viewAll = 0; + } + else + { + showAllSlides(); + hideToolbar(); + viewAll = 1; + } +} + +// prepare for printing +function showAllSlides() +{ + var slide; + + for (var i = 0; i < slides.length; ++i) + { + slide = slides[i]; + + slide.style.position = "relative"; + slide.style.borderTopStyle = "solid"; + slide.style.borderTopWidth = "thin"; + slide.style.borderTopColor = "black"; + + try { + if (i == 0) + slide.style.pageBreakBefore = "avoid"; + else + slide.style.pageBreakBefore = "always"; + } + catch (e) + { + //do nothing + } + + setVisibilityAllIncremental("visible"); + showSlide(slide); + } + + var note; + + for (var i = 0; i < notes.length; ++i) + { + showSlide(notes[i]); + } + + // no easy way to render background under each slide + // without duplicating the background divs for each slide + // therefore hide backgrounds to avoid messing up slides + hideBackgrounds(); +} + +// restore after printing +function singleSlideView() +{ + var slide; + + for (var i = 0; i < slides.length; ++i) + { + slide = slides[i]; + + slide.style.position = "absolute"; + + if (i == slidenum) + { + slide.style.borderStyle = "none"; + showSlide(slide); + } + else + { + slide.style.borderStyle = "none"; + hideSlide(slide); + } + } + + setVisibilityAllIncremental("visible"); + lastShown = previousIncrementalItem(null); + + var note; + + for (var i = 0; i < notes.length; ++i) + { + hideSlide(notes[i]); + } +} + +// the string str is a whitespace separated list of tokens +// test if str contains a particular token, e.g. "slide" +function hasToken(str, token) +{ + if (str) + { + // define pattern as regular expression + var pattern = /\w+/g; + + // check for matches + // place result in array + var result = str.match(pattern); + + // now check if desired token is present + for (var i = 0; i < result.length; i++) + { + if (result[i] == token) + return true; + } + } + + return false; +} + +function getClassList(element) +{ + if (typeof window.pageYOffset =='undefined') + return element.getAttribute("className"); + + return element.getAttribute("class"); +} + +function hasClass(element, name) +{ + var regexp = new RegExp("(^| )" + name + "\W*"); + + if (regexp.test(getClassList(element))) + return true; + + return false; + +} + +function removeClass(element, name) +{ + // IE getAttribute requires "class" to be "className" + var clsname = ns_pos ? "class" : "className"; + var clsval = element.getAttribute(clsname); + + var regexp = new RegExp("(^| )" + name + "\W*"); + + if (clsval) + { + clsval = clsval.replace(regexp, ""); + element.setAttribute(clsname, clsval); + } +} + +function addClass(element, name) +{ + if (!hasClass(element, name)) + { + // IE getAttribute requires "class" to be "className" + var clsname = ns_pos ? "class" : "className"; + var clsval = element.getAttribute(clsname); + element.setAttribute(clsname, (clsval ? clsval + " " + name : name)); + } +} + +// wysiwyg editors make it hard to use div elements +// e.g. amaya loses the div when you copy and paste +// this function wraps div elements around implicit +// slides which start with an h1 element and continue +// up to the next heading or div element +function wrapImplicitSlides() +{ + var i, heading, node, next, div; + var headings = document.getElementsByTagName("h1"); + + if (!headings) + return; + + for (i = 0; i < headings.length; ++i) + { + heading = headings[i]; + + if (heading.parentNode != document.body) + continue; + + node = heading.nextSibling; + + div = document.createElement("div"); + div.setAttribute((ns_pos ? "class" : "className"), "slide"); + document.body.replaceChild(div, heading); + div.appendChild(heading); + + while (node) + { + if (node.nodeType == 1 && // an element + (node.nodeName == "H1" || + node.nodeName == "h1" || + node.nodeName == "DIV" || + node.nodeName == "div")) + break; + + next = node.nextSibling; + node = document.body.removeChild(node); + div.appendChild(node); + node = next; + } + } +} + +// return new array of all slides +function collectSlides() +{ + var slides = new Array(); + var divs = document.body.getElementsByTagName("div"); + + for (var i = 0; i < divs.length; ++i) + { + div = divs.item(i); + + if (hasClass(div, "slide")) + { + // add slide to collection + slides[slides.length] = div; + + // hide each slide as it is found + div.style.display = "none"; + div.style.visibility = "hidden"; + + // add dummy <br/> at end for scrolling hack + var node1 = document.createElement("br"); + div.appendChild(node1); + var node2 = document.createElement("br"); + div.appendChild(node2); + } + else if (hasClass(div, "background")) + { // work around for Firefox SVG reload bug + // which otherwise replaces 1st SVG graphic with 2nd + div.style.display = "block"; + } + } + + return slides; +} + +// return new array of all <div class="handout"> +function collectNotes() +{ + var notes = new Array(); + var divs = document.body.getElementsByTagName("div"); + + for (var i = 0; i < divs.length; ++i) + { + div = divs.item(i); + + if (hasClass(div, "handout")) + { + // add slide to collection + notes[notes.length] = div; + + // hide handout notes as they are found + div.style.display = "none"; + div.style.visibility = "hidden"; + } + } + + return notes; +} + +// return new array of all <div class="background"> +// including named backgrounds e.g. class="background titlepage" +function collectBackgrounds() +{ + var backgrounds = new Array(); + var divs = document.body.getElementsByTagName("div"); + + for (var i = 0; i < divs.length; ++i) + { + div = divs.item(i); + + if (hasClass(div, "background")) + { + // add slide to collection + backgrounds[backgrounds.length] = div; + + // hide named backgrounds as they are found + // e.g. class="background epilog" + if (getClassList(div) != "background") + { + div.style.display = "none"; + div.style.visibility = "hidden"; + } + } + } + + return backgrounds; +} + +// show just the backgrounds pertinent to this slide +function syncBackground(slide) +{ + var background; + var bgColor; + + if (slide.currentStyle) + bgColor = slide.currentStyle["backgroundColor"]; + else if (document.defaultView) + { + var styles = document.defaultView.getComputedStyle(slide,null); + + if (styles) + bgColor = styles.getPropertyValue("background-color"); + else // broken implementation probably due Safari or Konqueror + { + //alert("defective implementation of getComputedStyle()"); + bgColor = "transparent"; + } + } + else + bgColor == "transparent"; + + if (bgColor == "transparent") + { + var slideClass = getClassList(slide); + + for (var i = 0; i < backgrounds.length; i++) + { + background = backgrounds[i]; + + var bgClass = getClassList(background); + + if (matchingBackground(slideClass, bgClass)) + { + background.style.display = "block"; + background.style.visibility = "visible"; + } + else + { + background.style.display = "none"; + background.style.visibility = "hidden"; + } + } + } + else // forcibly hide all backgrounds + hideBackgrounds(); +} + +function hideBackgrounds() +{ + for (var i = 0; i < backgrounds.length; i++) + { + background = backgrounds[i]; + background.style.display = "none"; + background.style.visibility = "hidden"; + } +} + +// compare classes for slide and background +function matchingBackground(slideClass, bgClass) +{ + if (bgClass == "background") + return true; + + // define pattern as regular expression + var pattern = /\w+/g; + + // check for matches and place result in array + var result = slideClass.match(pattern); + + // now check if desired name is present for background + for (var i = 0; i < result.length; i++) + { + if (hasToken(bgClass, result[i])) + return true; + } + + return false; +} + +// left to right traversal of root's content +function nextNode(root, node) +{ + if (node == null) + return root.firstChild; + + if (node.firstChild) + return node.firstChild; + + if (node.nextSibling) + return node.nextSibling; + + for (;;) + { + node = node.parentNode; + + if (!node || node == root) + break; + + if (node && node.nextSibling) + return node.nextSibling; + } + + return null; +} + +// right to left traversal of root's content +function previousNode(root, node) +{ + if (node == null) + { + node = root.lastChild; + + if (node) + { + while (node.lastChild) + node = node.lastChild; + } + + return node; + } + + if (node.previousSibling) + { + node = node.previousSibling; + + while (node.lastChild) + node = node.lastChild; + + return node; + } + + if (node.parentNode != root) + return node.parentNode; + + return null; +} + +// HTML elements that can be used with class="incremental" +// note that you can also put the class on containers like +// up, ol, dl, and div to make their contents appear +// incrementally. Upper case is used since this is what +// browsers report for HTML node names (text/html). +function incrementalElementList() +{ + var inclist = new Array(); + inclist["P"] = true; + inclist["PRE"] = true; + inclist["LI"] = true; + inclist["BLOCKQUOTE"] = true; + inclist["DT"] = true; + inclist["DD"] = true; + inclist["H2"] = true; + inclist["H3"] = true; + inclist["H4"] = true; + inclist["H5"] = true; + inclist["H6"] = true; + inclist["SPAN"] = true; + inclist["ADDRESS"] = true; + inclist["TABLE"] = true; + inclist["TR"] = true; + inclist["TH"] = true; + inclist["TD"] = true; + inclist["IMG"] = true; + inclist["OBJECT"] = true; + return inclist; +} + +function nextIncrementalItem(node) +{ + var slide = slides[slidenum]; + + for (;;) + { + node = nextNode(slide, node); + + if (node == null || node.parentNode == null) + break; + + if (node.nodeType == 1) // ELEMENT + { + if (node.nodeName == "BR") + continue; + + if (hasClass(node, "incremental") + && okayForIncremental[node.nodeName]) + return node; + + if (hasClass(node.parentNode, "incremental") + && !hasClass(node, "non-incremental")) + return node; + } + } + + return node; +} + +function previousIncrementalItem(node) +{ + var slide = slides[slidenum]; + + for (;;) + { + node = previousNode(slide, node); + + if (node == null || node.parentNode == null) + break; + + if (node.nodeType == 1) + { + if (node.nodeName == "BR") + continue; + + if (hasClass(node, "incremental") + && okayForIncremental[node.nodeName]) + return node; + + if (hasClass(node.parentNode, "incremental") + && !hasClass(node, "non-incremental")) + return node; + } + } + + return node; +} + +// set visibility for all elements on current slide with +// a parent element with attribute class="incremental" +function setVisibilityAllIncremental(value) +{ + var node = nextIncrementalItem(null); + + while (node) + { + node.style.visibility = value; + node = nextIncrementalItem(node); + } +} + +// reveal the next hidden item on the slide +// node is null or the node that was last revealed +function revealNextItem(node) +{ + node = nextIncrementalItem(node); + + if (node && node.nodeType == 1) // an element + node.style.visibility = "visible"; + + return node; +} + + +// exact inverse of revealNextItem(node) +function hidePreviousItem(node) +{ + if (node && node.nodeType == 1) // an element + node.style.visibility = "hidden"; + + return previousIncrementalItem(node); +} + + +/* set click handlers on all anchors */ +function patchAnchors() +{ + var anchors = document.body.getElementsByTagName("a"); + + for (var i = 0; i < anchors.length; ++i) + { + anchors[i].onclick = clickedAnchor; + } +} + +function clickedAnchor(e) +{ + if (!e) + var e = window.event; + + // compare this.href with location.href + // for link to another slide in this doc + + if (pageAddress(this.href) == pageAddress(location.href)) + { + // yes, so find new slide number + var newslidenum = findSlideNumber(this.href); + + if (newslidenum != slidenum) + { + slide = slides[slidenum]; + hideSlide(slide); + slidenum = newslidenum; + slide = slides[slidenum]; + showSlide(slide); + setLocation(); + } + } + else if (this.target == null) + location.href = this.href; + + this.blur(); + stopPropagation(e); +} + +function pageAddress(uri) +{ + var i = uri.indexOf("#"); + + // check if anchor is entire page + + if (i < 0) + return uri; // yes + + return uri.substr(0, i); +} + +function showSlideNumber() +{ + slideNumElement.innerHTML = "slide".localize() + " " + + (slidenum + 1) + "/" + slides.length; +} + +function setLocation() +{ + var uri = pageAddress(location.href); + + //if (slidenum > 0) + uri = uri + "#(" + (slidenum+1) + ")"; + + if (uri != location.href && !khtml) + location.href = uri; + + document.title = title + " (" + (slidenum+1) + ")"; + //document.title = (slidenum+1) + ") " + slideName(slidenum); + + showSlideNumber(); +} + +// find current slide based upon location +// first find target anchor and then look +// for associated div element enclosing it +// finally map that to slide number +function findSlideNumber(uri) +{ + // first get anchor from page location + + var i = uri.indexOf("#"); + + // check if anchor is entire page + + if (i < 0) + return 0; // yes + + var anchor = unescape(uri.substr(i+1)); + + // now use anchor as XML ID to find target + var target = document.getElementById(anchor); + + if (!target) + { + // does anchor look like "(2)" for slide 2 ?? + // where first slide is (1) + var re = /\((\d)+\)/; + + if (anchor.match(re)) + { + var num = parseInt(anchor.substring(1, anchor.length-1)); + + if (num > slides.length) + num = 1; + + if (--num < 0) + num = 0; + + return num; + } + + // accept [2] for backwards compatibility + re = /\[(\d)+\]/; + + if (anchor.match(re)) + { + var num = parseInt(anchor.substring(1, anchor.length-1)); + + if (num > slides.length) + num = 1; + + if (--num < 0) + num = 0; + + return num; + } + + // oh dear unknown anchor + return 0; + } + + // search for enclosing slide + + while (true) + { + // browser coerces html elements to uppercase! + if (target.nodeName.toLowerCase() == "div" && + hasClass(target, "slide")) + { + // found the slide element + break; + } + + // otherwise try parent element if any + + target = target.parentNode; + + if (!target) + { + return 0; // no luck! + } + }; + + for (i = 0; i < slides.length; ++i) + { + if (slides[i] == target) + return i; // success + } + + // oh dear still no luck + return 0; +} + +// find slide name from first h1 element +// default to document title + slide number +function slideName(index) +{ + var name = null; + var slide = slides[index]; + + var heading = findHeading(slide); + + if (heading) + name = extractText(heading); + + if (!name) + name = title + "(" + (index + 1) + ")"; + + name.replace(/\&/g, "&"); + name.replace(/\</g, "<"); + name.replace(/\>/g, ">"); + + return name; +} + +// find first h1 element in DOM tree +function findHeading(node) +{ + if (!node || node.nodeType != 1) + return null; + + if (node.nodeName == "H1" || node.nodeName == "h1") + return node; + + var child = node.firstChild; + + while (child) + { + node = findHeading(child); + + if (node) + return node; + + child = child.nextSibling; + } + + return null; +} + +// recursively extract text from DOM tree +function extractText(node) +{ + if (!node) + return ""; + + // text nodes + if (node.nodeType == 3) + return node.nodeValue; + + // elements + if (node.nodeType == 1) + { + node = node.firstChild; + var text = ""; + + while (node) + { + text = text + extractText(node); + node = node.nextSibling; + } + + return text; + } + + return ""; +} + + +// find copyright text from meta element +function findCopyright() +{ + var name, content; + var meta = document.getElementsByTagName("meta"); + + for (var i = 0; i < meta.length; ++i) + { + name = meta[i].getAttribute("name"); + content = meta[i].getAttribute("content"); + + if (name == "copyright") + return content; + } + + return null; +} + +function findSizeAdjust() +{ + var name, content, offset; + var meta = document.getElementsByTagName("meta"); + + for (var i = 0; i < meta.length; ++i) + { + name = meta[i].getAttribute("name"); + content = meta[i].getAttribute("content"); + + if (name == "font-size-adjustment") + return 1 * content; + } + + return 0; +} + +function addToolbar() +{ + var slideCounter, page; + + var toolbar = createElement("div"); + toolbar.setAttribute("class", "toolbar"); + + if (ns_pos) // a reasonably behaved browser + { + var right = document.createElement("div"); + right.setAttribute("style", "float: right; text-align: right"); + + slideCounter = document.createElement("div") + slideCounter.innerHTML = "slide".localize() + " n/m"; + right.appendChild(slideCounter); + toolbar.appendChild(right); + + var left = document.createElement("div"); + left.setAttribute("style", "text-align: left"); + + // global end of slide indicator + eos = document.createElement("span"); + eos.innerHTML = "* "; + left.appendChild(eos); + + var help = document.createElement("a"); + help.setAttribute("href", helpPage); + help.setAttribute("title", helpText.localize()); + help.innerHTML = "help?".localize(); + left.appendChild(help); + helpAnchor = help; // save for focus hack + + var gap1 = document.createTextNode(" "); + left.appendChild(gap1); + + var contents = document.createElement("a"); + contents.setAttribute("href", "javascript:toggleTableOfContents()"); + contents.setAttribute("title", "table of contents".localize()); + contents.innerHTML = "contents?".localize(); + left.appendChild(contents); + + var gap2 = document.createTextNode(" "); + left.appendChild(gap2); + + var i = location.href.indexOf("#"); + + // check if anchor is entire page + + if (i > 0) + page = location.href.substr(0, i); + else + page = location.href; + + var start = document.createElement("a"); + start.setAttribute("href", page); + start.setAttribute("title", "restart presentation".localize()); + start.innerHTML = "restart?".localize(); +// start.setAttribute("href", "javascript:printSlides()"); +// start.setAttribute("title", "print all slides".localize()); +// start.innerHTML = "print!".localize(); + left.appendChild(start); + + var copyright = findCopyright(); + + if (copyright) + { + var span = document.createElement("span"); + span.innerHTML = copyright; + span.style.color = "black"; + span.style.marginLeft = "4em"; + left.appendChild(span); + } + + toolbar.appendChild(left); + } + else // IE so need to work around its poor CSS support + { + toolbar.style.position = (ie7 ? "fixed" : "absolute"); + toolbar.style.zIndex = "200"; + toolbar.style.width = "99.9%"; + toolbar.style.height = "1.2em"; + toolbar.style.top = "auto"; + toolbar.style.bottom = "0"; + toolbar.style.left = "0"; + toolbar.style.right = "0"; + toolbar.style.textAlign = "left"; + toolbar.style.fontSize = "60%"; + toolbar.style.color = "red"; + toolbar.borderWidth = 0; + toolbar.style.background = "rgb(240,240,240)"; + + // would like to have help text left aligned + // and page counter right aligned, floating + // div's don't work, so instead use nested + // absolutely positioned div's. + + var sp = document.createElement("span"); + sp.innerHTML = " * "; + toolbar.appendChild(sp); + eos = sp; // end of slide indicator + + var help = document.createElement("a"); + help.setAttribute("href", helpPage); + help.setAttribute("title", helpText.localize()); + help.innerHTML = "help?".localize(); + toolbar.appendChild(help); + helpAnchor = help; // save for focus hack + + var gap1 = document.createTextNode(" "); + toolbar.appendChild(gap1); + + var contents = document.createElement("a"); + contents.setAttribute("href", "javascript:toggleTableOfContents()"); + contents.setAttribute("title", "table of contents".localize()); + contents.innerHTML = "contents?".localize(); + toolbar.appendChild(contents); + + var gap2 = document.createTextNode(" "); + toolbar.appendChild(gap2); + + var i = location.href.indexOf("#"); + + // check if anchor is entire page + + if (i > 0) + page = location.href.substr(0, i); + else + page = location.href; + + var start = document.createElement("a"); + start.setAttribute("href", page); + start.setAttribute("title", "restart presentation".localize()); + start.innerHTML = "restart?".localize(); +// start.setAttribute("href", "javascript:printSlides()"); +// start.setAttribute("title", "print all slides".localize()); +// start.innerHTML = "print!".localize(); + toolbar.appendChild(start); + + var copyright = findCopyright(); + + if (copyright) + { + var span = document.createElement("span"); + span.innerHTML = copyright; + span.style.color = "black"; + span.style.marginLeft = "2em"; + toolbar.appendChild(span); + } + + slideCounter = document.createElement("div") + slideCounter.style.position = "absolute"; + slideCounter.style.width = "auto"; //"20%"; + slideCounter.style.height = "1.2em"; + slideCounter.style.top = "auto"; + slideCounter.style.bottom = 0; + slideCounter.style.right = "0"; + slideCounter.style.textAlign = "right"; + slideCounter.style.color = "red"; + slideCounter.style.background = "rgb(240,240,240)"; + + slideCounter.innerHTML = "slide".localize() + " n/m"; + toolbar.appendChild(slideCounter); + } + + // ensure that click isn't passed through to the page + toolbar.onclick = stopPropagation; + document.body.appendChild(toolbar); + slideNumElement = slideCounter; + setEosStatus(false); + + return toolbar; +} + +function isShownToc() +{ + if (toc && toc.style.visible == "visible") + return true; + + return false; +} + +function showTableOfContents() +{ + if (toc) + { + if (toc.style.visibility != "visible") + { + toc.style.visibility = "visible"; + toc.style.display = "block"; + toc.focus(); + + if (ie7 && slidenum == 0) + setTimeout("ieHack()", 100); + } + else + hideTableOfContents(); + } +} + +function hideTableOfContents() +{ + if (toc && toc.style.visibility != "hidden") + { + toc.style.visibility = "hidden"; + toc.style.display = "none"; + + try + { + if (!opera) + helpAnchor.focus(); + } + catch (e) + { + } + } +} + +function toggleTableOfContents() +{ + if (toc) + { + if (toc.style.visible != "visible") + showTableOfContents(); + else + hideTableOfContents(); + } +} + +// called on clicking toc entry +function gotoEntry(e) +{ + var target; + + if (!e) + var e = window.event; + + if (e.target) + target = e.target; + else if (e.srcElement) + target = e.srcElement; + + // work around Safari bug + if (target.nodeType == 3) + target = target.parentNode; + + if (target && target.nodeType == 1) + { + var uri = target.getAttribute("href"); + + if (uri) + { + //alert("going to " + uri); + var slide = slides[slidenum]; + hideSlide(slide); + slidenum = findSlideNumber(uri); + slide = slides[slidenum]; + lastShown = null; + setLocation(); + setVisibilityAllIncremental("hidden"); + setEosStatus(!nextIncrementalItem(lastShown)); + showSlide(slide); + //target.focus(); + + try + { + if (!opera) + helpAnchor.focus(); + } + catch (e) + { + } + } + } + + hideTableOfContents(e); + if (ie7) ieHack(); + stopPropagation(e); + return cancel(e); +} + +// called onkeydown for toc entry +function gotoTocEntry(event) +{ + var key; + + if (!event) + var event = window.event; + + // kludge around NS/IE differences + if (window.event) + key = window.event.keyCode; + else if (event.which) + key = event.which; + else + return true; // Yikes! unknown browser + + // ignore event if key value is zero + // as for alt on Opera and Konqueror + if (!key) + return true; + + // check for concurrent control/command/alt key + // but are these only present on mouse events? + + if (event.ctrlKey || event.altKey) + return true; + + if (key == 13) + { + var uri = this.getAttribute("href"); + + if (uri) + { + //alert("going to " + uri); + var slide = slides[slidenum]; + hideSlide(slide); + slidenum = findSlideNumber(uri); + slide = slides[slidenum]; + lastShown = null; + setLocation(); + setVisibilityAllIncremental("hidden"); + setEosStatus(!nextIncrementalItem(lastShown)); + showSlide(slide); + //target.focus(); + + try + { + if (!opera) + helpAnchor.focus(); + } + catch (e) + { + } + } + + hideTableOfContents(); + if (ie7) ieHack(); + return cancel(event); + } + + if (key == 40 && this.next) + { + this.next.focus(); + return cancel(event); + } + + if (key == 38 && this.previous) + { + this.previous.focus(); + return cancel(event); + } + + return true; +} + +function isTitleSlide(slide) +{ + return hasClass(slide, "title"); +} + +// create div element with links to each slide +function tableOfContents() +{ + var toc = document.createElement("div"); + addClass(toc, "toc"); + //toc.setAttribute("tabindex", "0"); + + var heading = document.createElement("div"); + addClass(heading, "toc-heading"); + heading.innerHTML = "Table of Contents".localize(); + + heading.style.textAlign = "center"; + heading.style.width = "100%"; + heading.style.margin = "0"; + heading.style.marginBottom = "1em"; + heading.style.borderBottomStyle = "solid"; + heading.style.borderBottomColor = "rgb(180,180,180)"; + heading.style.borderBottomWidth = "1px"; + + toc.appendChild(heading); + var previous = null; + + for (var i = 0; i < slides.length; ++i) + { + var title = hasClass(slides[i], "title"); + var num = document.createTextNode((i + 1) + ". "); + + toc.appendChild(num); + + var a = document.createElement("a"); + a.setAttribute("href", "#(" + (i+1) + ")"); + + if (title) + addClass(a, "titleslide"); + + var name = document.createTextNode(slideName(i)); + a.appendChild(name); + a.onclick = gotoEntry; + a.onkeydown = gotoTocEntry; + a.previous = previous; + + if (previous) + previous.next = a; + + toc.appendChild(a); + + if (i == 0) + toc.first = a; + + if (i < slides.length - 1) + { + var br = document.createElement("br"); + toc.appendChild(br); + } + + previous = a; + } + + toc.focus = function () { + if (this.first) + this.first.focus(); + } + + toc.onclick = function (e) { + e||(e=window.event); + hideTableOfContents(); + stopPropagation(e); + + if (e.cancel != undefined) + e.cancel = true; + + if (e.returnValue != undefined) + e.returnValue = false; + + return false; + }; + + toc.style.position = "absolute"; + toc.style.zIndex = "300"; + toc.style.width = "60%"; + toc.style.maxWidth = "30em"; + toc.style.height = "30em"; + toc.style.overflow = "auto"; + toc.style.top = "auto"; + toc.style.right = "auto"; + toc.style.left = "4em"; + toc.style.bottom = "4em"; + toc.style.padding = "1em"; + toc.style.background = "rgb(240,240,240)"; + toc.style.borderStyle = "solid"; + toc.style.borderWidth = "2px"; + toc.style.fontSize = "60%"; + + document.body.insertBefore(toc, document.body.firstChild); + return toc; +} + +function replaceByNonBreakingSpace(str) +{ + for (var i = 0; i < str.length; ++i) + str[i] = 160; +} + + +function initOutliner() +{ + var items = document.getElementsByTagName("LI"); + + for (var i = 0; i < items.length; ++i) + { + var target = items[i]; + + if (!hasClass(target.parentNode, "outline")) + continue; + + target.onclick = outlineClick; + + if (!ns_pos) + { + target.onmouseover = hoverOutline; + target.onmouseout = unhoverOutline; + } + + if (foldable(target)) + { + target.foldable = true; + target.onfocus = function () {outline = this;}; + target.onblur = function () {outline = null;}; + + if (!target.getAttribute("tabindex")) + target.setAttribute("tabindex", "0"); + + if (hasClass(target, "expand")) + unfold(target); + else + fold(target); + } + else + { + addClass(target, "nofold"); + target.visible = true; + target.foldable = false; + } + } +} + +function foldable(item) +{ + if (!item || item.nodeType != 1) + return false; + + var node = item.firstChild; + + while (node) + { + if (node.nodeType == 1 && isBlock(node)) + return true; + + node = node.nextSibling; + } + + return false; +} + +function fold(item) +{ + if (item) + { + removeClass(item, "unfolded"); + addClass(item, "folded"); + } + + var node = item ? item.firstChild : null; + + while (node) + { + if (node.nodeType == 1 && isBlock(node)) // element + { + // note that getElementStyle won't work for Safari 1.3 + node.display = getElementStyle(node, "display", "display"); + node.style.display = "none"; + node.style.visibility = "hidden"; + } + + node = node.nextSibling; + } + + item.visible = false; +} + +function unfold(item) +{ + if (item) + { + addClass(item, "unfolded"); + removeClass(item, "folded"); + } + + var node = item ? item.firstChild : null; + + while (node) + { + if (node.nodeType == 1 && isBlock(node)) // element + { + // with fallback for Safari, see above + node.style.display = (node.display ? node.display : "block"); + node.style.visibility = "visible"; + } + + node = node.nextSibling; + } + + item.visible = true; +} + +function outlineClick(e) +{ + var rightclick = false; + var target; + + if (!e) + var e = window.event; + + if (e.target) + target = e.target; + else if (e.srcElement) + target = e.srcElement; + + // work around Safari bug + if (target.nodeType == 3) + target = target.parentNode; + + while (target && target.visible == undefined) + target = target.parentNode; + + if (!target) + return true; + + if (e.which) + rightclick = (e.which == 3); + else if (e.button) + rightclick = (e.button == 2); + + if (!rightclick && target.visible != undefined) + { + if (target.foldable) + { + if (target.visible) + fold(target); + else + unfold(target); + } + + stopPropagation(e); + e.cancel = true; + e.returnValue = false; + } + + return false; +} + +function hoverOutline(e) +{ + var target; + + if (!e) + var e = window.event; + + if (e.target) + target = e.target; + else if (e.srcElement) + target = e.srcElement; + + // work around Safari bug + if (target.nodeType == 3) + target = target.parentNode; + + while (target && target.visible == undefined) + target = target.parentNode; + + if (target && target.foldable) + target.style.cursor = "pointer"; + + return true; +} + +function unhoverOutline(e) +{ + var target; + + if (!e) + var e = window.event; + + if (e.target) + target = e.target; + else if (e.srcElement) + target = e.srcElement; + + // work around Safari bug + if (target.nodeType == 3) + target = target.parentNode; + + while (target && target.visible == undefined) + target = target.parentNode; + + if (target) + target.style.cursor = "default"; + + return true; +} + + +function stopPropagation(e) +{ + if (window.event) + { + window.event.cancelBubble = true; + //window.event.returnValue = false; + } + else if (e) + { + e.cancelBubble = true; + e.stopPropagation(); + //e.preventDefault(); + } +} + +/* can't rely on display since we set that to none to hide things */ +function isBlock(elem) +{ + var tag = elem.nodeName; + + return tag == "OL" || tag == "UL" || tag == "P" || + tag == "LI" || tag == "TABLE" || tag == "PRE" || + tag == "H1" || tag == "H2" || tag == "H3" || + tag == "H4" || tag == "H5" || tag == "H6" || + tag == "BLOCKQUOTE" || tag == "ADDRESS"; +} + +function getElementStyle(elem, IEStyleProp, CSSStyleProp) +{ + if (elem.currentStyle) + { + return elem.currentStyle[IEStyleProp]; + } + else if (window.getComputedStyle) + { + var compStyle = window.getComputedStyle(elem, ""); + return compStyle.getPropertyValue(CSSStyleProp); + } + return ""; +} + +// works with text/html and text/xhtml+xml with thanks to Simon Willison +function createElement(element) +{ + if (typeof document.createElementNS != 'undefined') + { + return document.createElementNS('http://www.w3.org/1999/xhtml', element); + } + + if (typeof document.createElement != 'undefined') + { + return document.createElement(element); + } + + return false; +} + +// designed to work with both text/html and text/xhtml+xml +function getElementsByTagName(name) +{ + if (typeof document.getElementsByTagNameNS != 'undefined') + { + return document.getElementsByTagNameNS('http://www.w3.org/1999/xhtml', name); + } + + if (typeof document.getElementsByTagName != 'undefined') + { + return document.getElementsByTagName(name); + } + + return null; +} + +/* +// clean alternative to innerHTML method, but on IE6 +// it doesn't work with named entities like +// which need to be replaced by numeric entities +function insertText(element, text) +{ + try + { + element.textContent = text; // DOM3 only + } + catch (e) + { + if (element.firstChild) + { + // remove current children + while (element.firstChild) + element.removeChild(element.firstChild); + } + + element.appendChild(document.createTextNode(text)); + } +} + +// as above, but as method of all element nodes +// doesn't work in IE6 which doesn't allow you to +// add methods to the HTMLElement prototype +if (HTMLElement != undefined) +{ + HTMLElement.prototype.insertText = function(text) { + var element = this; + + try + { + element.textContent = text; // DOM3 only + } + catch (e) + { + if (element.firstChild) + { + // remove current children + while (element.firstChild) + element.removeChild(element.firstChild); + } + + element.appendChild(document.createTextNode(text)); + } + }; +} +*/ + +function getSelectedText() +{ + try + { + if (window.getSelection) + return window.getSelection().toString(); + + if (document.getSelection) + return document.getSelection().toString(); + + if (document.selection) + return document.selection.createRange().text; + } + catch (e) + { + return ""; + } + return ""; +} diff --git a/pres/Slidy/slidy.js.gz b/pres/Slidy/slidy.js.gz Binary files differnew file mode 100644 index 00000000..0e84fca8 --- /dev/null +++ b/pres/Slidy/slidy.js.gz diff --git a/pres/Slidy/template.html b/pres/Slidy/template.html new file mode 100644 index 00000000..c6801dfc --- /dev/null +++ b/pres/Slidy/template.html @@ -0,0 +1,187 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<title>Slidy template for W3C Blue2 Style</title> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta name="copyright" content="Copyright © 2005 W3C (MIT, ERCIM, Keio)" /> +<meta name="font-size-adjustment" content="-1" /> +<link rel="stylesheet" href="../Slidy/w3c-blue2.css" + type="text/css" media="screen, projection, print" /> +<script src="../Slidy/slidy.js" type="text/javascript"> +</script> +</head> +<body> +<!-- this defines the slide background --> + +<div class="background"> + <div class="header"> + <!-- sized and colored via CSS --> + </div> + <!-- hidden style graphics to ensure they are saved with other content --> + <img class="hidden" src="../Slidy/bullet.png" alt="" /> + <img class="hidden" src="../Slidy/fold.bmp" alt="" /> + <img class="hidden" src="../Slidy/unfold.bmp" alt="" /> + <img class="hidden" src="../Slidy/fold-dim.bmp" alt="" /> + <img class="hidden" src="../Slidy/nofold-dim.bmp" alt="" /> + <img class="hidden" src="../Slidy/unfold-dim.bmp" alt="" /> + <img class="hidden" src="../Slidy/bullet-fold.gif" alt="" /> + <img class="hidden" src="../Slidy/bullet-unfold.gif" alt="" /> + <img class="hidden" src="../Slidy/bullet-fold-dim.gif" alt="" /> + <img class="hidden" src="../Slidy/bullet-nofold-dim.gif" alt="" /> + <img class="hidden" src="../Slidy/bullet-unfold-dim.gif" alt="" /> + + <div class="footer"> + <object id="w3c-logo" + data="../Slidy/w3c-logo-blue.svg" type="image/svg+xml" + title="W3C logo"><a href="http://www.w3.org/"><img + alt="W3C logo" id="w3c-logo-fallback" + src="../Slidy/w3c-logo-blue.gif" /></a></object> + + <!-- modify the following text as appropriate --> + Presentation Name<br /> + Event, Location, Month Year + </div> +</div> + +<div class="slide cover"> +<div class="header"> +<h1>Slidy template for W3C Blue2 Style</h1> +<p><a href="http://www.w3.org/People/Raggett/">Dave Raggett</a>, +<<a href="mailto:dsr@w3.org">dsr@w3.org</a>></p> +</div> +<img src="../Slidy/keys.jpg" class="cover" +alt="W3C as letters on 3 plastic buttons from a keyboard" /> +<h2>Event, Location, Month Year</h2> +</div> + +<div class="slide"> +<h1>W3C Blue2 Style I - Document Structure</h1> + +<p class="subhead">To get the W3C Blue2 Style:</p> + +<p>The xhtml file should match the following template, but +please modify the content of the title element as appropriate +for your presentation. The relative links below assume +that your presentation is in a directory like +/2005/Talks/your-slide-dir and should be modified as needed, +or you can use absolute links, as generated by comma slidy.</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<title>Slidy template for W3C Meetings</title> +<meta name="copyright" content="Copyright &#169; 2005 W3C (MIT, ERCIM, Keio)" /> +<link rel="stylesheet" href="../Slidy/w3c-blue2.css" type="text/css" +media="screen, projection, print" /> +<script src="../Slidy/slidy.js" type="text/javascript"> +</script> +</head> +<body> + ... background, cover slide ... + <div class="slide"> + <h1>Introduction</h1> + ... slide content ... + </div> + ... other slides ... +</body> +</html> +</pre> +</div> + +<div class="slide"> +<h1>W3C Blue2 Style II - Background and Cover</h1> + +<p class="subhead">To get the slide background and cover page:</p> + +<p>The body element's content should start with the following +markup, but please modify the heading and contact details +as appropriate:</p> + +<pre> +<div class="background"> + <div class="header"> </div> + <img src="../Slidy/bullet.png" alt="hidden bullet" /> + <div class="footer"> + <object id="w3c-logo" + data="../Slidy/w3c-logo-blue.svg" type="image/svg+xml" + title="W3C logo"><a href="http://www.w3.org/"><img + alt="W3C logo" id="w3c-logo-fallback" + src="../Slidy/w3c-logo-blue.gif" /></a></object> + <!-- modify the following text as appropriate --> + Presentation Name<br /> + Event, Location, Month Year + </div> +</div> + +<div class="slide cover"> + <div class="header"> + <h1>Slidy template for W3C Blue2 Style</h1> + <p><a href="http://www.w3.org/People/Raggett/">Dave Raggett</a>, + &lt;<a href="mailto:dsr@w3.org">dsr@w3.org</a>&gt;</p> + </div> + <img src="../Slidy/keys.jpg" class="cover" + alt="W3C as letters on 3 plastic buttons from a keyboard" /> +</div> +</pre> +</div> + +<div class="slide"> +<h1>Further information</h1> + +<ul> +<li>Avoid placing too much content on each slide, no one will remember +it all, and it will overflow the slide design.</li> +<li>For further guidance on slide markup see the <a +href="http://www.w3.org/Talks/Tools/Slidy">Slidy pages</a></li> +<li>If you want to print all the slides, press the "A" key to toggle +to the "all slides view" then use the browser's print function in the +normal way.</li> +<li>The comma slidy tool can be used with all.htm files in the old +W3C slide format, for example: +<pre>http://www.w3.org/2005/Talks/0605-sb-acintroduction/all.htm,slidy</pre> +</li> +<li>You are advised to save the output from the tool to a different +location and make it available Public, Members only or Team restricted +as appropriate.</li> +<li>If you want to view slides offline, be careful to save the slides +with all of the content (see browser's <em>Save As</em> function, and +<em>"Web Page, complete"</em> for the save as type).</li> +<li>Firefox users may want the <a +href="http://extensionroom.mozdev.org/more-info/autohide">autohide</a> +extension to hide the toolbars when entering full screen with F11</li> +<li>If you get stuck then <a href="http://www.w3.org/Team/Raggett/">Dave +Raggett</a> should be able to help.</li> +</ul> +</div> + +<div class="slide"> +<h1>Using SVG for great looking graphics</h1> + +<ul> +<li>If you want to include graphics in SVG, you should stick to SVG +Tiny to increase the chance of people seeing it on whatever browser +they are using. Always, provide a fallback as a JPEG, PNG or GIF.</li> +<li>The use of SVG may result in users being prompted to install +a plugin. The Adobe SVG plugin is reasonably effective, but an +alternative may be to switch to a browser with native SVG support.</li> +<li>Some versions of browsers implement SVG in a brittle way that +may cause the browser to crash. If this happens to you, please +consider switching to a different version. Alternatively, you could +strip out the object tag and just use the img element</li> +<li>The W3C logo in the footer uses SVG for jaggy free scaling +via the markup for the slide background, see <a href="#(3)">slide 3</a></li> +<li>Firefox users on Windows should install the <a +href="http://plugindoc.mozdev.org/windows.html">Adobe SVG Viewer 6.0</a>, +or <a href="http://www.mozilla.org/">install Firefox 1.5</a> which +has native SVG support.</li> +</ul> +</div> + +</body> +</html> diff --git a/pres/Slidy/unfold-bright.gif b/pres/Slidy/unfold-bright.gif Binary files differnew file mode 100644 index 00000000..2748131a --- /dev/null +++ b/pres/Slidy/unfold-bright.gif diff --git a/pres/Slidy/unfold-dim.bmp b/pres/Slidy/unfold-dim.bmp Binary files differnew file mode 100644 index 00000000..c2a6bafa --- /dev/null +++ b/pres/Slidy/unfold-dim.bmp diff --git a/pres/Slidy/unfold-dim.gif b/pres/Slidy/unfold-dim.gif Binary files differnew file mode 100644 index 00000000..e52448f8 --- /dev/null +++ b/pres/Slidy/unfold-dim.gif diff --git a/pres/Slidy/unfold.bmp b/pres/Slidy/unfold.bmp Binary files differnew file mode 100644 index 00000000..30af625e --- /dev/null +++ b/pres/Slidy/unfold.bmp diff --git a/pres/update.sh b/pres/update.sh new file mode 100644 index 00000000..4746524e --- /dev/null +++ b/pres/update.sh @@ -0,0 +1 @@ +scp -r Slidy/* fedorapeople.org:~/public_html/presentations/cobbler/ diff --git a/pusher.py b/pusher.py new file mode 100644 index 00000000..ce7b9173 --- /dev/null +++ b/pusher.py @@ -0,0 +1,148 @@ +#!/usr/bin/python + +""" +Michael DeHaan <mdehaan@fedoraproject.org>, 2008 + +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. + +--- + +This script automates pushes from git checkouts +into Fedora CVS. It is expected you already have +Fedora CVS set up for a project and have the build +system tools installed. + +After that, usage looks like: +python pusher.py --proj=/cg/func --cvs=~/func + +Work in progress +""" + +# if new releases come out or old ones go away, edit here +#PROCESS_RELEASES = [ "devel", "F-9", "F-8", "F-7", "EL-5", "EL-4" ] +PROCESS_RELEASES = [ "devel", "F-10", "F-9", "F-8", "EL-5", "EL-4" ] + +import optparse +import os +import sys +import glob +import subprocess + +def run(cmd,failok=False): + """ + Wrapper around subprocess + """ + print "running: %s" % cmd + rc = subprocess.call(cmd, shell=True) + print "rc: %s" % rc + if not failok and not rc == 0: + croak("aborting") + + +def croak(msg): + """ + Print something and die. + """ + print msg + sys.exit(1) + + +# process options, as described at the top of this file +p = optparse.OptionParser(usage="pusher [ARGS]") +p.add_option("--cvs", dest="cvs", help="EX: ~/cvs/func") +p.add_option("--proj", dest="proj", help="EX: /cg/func") +(options,args) = p.parse_args() +if options.cvs is None: + croak("--cvs is required, PEBKAC") +if options.proj is None: + croak("--proj is required, PEBKAC") + +cvsdir = os.path.expanduser(options.cvs) +projdir = os.path.expanduser(options.proj) + +print "----------------------------------------------" +print "Running Michael's totally awesome code pusher script" +print "----------------------------------------------" +print "assuming you first ran something like..." +print " ssh-agent bash" +print " ssh-agent ~/.ssh/id_dsa" +print "if not, expect pain and it's not my fault" +print "----------------------------------------------" +print " " +print "ok, here we go..." +print " " + +# find the RPM build directory +rpmbuild = os.path.join(projdir, "rpm-build") +if not os.path.exists(rpmbuild): + croak("no directory: %s" % rpmbuild) +print "found rpm-build directory" + +# find the tarballs +tarsearch = "%s/*.tar.gz" % rpmbuild +tars = glob.glob(tarsearch) +if len(tars) != 1: + croak("expected to find just one tar.gz in %s, no luck") % rpmbuild +tarfile = tars[0] +print "found tarball: %s" % tarfile + +# find a version file, if any +versionfile = None +versearch = os.path.join(projdir,"version") +if os.path.exists(versearch): + print "found a version file: %s" % versearch + versionfile = versearch +print "found version file: %s" % versionfile + +# find a specfile +specsearch = "%s/*.spec" % projdir +specs = glob.glob(specsearch) +if len(specs) != 1: + croak("need one and only one specfile in %s" % projdir) +specfile = specs[0] +print "found specfile: %s" % specfile + +# verify cvsdir exists +if not os.path.exists(cvsdir): + croak("can't find cvs directory: %s" % cvsdir) + +# store current directory +topdir = os.getcwd() + +# do cvs update +os.chdir(cvsdir) +run("cvs update -d") +os.chdir(topdir) + +# copy specfile and version file into CVS +# plus upload tarball +# and then commit +for x in PROCESS_RELEASES: + releasedir = os.path.join(cvsdir, x) + rc = run("cp %s %s" % (specfile, releasedir)) + if versionfile: + rc = run("cp %s %s" % (versionfile, releasedir)) + print "cd into %s" % releasedir + os.chdir(releasedir) + rc = run("make upload FILES=%s" % tarfile) +os.chdir(cvsdir) +run("cvs commit") + +# go back through each CVS directory and build stuff +for x in PROCESS_RELEASES: + releasedir = os.path.join(cvsdir, x) + print "cd into %s" % releasedir + os.chdir(releasedir) + rc = run("make tag") + rc = run("BUILD_FLAGS=\"--nowait\" make build",failok=True) + +print "---------------------------------------------" +print "all done, assuming you didn't see anything weird" +print "don't forget to visit https://admin.fedoraproject.org/updates" +print " " + diff --git a/scripts/cobblerd b/scripts/cobblerd index 37fa44ad..dd4d610b 100755 --- a/scripts/cobblerd +++ b/scripts/cobblerd @@ -16,24 +16,17 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. import sys import os -import cobbler.api as bootapi import cobbler.cobblerd as app import logging import cobbler.utils as utils - +import cobbler.sub_process as sub_process +import traceback +import cobbler.api as cobbler_api import optparse -#logger = logging.getLogger("cobbler.cobblerd") -#logger.setLevel(logging.DEBUG) -#ch = logging.FileHandler("/var/log/cobbler/cobblerd.log") -#ch.setLevel(logging.DEBUG) -#formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") -#ch.setFormatter(formatter) -#logger.addHandler(ch) - -def daemonize_self(logger): +def daemonize_self(): # daemonizing code: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012 - logger.info("cobblerd started") + # logger.info("cobblerd started") try: pid = os.fork() if pid > 0: @@ -41,7 +34,6 @@ def daemonize_self(logger): sys.exit(0) except OSError, e: print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror) - utils.log_exc(logger) sys.exit(1) # decouple from parent environment @@ -57,7 +49,7 @@ def daemonize_self(logger): sys.exit(0) except OSError, e: print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror) - utils.log_exc(logger) + #utils.log_exc(logger) sys.exit(1) dev_null = file('/dev/null','rw') @@ -87,16 +79,29 @@ def main(): if not isinstance(log_level, int): op.error('Unrecognized log level %r given') log_settings['log_level'] = log_level - api = bootapi.BootAPI(log_settings=log_settings) + + # load the API now rather than later, to ensure cobblerd + # startup time is done before the service returns + api = None + try: + api = cobbler_api.BootAPI(log_settings=log_settings, is_cobblerd=True) + except Exception, exc: + if sys.exc_type==SystemExit: + return exc.code + else: + utils.print_exc(exc,full=True) + return 1 + logger = api.logger if options.daemonize: - daemonize_self(logger) + daemonize_self() try: - app.core(logger=logger) + app.core(api) except: - utils.log_exc(logger) + # FIXME: logging also? + traceback.print_exc() if __name__ == "__main__": main() diff --git a/scripts/debuginator.py b/scripts/debuginator.py new file mode 100644 index 00000000..bc5a35a5 --- /dev/null +++ b/scripts/debuginator.py @@ -0,0 +1,50 @@ +#!/usr/bin/python + +""" +Quick test script to read the cobbler configurations and touch and mkdir -p any files +neccessary to trivially debug another user's configuration even if the distros don't exist yet +Intended for basic support questions only. Not for production use. + +Copyright 2008, 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 glob +import cobbler.yaml as camel +import os.path +import os + +for f in glob.glob("/var/lib/cobbler/config/distros.d/*"): + + fh = open(f) + data = fh.read() + fh.close() + + d = camel.load(data).next() + + k = d["kernel"] + i = d["initrd"] + dir = os.path.dirname(k) + + if not os.path.exists(dir): + os.system("mkdir -p %s" % dir) + + os.system("touch %s" % k) + os.system("touch %s" % i) + + diff --git a/scripts/index.py b/scripts/index.py index 26b86f36..5acfe09e 100755 --- a/scripts/index.py +++ b/scripts/index.py @@ -21,9 +21,9 @@ import cgi import os from cobbler.webui import CobblerWeb import cobbler.utils as utils -import cobbler.yaml as yaml +import yaml # PyYAML -XMLRPC_SERVER = "http://127.0.0.1:25152" # was http://127.0.0.1/cobbler_api_rw" +XMLRPC_SERVER = "http://127.0.0.1:25151" # FIXME: pull port from settings #======================================= @@ -109,8 +109,8 @@ def handler(req): fd = open("/etc/cobbler/settings") data = fd.read() fd.close() - ydata = yaml.load(data).next() - remote_port = ydata.get("xmlrpc_rw_port", 25152) + ydata = yaml.load(data) + remote_port = ydata.get("xmlrpc_port", 25151) mode = form.get('mode','index') diff --git a/scripts/services.py b/scripts/services.py index 0b7036cf..36d00c01 100755 --- a/scripts/services.py +++ b/scripts/services.py @@ -20,7 +20,7 @@ import xmlrpclib import cgi import os from cobbler.services import CobblerSvc -import cobbler.yaml as yaml +import yaml # PyYAML version import cobbler.utils as utils #======================================= @@ -58,10 +58,8 @@ def handler(req): for t in tokens: if label: field = t - apache.log_error("field %s" % field) else: form[field] = t - apache.log_error("adding %s to %s" % (field,t)) label = not label # TESTING.. @@ -74,7 +72,7 @@ def handler(req): fd = open("/etc/cobbler/settings") data = fd.read() fd.close() - ydata = yaml.load(data).next() + ydata = yaml.load(data) remote_port = ydata.get("xmlrpc_port",25151) # instantiate a CobblerWeb object @@ -4,15 +4,15 @@ import sys import os.path from distutils.core import setup, Extension import string -import cobbler.yaml as yaml +import yaml # PyYAML import cobbler.sub_process as subprocess import Cheetah.Template as Template import time -VERSION = "1.3.4" +VERSION = "1.7.0" SHORT_DESC = "Network Boot and Update Server" LONG_DESC = """ -Cobbler is a network boot and update server. Cobbler supports PXE, provisioning virtualized images, and reinstalling existing Linux machines. The last two modes require a helper tool called 'koan' that integrates with cobbler. Cobbler's advanced features include importing distributions from DVDs and rsync mirrors, kickstart templating, integrated yum mirroring, and built-in DHCP/DNS Management. Cobbler also has a Python and XMLRPC API for integration with other applications. +Cobbler is a network install server. Cobbler supports PXE, virtualized installs, and reinstalling existing Linux machines. The last two modes use a helper tool, 'koan', that integrates with cobbler. Cobbler's advanced features include importing distributions from DVDs and rsync mirrors, kickstart templating, integrated yum mirroring, and built-in DHCP/DNS Management. Cobbler has a Python and XMLRPC API for integration with other applications. There is also a web interface. """ TEMPLATES_DIR = "installer_templates" DEFAULTS = os.path.join(TEMPLATES_DIR, "defaults") @@ -23,7 +23,11 @@ OUTPUT_DIR = "config" # ========================================================= def templatify(template, answers, output): t = Template.Template(file=template, searchList=answers) - open(output,"w").write(t.respond()) + data = t.respond() + print "response=%s" % data + outf = open(output,"w") + outf.write(data) + outf.close() def gen_build_version(): fd = open(os.path.join(OUTPUT_DIR, "version"),"w+") @@ -54,9 +58,10 @@ def gen_build_version(): def gen_config(): - defaults = {} - data = yaml.loadFile(DEFAULTS).next() - defaults.update(data) + defaults_file = open(DEFAULTS) + defaults_data = defaults_file.read() + defaults_file.close() + defaults = yaml.load(defaults_data) templatify(MODULES_TEMPLATE, defaults, os.path.join(OUTPUT_DIR, "modules.conf")) templatify(SETTINGS_TEMPLATE, defaults, os.path.join(OUTPUT_DIR, "settings")) @@ -71,6 +76,7 @@ if __name__ == "__main__": rotpath = "/etc/logrotate.d" powerpath = etcpath + "/power" pxepath = etcpath + "/pxe" + reppath = etcpath + "/reporting" zonepath = etcpath + "/zone_templates" # lib paths @@ -105,6 +111,7 @@ if __name__ == "__main__": vw_systems = wwwpath + "/systems" vw_profiles = wwwpath + "/profiles" vw_links = wwwpath + "/links" + vw_aux = wwwpath + "/aux" # cgipath = "/var/www/cgi-bin/cobbler" modpython = wwwpath + "/web" modpythonsvc = wwwpath + "/svc" @@ -114,37 +121,8 @@ if __name__ == "__main__": logpath2 = logpath + "/kicklog" logpath3 = logpath + "/syslog" logpath4 = "/var/log/httpd/cobbler" + logpath5 = logpath + "/anamon" - # tftp paths - tftp_cfg = "/tftpboot/pxelinux.cfg" - tftp_images = "/tftpboot/images" - - - # hack to bundle jquery until we have packaging guidelines to avoid JS bundling - # bundling is evil, but temporary. - - def file_slurper(arg, dirname, fnames): - # FIXME: shell glob would be simpler - for fn in fnames: - fn2 = os.path.join(dirname,fn) - if os.path.isfile(fn2): - if not fn2 in arg: - arg.append(fn2) - else: - # don't recurse - fnames.remove(fn) - - jui_files = [] - jui_files2 = [] - jui_files3 = [] - jui_files4 = [] - jui_files5 = [] - os.path.walk("./webui_content/jquery.ui/ui", file_slurper, jui_files) - os.path.walk("./webui_content/jquery.ui/ui/i18n", file_slurper, jui_files2) - os.path.walk("./webui_content/jquery.ui/themes", file_slurper, jui_files3) - os.path.walk("./webui_content/jquery.ui/themes/flora", file_slurper, jui_files4) - os.path.walk("./webui_content/jquery.ui/themes/flora/i", file_slurper, jui_files5) - setup( name="cobbler", version = VERSION, @@ -154,7 +132,6 @@ if __name__ == "__main__": license = "GPL", packages = [ "cobbler", - "cobbler/yaml", "cobbler/modules", "cobbler/server", "cobbler/webui", @@ -192,6 +169,7 @@ if __name__ == "__main__": (libpath, ['loaders/elilo-3.8-ia64.efi']), (libpath, ['loaders/menu.c32']), (libpath, ['loaders/yaboot-1.3.14']), + (libpath, ['loaders/zpxe.rexx']), # database/serializer (dbpath + "/distros.d", []), @@ -210,23 +188,30 @@ if __name__ == "__main__": # seed files for debian (kickpath, ['kickstarts/sample.seed']), - # templates for DHCP, DNS + # templates for DHCP, DNS, TFTP (etcpath, ['templates/dhcp.template']), (etcpath, ['templates/dnsmasq.template']), (etcpath, ['templates/named.template']), (etcpath, ['templates/zone.template']), + (etcpath, ['templates/tftpd.template']), + (etcpath, ['templates/tftpd-rules.template']), - # templates for syslinux PXE configs + # templates for netboot configs (pxepath, ['templates/pxedefault.template']), (pxepath, ['templates/pxesystem.template']), (pxepath, ['templates/pxesystem_s390x.template']), + (pxepath, ['templates/pxeprofile_s390x.template']), + (pxepath, ['templates/s390x_conf.template']), + (pxepath, ['templates/s390x_parm.template']), (pxepath, ['templates/pxesystem_ia64.template']), (pxepath, ['templates/pxesystem_ppc.template']), (pxepath, ['templates/pxeprofile.template']), (pxepath, ['templates/pxelocal.template']), + (pxepath, ['templates/pxelocal_s390x.template']), # templates for power management (powerpath, ['templates/power_apc_snmp.template']), + (powerpath, ['templates/power_integrity.template']), (powerpath, ['templates/power_ipmilan.template']), (powerpath, ['templates/power_bullpap.template']), (powerpath, ['templates/power_ipmitool.template']), @@ -236,10 +221,13 @@ if __name__ == "__main__": (powerpath, ['templates/power_wti.template']), (powerpath, ['templates/power_ilo.template']), (powerpath, ['templates/power_lpar.template']), - (powerpath, ['templates/power_bladecenter.template']), + (powerpath, ['templates/power_bladecenter.template']), (powerpath, ['templates/power_virsh.template']), - # templates for /usr/bin/cobbler-setup + # templates for reporting + (reppath, ['templates/build_report_email.template']), + + # templates for setup (itemplates, ['installer_templates/modules.conf.template']), (itemplates, ['installer_templates/settings.template']), (itemplates, ['installer_templates/defaults']), @@ -256,7 +244,11 @@ if __name__ == "__main__": (snippetpath, ['snippets/func_register_if_enabled']), (snippetpath, ['snippets/download_config_files']), (snippetpath, ['snippets/koan_environment']), + (snippetpath, ['snippets/pre_anamon']), + (snippetpath, ['snippets/post_anamon']), + (snippetpath, ['snippets/post_s390_reboot']), (snippetpath, ['snippets/redhat_register']), + (snippetpath, ['snippets/cobbler_register']), # documentation (manpath, ['docs/cobbler.1.gz']), @@ -266,6 +258,7 @@ if __name__ == "__main__": (logpath2, []), (logpath3, []), (logpath4, []), + (logpath5, []), # web page directories that we own (vw_localmirror, []), @@ -279,13 +272,11 @@ if __name__ == "__main__": (vw_systems, []), (vw_profiles, []), (vw_links, []), + (vw_aux, []), # zone-specific templates directory (zonepath, []), - # tftp directories that we own - (tftp_cfg, []), - (tftp_images, []), # Web UI templates for object viewing & modification # FIXME: other templates to add as they are created. @@ -293,12 +284,18 @@ if __name__ == "__main__": (wwwtmpl, ['webui_templates/empty.tmpl']), (wwwtmpl, ['webui_templates/blank.tmpl']), + (wwwtmpl, ['webui_templates/search.tmpl']), (wwwtmpl, ['webui_templates/enoaccess.tmpl']), (wwwtmpl, ['webui_templates/distro_list.tmpl']), (wwwtmpl, ['webui_templates/distro_edit.tmpl']), (wwwtmpl, ['webui_templates/profile_list.tmpl']), (wwwtmpl, ['webui_templates/profile_edit.tmpl']), (wwwtmpl, ['webui_templates/system_list.tmpl']), + (wwwtmpl, ['webui_templates/system_netboot.tmpl']), + (wwwtmpl, ['webui_templates/system_rename.tmpl']), + (wwwtmpl, ['webui_templates/system_delete.tmpl']), + (wwwtmpl, ['webui_templates/system_profile.tmpl']), + (wwwtmpl, ['webui_templates/system_power.tmpl']), (wwwtmpl, ['webui_templates/system_edit.tmpl']), (wwwtmpl, ['webui_templates/repo_list.tmpl']), (wwwtmpl, ['webui_templates/repo_edit.tmpl']), @@ -306,6 +303,7 @@ if __name__ == "__main__": (wwwtmpl, ['webui_templates/image_edit.tmpl']), # Web UI common templates + (wwwtmpl, ['webui_templates/checkboxes.tmpl']), (wwwtmpl, ['webui_templates/paginate.tmpl']), (wwwtmpl, ['webui_templates/message.tmpl']), (wwwtmpl, ['webui_templates/error_page.tmpl']), @@ -332,6 +330,9 @@ if __name__ == "__main__": (wwwcon, ['webui_content/logo-cobbler.png']), (wwwcon, ['webui_content/cobblerweb.css']), + # Anamon script + (vw_aux, ['aux/anamon.py', 'aux/anamon.init']), + # Directories to hold cobbler triggers ("%s/add/distro/pre" % trigpath, []), ("%s/add/distro/post" % trigpath, []), @@ -350,10 +351,10 @@ if __name__ == "__main__": ("%s/delete/repo/pre" % trigpath, []), ("%s/delete/repo/post" % trigpath, []), ("%s/delete/repo/post" % trigpath, []), - ("%s/install/pre" % trigpath, [ "triggers/status_pre.trigger"]), - ("%s/install/post" % trigpath, [ "triggers/status_post.trigger"]), + ("%s/install/pre" % trigpath, []), + ("%s/install/post" % trigpath, []), ("%s/sync/pre" % trigpath, []), - ("%s/sync/post" % trigpath, [ "triggers/restart-services.trigger" ]) + ("%s/sync/post" % trigpath, []) ], description = SHORT_DESC, long_description = LONG_DESC diff --git a/snippets/cobbler_register b/snippets/cobbler_register new file mode 100644 index 00000000..87dbb370 --- /dev/null +++ b/snippets/cobbler_register @@ -0,0 +1,13 @@ +# Begin cobbler registration +#if $getVar('system_name','') == '' +#if $str($getVar('register_new_installs','')) in [ "1", "true", "yes", "y" ] +if [ -f "/usr/bin/cobbler-register" ]; then + cobbler-register --server=$server --fqdn '*AUTO*' --profile=$profile_name --batch +fi +#else +# cobbler registration is disabled in /etc/cobbler/settings +#end if +#else +# skipping for system-based installation +#end if +# End cobbler registration diff --git a/snippets/download_config_files b/snippets/download_config_files index f93a2a39..04443113 100644 --- a/snippets/download_config_files +++ b/snippets/download_config_files @@ -1,11 +1,7 @@ - # Start download cobbler managed config files (if applicable) - #for $tkey, $tpath in $template_files.items() - #set $orig = $tpath #set $tpath = $tpath.replace("_","__").replace("/","_") - #if $getVar("system_name","") != "" #set $ttype = "system" #set $tname = $system_name @@ -13,14 +9,10 @@ #set $ttype = "profile" #set $tname = $profile_name #end if - #set $turl = "http://"+$http_server+"/cblr/svc/op/template/"+$ttype+"/"+$tname+"/path/"+$tpath - #if $orig.startswith("/") +mkdir -p `dirname $orig` wget "$turl" --output-document="$orig" #end if - - #end for - # End download cobbler managed config files (if applicable) diff --git a/snippets/func_register_if_enabled b/snippets/func_register_if_enabled index 0a93200a..4258fa33 100644 --- a/snippets/func_register_if_enabled +++ b/snippets/func_register_if_enabled @@ -4,15 +4,19 @@ /sbin/chkconfig --level 345 funcd on -cat << EOFM >> /etc/func/minion.conf +cat <<EOFM > /etc/func/minion.conf [main] log_level = INFO acl_dir = /etc/func/minion-acl.d + +listen_addr = +listen_port = 51234 EOFM -cat << EOCM >> /etc/certmaster/minion.conf +cat <<EOCM > /etc/certmaster/minion.conf [main] certmaster = $func_master +certmaster_port = 51235 log_level = DEBUG cert_dir = /etc/pki/certmaster EOCM diff --git a/snippets/keep_ssh_host_keys b/snippets/keep_ssh_host_keys new file mode 100644 index 00000000..93a6fadb --- /dev/null +++ b/snippets/keep_ssh_host_keys @@ -0,0 +1,44 @@ +#raw +# Nifty trick to restore ssh keys without using a nochroot %post + +echo "Saving ssh host keys..." > /dev/ttyS0 + +keys_found=no + +insmod /lib/jbd.o +insmod /lib/ext3.o + +drives=$(list-harddrives | awk '{print $1}') +for disk in $drives; do + DISKS="$DISKS $(fdisk -l /dev/$disk | awk '/^\/dev/{print $1}')" +done + +for disk in $DISKS; do + name=$(basename $disk) + mkdir -p /tmp/$name /tmp/ssh + mount $disk /tmp/$name + [ $? -eq 0 ] || continue # Skip to the next partition if the mount fails + + # Copy current ssh host keys out to be reused + if [ -d /tmp/${name}/etc/ssh ]; then + cp -a /tmp/${name}/etc/ssh/ssh_host* /tmp/ssh + keys_found="yes" + umount /tmp/$name + break + fi + umount /tmp/$name + rm -r /tmp/$name +done + +# Loop until the ssh rpm is installed +if [ "$keys_found" = "yes" ]; then + while : ; do + sleep 10 + if [ -d /mnt/sysimage/etc/ssh ] ; then + cp -f /tmp/ssh/ssh_host* /mnt/sysimage/etc/ssh/ + logger "SSH-HOST-KEY copied to newly installed system" + break + fi + done & +fi +#end diff --git a/snippets/main_partition_select b/snippets/main_partition_select index 2469657a..9d996e6f 100644 --- a/snippets/main_partition_select +++ b/snippets/main_partition_select @@ -1,4 +1,3 @@ # partition selection - %include /tmp/partinfo diff --git a/snippets/network_config b/snippets/network_config index 001aa506..ec1c9aca 100644 --- a/snippets/network_config +++ b/snippets/network_config @@ -47,6 +47,10 @@ #if $gateway != "": #set $network_str = $network_str + " --gateway=" + $gateway #end if + #if $name_servers and $name_servers[0] != "": + ## Anaconda only allows one nameserver + #set $network_str = $network_str + " --nameserver=" + $name_servers[0] + #end if #else #set $network_str = "--bootproto=none" #end if @@ -61,7 +65,7 @@ #end if ## network details are populated from the cobbler system object #if $is_vlan == "false" -network $network_str --device=eth$i --onboot=on +network $network_str --device=$iname --onboot=on #end if #end for #end if diff --git a/snippets/post_anamon b/snippets/post_anamon new file mode 100644 index 00000000..fc33cfa5 --- /dev/null +++ b/snippets/post_anamon @@ -0,0 +1,23 @@ +#if $str($getVar('anamon_enabled','')) == "1" + +## install anamon script +wget -O /usr/local/sbin/anamon.py "http://$server/cobbler/aux/anamon.py" +## install anamon system service +wget -O /etc/rc.d/init.d/anamon "http://$server/cobbler/aux/anamon.init" + +## adjust permissions +chmod 755 /etc/rc.d/init.d/anamon /usr/local/sbin/anamon.py +test -d /selinux && restorecon /etc/rc.d/init.d/anamon /usr/local/sbin/anamon.py + +## enable the script +chkconfig --add anamon + +## configure anamon service +cat << __EOT__ > /etc/sysconfig/anamon +COBBLER_SERVER="$server" +COBBLER_PORT="$http_port" +COBBLER_NAME="$name" +LOGFILES="/var/log/boot.log /var/log/messages /var/log/dmesg" +__EOT__ + +#end if diff --git a/snippets/post_install_network_config b/snippets/post_install_network_config index 9e121f1a..e6839f77 100644 --- a/snippets/post_install_network_config +++ b/snippets/post_install_network_config @@ -1,16 +1,11 @@ # Start post_install_network_config generated code - #if $getVar("system_name","") != "" - ## this is being provisioned by system records, not profile records ## so we can do the more complex stuff - ## get the list of interface names #set ikeys = $interfaces.keys() - #import re #set $vlanpattern = $re.compile("[a-zA-Z0-9]+\.[0-9]+") - ## Determine if we should use the MAC address to configure the interfaces first ## Only physical interfaces are required to have a MAC address ## Also determine the number of bonding devices we have, so we can set the @@ -18,74 +13,61 @@ # #set $configbymac = True #set $numbondingdevs = 0 - ## ============================================================================= - #for $iname in $ikeys - ## look at the interface hash data for the specific interface - #set $idata = $interfaces[$iname] - ## do not configure by mac address if we don't have one AND it's not for bonding/vlans ## as opposed to a "real" physical interface - #if $idata["mac_address"] == "" and not $vlanpattern.match($iname) and not $idata["bonding"].lower() == "master": ## we have to globally turn off the config by mac feature as we can't ## use it now - #set $configbymac = False - #end if - ## count the number of bonding devices we have. - #if $idata["bonding"].lower() == "master" #set $numbondingdevs += 1 #end if #end for - ## end looping through the interfaces to see which ones we need to configure. - ## ============================================================================= - #set $i = 0 - ## setup bonding if we have to - #if $numbondingdevs > 0 - echo "options bonding max_bonds=$numbondingdevs" >> /etc/modprobe.conf +if [ -f "/etc/modprobe.conf" ]; then + echo "options bonding max_bonds=$numbondingdevs" >> /etc/modprobe.conf +fi #end if - ## ============================================================================= - ## create a staging directory to build out our network scripts into ## make sure we preserve the loopback device - - mkdir /etc/sysconfig/network-scripts/cobbler - cp /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/cobbler/ - +mkdir /etc/sysconfig/network-scripts/cobbler +cp /etc/sysconfig/network-scripts/ifcfg-lo /etc/sysconfig/network-scripts/cobbler/ ## ============================================================================= - ## configure the gateway if set up (this is global, not a per-interface setting) - #if $gateway != "" - grep -v GATEWAY /etc/sysconfig/network > /etc/sysconfig/network.cobbler - echo "GATEWAY=$gateway" >> /etc/sysconfig/network.cobbler - rm -f /etc/sysconfig/network - mv /etc/sysconfig/network.cobbler /etc/sysconfig/network +grep -v GATEWAY /etc/sysconfig/network > /etc/sysconfig/network.cobbler +echo "GATEWAY=$gateway" >> /etc/sysconfig/network.cobbler +rm -f /etc/sysconfig/network +mv /etc/sysconfig/network.cobbler /etc/sysconfig/network + #end if + ## ============================================================================= + ## Configure the system's primary hostname. This is also passed to anaconda, but + ## anaconda doesn't seem to honour it in DHCP-setups. + #if $hostname != "" +grep -v HOSTNAME /etc/sysconfig/network > /etc/sysconfig/network.cobbler +echo "HOSTNAME=$hostname" >> /etc/sysconfig/network.cobbler +rm -f /etc/sysconfig/network +mv /etc/sysconfig/network.cobbler /etc/sysconfig/network + # Also set the hostname now, some applications require it (e.g.: if we're + # connecting to Puppet before a reboot). +/bin/hostname $hostname #end if - ## ============================================================================= - ## now create the config file for each interface - #for $iname in $ikeys - # Start configuration for $iname - ## create lots of variables to use later - #set $idata = $interfaces[$iname] #set $mac = $idata["mac_address"].upper() #set $static = $idata["static"] @@ -97,9 +79,7 @@ #set $bonding_opts = $idata["bonding_opts"] #set $devfile = "/etc/sysconfig/network-scripts/cobbler/ifcfg-" + $iname #set $routesfile = "/etc/sysconfig/network-scripts/cobbler/route-" + $iname - ## determine if this interface is for a VLAN - #if $vlanpattern.match($iname) ## If this is a VLAN interface, skip it, anaconda doesn't know ## about VLANs. @@ -107,201 +87,176 @@ #else #set $is_vlan = "false" #end if - ## if this is a bonded interface, configure it in modprobe.conf - #if $bonding.lower() == "master" ## Add required entry to modprobe.conf - echo "alias $iname bonding" >> /etc/modprobe.conf.cobbler +if [ -f "/etc/modprobe.conf" ]; then + echo "alias $iname bonding" >> /etc/modprobe.conf.cobbler +fi #end if - #if $configbymac and $is_vlan == "false" and $bonding.lower() != "master" - ## This is the code path physical interfaces will follow. - ## Get the current interface name - IFNAME=\$(ifconfig -a | grep -i '$mac' | cut -d ' ' -f 1) - +IFNAME=\$(ifconfig -a | grep -i '$mac' | cut -d ' ' -f 1) ## Rename this interface in modprobe.conf ## FIXME: if both interfaces startwith eth this is wrong - - grep \$IFNAME /etc/modprobe.conf | sed "s/\$IFNAME/$iname/" >> /etc/modprobe.conf.cobbler - grep -v \$IFNAME /etc/modprobe.conf >> /etc/modprobe.conf.new - rm -f /etc/modprobe.conf - mv /etc/modprobe.conf.new /etc/modprobe.conf - - echo "DEVICE=$iname" > $devfile - echo "HWADDR=$mac" >> $devfile - echo "ONBOOT=yes" >> $devfile - +if [ -f "/etc/modprobe.conf" ]; then + grep \$IFNAME /etc/modprobe.conf | sed "s/\$IFNAME/$iname/" >> /etc/modprobe.conf.cobbler + grep -v \$IFNAME /etc/modprobe.conf >> /etc/modprobe.conf.new + rm -f /etc/modprobe.conf + mv /etc/modprobe.conf.new /etc/modprobe.conf +fi +echo "DEVICE=$iname" > $devfile +echo "HWADDR=$mac" >> $devfile +echo "ONBOOT=yes" >> $devfile #if $bonding.lower() == "slave" and $bonding_master != "" - ## if needed setup bonding - - echo "SLAVE=yes" >> $devfile - echo "MASTER=$bonding_master" >> $devfile +echo "SLAVE=yes" >> $devfile +echo "MASTER=$bonding_master" >> $devfile ## see Red Hat bugzilla 442339 - echo "HOTPLUG=no" >> $devfile +echo "HOTPLUG=no" >> $devfile #end if - - #if $static.lower() == "true" or $bonding.lower() == "slave" - + #if $static or $bonding.lower() == "slave" ## for static or slave interfaces - #if $ip != "" and $bonding.lower() != "slave" - - ## Only configure static networking if an IP-address is ## configured - - echo "BOOTPROTO=static" >> $devfile - echo "IPADDR=$ip" >> $devfile +echo "BOOTPROTO=static" >> $devfile +echo "IPADDR=$ip" >> $devfile #if $netmask == "" ## Default to 255.255.255.0? #set $netmask = "255.255.255.0" #end if - echo "NETMASK=$netmask" >> $devfile - +echo "NETMASK=$netmask" >> $devfile #else - ## Leave the interface unconfigured ## we don't have enough info for static configuration - echo "BOOTPROTO=none" >> $devfile - +echo "BOOTPROTO=none" >> $devfile #end if - #else ## this is a DHCP interface, much less work to do - echo "BOOTPROTO=dhcp" >> $devfile +echo "BOOTPROTO=dhcp" >> $devfile #end if - #else if $is_vlan == "true" or $bonding.lower() == "master" - ## Handle non-physical interfaces with special care. :) - - echo "# Cobbler generated non-physical interface" > $devfile - echo "DEVICE=$iname" >> $devfile - +echo "# Cobbler generated non-physical interface" > $devfile +echo "DEVICE=$iname" >> $devfile #if $is_vlan == "true" ## configure vlan if required - echo "VLAN=yes" >> $devfile +echo "VLAN=yes" >> $devfile #end if - #if $bonding.lower() == "master" and $bonding_opts != "" ## configure bonding if required - cat >> $devfile << EOF - BONDING_OPTS="$bonding_opts" - EOF +cat >> $devfile << EOF +BONDING_OPTS="$bonding_opts" +EOF #end if - - echo "ONPARENT=yes" >> $devfile - - #if $static.lower() == "true" - +echo "ONPARENT=yes" >> $devfile + #if $static ## for static non-physical interfaces... - #if $ip != "" ## Only configure static networking if an IP-address is ## configured - echo "BOOTPROTO=static" >> $devfile - echo "IPADDR=$ip" >> $devfile +echo "BOOTPROTO=static" >> $devfile +echo "IPADDR=$ip" >> $devfile #if $netmask == "" ## Default to 255.255.255.0? #set $netmask = "255.255.255.0" #end if - echo "NETMASK=$netmask" >> $devfile +echo "NETMASK=$netmask" >> $devfile #else ## Leave the interface unconfigured - echo "BOOTPROTO=none" >> $devfile +echo "BOOTPROTO=none" >> $devfile #end if #else - echo "BOOTPROTO=dhcp" >> $devfile +echo "BOOTPROTO=dhcp" >> $devfile #end if #else if $configbymac == False ## We'll end up here when not all physical interfaces present for ## this system have MAC-addresses configured for them. We don't ## support interface renaming here. - - MAC=\$(ifconfig -a | grep $iname | awk '{ print \$5 }') - - echo "DEVICE=$iname" > $devfile - echo "HWADDR=\$MAC" >> $devfile - echo "ONBOOT=yes" >> $devfile - - +MAC=\$(ifconfig -a | grep $iname | awk '{ print \$5 }') +echo "DEVICE=$iname" > $devfile +echo "HWADDR=\$MAC" >> $devfile +echo "ONBOOT=yes" >> $devfile #if $bonding.lower() == "slave" and $bonding_master != "" - ## if needed setup bonding - - echo "SLAVE=yes" >> $devfile - echo "MASTER=$bonding_master" >> $devfile +echo "SLAVE=yes" >> $devfile +echo "MASTER=$bonding_master" >> $devfile ## see Red Hat bugzilla 442339 - echo "HOTPLUG=no" >> $devfile +echo "HOTPLUG=no" >> $devfile #end if - - #if $static.lower() == "true" or $bonding.lower() == "slave" - + #if $static or $bonding.lower() == "slave" ## for static or slave interfaces - #if $ip != "" and $bonding.lower() != "slave" - - ## Only configure static networking if an IP-address is ## configured - - echo "BOOTPROTO=static" >> $devfile - echo "IPADDR=$ip" >> $devfile +echo "BOOTPROTO=static" >> $devfile +echo "IPADDR=$ip" >> $devfile #if $netmask == "" ## Default to 255.255.255.0? #set $netmask = "255.255.255.0" #end if - echo "NETMASK=$netmask" >> $devfile - +echo "NETMASK=$netmask" >> $devfile #else - ## Leave the interface unconfigured ## we don't have enough info for static configuration - echo "BOOTPROTO=none" >> $devfile - +echo "BOOTPROTO=none" >> $devfile #end if - #else ## this is a DHCP interface, much less work to do - echo "BOOTPROTO=dhcp" >> $devfile +echo "BOOTPROTO=dhcp" >> $devfile #end if #else - # jcapel: # If you end up here, please mail the list... This shouldn't - # happen. ;-) + # happen. ;-) -- jcapel + #end if + ## If the interface is anything but a slave then add DNSn entry + #if $bonding.lower() != "slave" + #set $nct = 0 + #for $nameserver in $name_servers + #set $nct = $nct + 1 +echo "DNS$nct=$nameserver" >> $devfile + #end for #end if - - #set $nct = 0 - #for $nameserver in $name_servers - #set $ct = $nct + 1 - echo "DNS$ct=$nameserver" >> $devfile - #end for - #for $route in $static_routes #set routepattern = $re.compile("[0-9/.]+:[0-9.]+") #if $routepattern.match($route) #set $routebits = $route.split(":") #set [$network, $router] = $route.split(":") - echo "$network via $router" >> $routesfile +echo "$network via $router" >> $routesfile #else # Warning: invalid route "$route" #end if #end for #set $i = $i + 1 - # End configuration for $iname -#end for - +# End configuration for $iname + #end for + ## ============================================================================= + ## Configure name server search path in /etc/resolv.conf + #if $name_servers_search != "" +sed -i -e "/^search /d" /etc/resolv.conf +echo -n "search " >>/etc/resolv.conf + #for $nameserversearch in $name_servers_search +echo -n "$nameserversearch " >>/etc/resolv.conf + #end for +echo "" >>/etc/resolv.conf + #end if + ## ============================================================================= + ## Configure name server search path in /etc/resolv.conf + #if $name_servers_search != "" +sed -i -e "/^nameserver /d" /etc/resolv.conf + #for $nameserver in $name_servers +echo "nameserver $nameserver" >>/etc/resolv.conf + #end for + #end if ## Move all staged files to their final location rm -f /etc/sysconfig/network-scripts/ifcfg-* mv /etc/sysconfig/network-scripts/cobbler/* /etc/sysconfig/network-scripts/ rm -r /etc/sysconfig/network-scripts/cobbler +if [ -f "/etc/modprobe.conf" ]; then cat /etc/modprobe.conf.cobbler >> /etc/modprobe.conf rm -f /etc/modprobe.conf.cobbler - +fi #end if - # End post_install_network_config generated code diff --git a/snippets/post_s390_reboot b/snippets/post_s390_reboot new file mode 100644 index 00000000..aa38e40f --- /dev/null +++ b/snippets/post_s390_reboot @@ -0,0 +1,67 @@ +## RHEL zVM installs do not properly reboot into the installed system. This +## issue has been resolved in RHEL-5 Update3. To get a consistent reboot +## behavior for s390* installs on all distros, this snippet can be used. The +## snippet will attempt to discover the IPL volume zipl is being installed +## to and will attempt a reipl. Be sure to set this snippet as the *last* +## snippet your kickstart template. + +#if $arch.startswith("s390"): +%post --nochroot + +# Does the kickstart file request a reboot? +grep -q "^reboot" /tmp/ks.cfg /ks.cfg 2>/dev/null +if [ \$? -ne 0 ]; then + exit 0 +fi + +# find out the location of /boot and use it to re-ipl +boot_dev="" +for mountpt in /mnt/sysimage/boot /mnt/sysimage; +do + set -- \$(grep " $mountpt " /proc/mounts) + if [ -b "\$1" ]; then + boot_dev=\$1 + break + fi +done + +# lookup dasd disk +if [[ \$boot_dev == *dasd* ]]; then + # remove the '/dev/' (aka basename) + boot_dev=\${boot_dev\#\#/[^/]*/} + # strip partition number from dasd device + boot_dev=\${boot_dev%%[0-9]} + type="ccw" + id=`basename \$(readlink /sys/block/\$boot_dev/device)` + + # HACK - In RHEL4 and RHEL3 ... we do it the hard way + grep -q "^[34]\$" /.buildstamp 2>/dev/null + if [ \$? -eq 0 ]; then + cat <<EOF> /mnt/sysimage/tmp/zeboot.sh +\#!/bin/bash +/sbin/modprobe -r vmcp +rm -f "/dev/vmcp" +sleep 2 +[ -b "/dev/vmcp" ] || /bin/mknod /dev/vmcp c 10 61 +/sbin/modprobe -a vmcp +sync +# Force a boot (e.g. IPL 0100) +/sbin/vmcp ipl \${id\#\#*.} +EOF + /bin/chmod +x /mnt/sysimage/tmp/zeboot.sh + /bin/chroot /mnt/sysimage /tmp/zeboot.sh + # In RHEL5 ... lets cleanly shutdown (Update 3 and newer) + else + echo \$type > /sys/firmware/reipl/reipl_type + echo \$id > /sys/firmware/reipl/\$type/device + + # Force a reboot + pid=\$(cat /var/run/init.pid) + [ -z "\$pid" ] && pid=\$(pidof init) + kill -12 \$pid + pid=\$(cat /var/run/loader.run) + [ -z "\$pid" ] && pid=\$(pidof loader) + kill \$pid + fi +fi +#end if diff --git a/snippets/pre_anamon b/snippets/pre_anamon new file mode 100644 index 00000000..636dc1bf --- /dev/null +++ b/snippets/pre_anamon @@ -0,0 +1,4 @@ +#if $str($getVar('anamon_enabled','')) == "1" +wget -O /tmp/anamon.py "http://$server/cobbler/aux/anamon.py" +python /tmp/anamon.py --name "$name" --server "$server" --port "$http_port" +#end if diff --git a/snippets/pre_install_network_config b/snippets/pre_install_network_config index daba6709..59d1cd3c 100644 --- a/snippets/pre_install_network_config +++ b/snippets/pre_install_network_config @@ -51,7 +51,7 @@ #end if #end for #end if - #if $static.lower() == "true" and $ip != "" + #if $static and $ip != "" #if $netmask == "" ## Netmask not provided, default to /24. #set $netmask = "255.255.255.0" @@ -60,7 +60,7 @@ #if $gateway != "" #set $netinfo = "%s --gateway=%s" % ($netinfo, $gateway) #end if - #else if $static.lower() == "false" + #else if not $static #set $netinfo = "--bootproto=dhcp" #else ## Skip this interface, it's set as static, but without diff --git a/snippets/redhat_register b/snippets/redhat_register index 9d0f465c..ad3291ba 100644 --- a/snippets/redhat_register +++ b/snippets/redhat_register @@ -1,6 +1,5 @@ - -#if $redhat_management_type != "off" and $redhat_management_key != "" # begin Red Hat management server registration +#if $redhat_management_type != "off" and $redhat_management_key != "" mkdir -p /usr/share/rhn/ #if $redhat_management_type == "site" #set $mycert = "/usr/share/rhn/RHN-ORG-TRUSTED-SSL-CERT" @@ -11,8 +10,7 @@ wget http://$redhat_management_server/pub/RHN-ORG-TRUSTED-SSL-CERT -O $mycert #end if #set $endpoint = "https://%s/XMLRPC" % $redhat_management_server rhnreg_ks --serverUrl=$endpoint --sslCACert=$mycert --activationkey=$redhat_management_key -# end Red Hat management server registration #else # not configured to register to any Red Hat management server (ok) #end if - +# end Red Hat management server registration diff --git a/templates/build_report_email.template b/templates/build_report_email.template new file mode 100644 index 00000000..3dc622f7 --- /dev/null +++ b/templates/build_report_email.template @@ -0,0 +1,46 @@ +From: $from_addr +To: $to_addr +#if $getVar("interfaces","") != "" +Subject: $subject (system: $name, ip: $boot_ip) +#else +Subject: $subject (profile: $name, ip: $boot_ip) +#end if + +Cobbler build report. +======================================================= +http://fedorahosted.org/cobbler + +#if $getVar("interfaces","") != "": + +system name : $name +profile : $profile +distro : $distro + +#for $intf in $interfaces.keys(): + #set $mac = $interfaces[$intf].get("mac_address","") + #set $ip = $interfaces[$intf].get("ip_address","") + #set $static = $interfaces[$intf].get("static","") +interface: $intf +mac: $mac +ip: $ip +static: $static + +#end for +#else +profile : $name +distro : $distro + +#end if + +kernel_options: $getVar("kernel_options","") + +#if $kickstart.startswith("/") or $kickstart == "": +#if $getVar("interfaces","") != "" +kickstart=http://$server/cblr/svc/op/ks/system/$name +#else +kickstart=http://$server/cblr/svc/op/ks/profile/$name +#end if +#else +kickstart=$kickstart +#end if + diff --git a/templates/power_integrity.template b/templates/power_integrity.template new file mode 100644 index 00000000..46dd8856 --- /dev/null +++ b/templates/power_integrity.template @@ -0,0 +1 @@ +/usr/local/bin/fence_integrity -a $power_address -l $power_user -p $power_pass -o $power_mode diff --git a/templates/pxelocal_s390x.template b/templates/pxelocal_s390x.template new file mode 100644 index 00000000..40830374 --- /dev/null +++ b/templates/pxelocal_s390x.template @@ -0,0 +1 @@ +local diff --git a/templates/pxeprofile_s390x.template b/templates/pxeprofile_s390x.template new file mode 100644 index 00000000..0c6f1b08 --- /dev/null +++ b/templates/pxeprofile_s390x.template @@ -0,0 +1,11 @@ +$kernel_path +$initrd_path +## Only include kickstart URL -- all other parms in _parm and _conf files +#set $start = $append_line.find("ks=") +#set $end = $append_line.find(" ", $start) +#if $start != -1 +#if $end == -1 +#set $end = $len($append_line) +#end if +$append_line[$start:$end] +#end if diff --git a/templates/pxesystem_s390x.template b/templates/pxesystem_s390x.template index ba0c95ea..0c6f1b08 100644 --- a/templates/pxesystem_s390x.template +++ b/templates/pxesystem_s390x.template @@ -1,3 +1,11 @@ $kernel_path $initrd_path -$append_line +## Only include kickstart URL -- all other parms in _parm and _conf files +#set $start = $append_line.find("ks=") +#set $end = $append_line.find(" ", $start) +#if $start != -1 +#if $end == -1 +#set $end = $len($append_line) +#end if +$append_line[$start:$end] +#end if diff --git a/templates/s390x_conf.template b/templates/s390x_conf.template new file mode 100644 index 00000000..93af25b1 --- /dev/null +++ b/templates/s390x_conf.template @@ -0,0 +1,77 @@ +## Stuff content into a list so we can display a condensed format later +#if $isinstance($kernel_options, $list) +#set $argList = $kernel_options +#else +#set $argList = $kernel_options.split(" ") +#end if +#silent $argList.append("DASD=100-101,200") +#silent $argList.append("SUBCHANNELS=0.0.0600,0.0.0601,0.0.0602") +#silent $argList.append("NETTYPE=qeth") +#if $getVar('hostname', '') != '' +#silent $argList.append("HOSTNAME=%s" % $hostname) +#end if +#if $getVar('name_servers_search', '') != '' +#silent $argList.append("SEARCHDNS=%s" % ':'.join($name_servers_search)) +#end if +#if $getVar('gateway', '') != '' +#silent $argList.append("GATEWAY=%s" % $gateway) +#end if +#if $getVar('name_servers', '') != '' +#silent $argList.append("DNS=%s" % ':'.join($name_servers)) +#end if +#if $getVar("interfaces","") != "" and $interfaces.has_key("eth0") + #set $ip=$interfaces['eth0'].get('ip_address','') + #set $netmask=$interfaces['eth0'].get('subnet','') + #if $ip != '' + #set $tokens = $ip.split('.') + #set $tokens = $tokens[0:-1] + #set $broadcast = ".".join($tokens) + ".255" + #else + #set $broadcast = "" + #end if +#else + #set $ip="" + #set $netmask="" + #set $broadcast = "" +#end if +#if $ip != '' +#silent $argList.append("IPADDR=%s" % $ip) +#end if +## Unless provided, calculate the network using netmask and broadcast +#if $getVar('network', '') != '' +#silent $argList.append("NETWORK=%s" % $network) +#elif $netmask != '' and $ip != '' +#set $ip_split = $ip.split('.') +#set $nm_split = $netmask.split('.') +#set $nw_split = [] +#for $oct in $range($len($ip_split)) +#silent $nw_split.append("%s" % ($int($nm_split[$oct]) & $int($ip_split[$oct]))) +#end for +#set $network=".".join($nw_split) +#silent $argList.append("NETWORK=%s" % $network) +#end if +#if $netmask != '' +#silent $argList.append("NETMASK=%s" % $netmask) +#end if +#if $broadcast != '' +#silent $argList.append("BROADCAST=%s" % $broadcast) +#end if +#silent $argList.append("MTU=1500") +#silent $argList.append("PORTNAME=UNASSIGNED") +#silent $argList.append("PORTNO=0") +#silent $argList.append("LAYER2=0") +## ===================================== +## Now write out data. Content cannot be longer than 80 characters in length, +## and must not exceed 11 lines +## ===================================== +#set $output_str="" +#for $item in $argList +#if $len($output_str) + $len($item) >= 80 +#echo "%s\n" % $output_str.strip() +#set $output_str = "" +#end if +#set $output_str = "%s %s" % ($output_str, $item) +#end for +#if $len($output_str) > 0 +#echo "%s\n" % $output_str.strip() +#end if diff --git a/templates/s390x_parm.template b/templates/s390x_parm.template new file mode 100644 index 00000000..f536c0d0 --- /dev/null +++ b/templates/s390x_parm.template @@ -0,0 +1,21 @@ +## Stuff content into a list so we can display a condensed format later +#if $isinstance($kernel_options, $list) +#set $argList = $kernel_options +#else +#set $argList = $kernel_options.split(" ") +#end if +## ===================================== +## Now write out data. Content cannot be longer than 80 characters in length, +## and must not exceed 11 lines +## ===================================== +#set $output_str="" +#for $item in $argList +#if $len($output_str) + $len($item) >= 80 +#echo "%s\n" % $output_str.strip() +#set $output_str = "" +#end if +#set $output_str = "%s %s" % ($output_str, $item) +#end for +#if $len($output_str) > 0 +#echo "%s\n" % $output_str.strip() +#end if diff --git a/config/tftp b/templates/tftpd.template index bd54502d..bd54502d 100644 --- a/config/tftp +++ b/templates/tftpd.template diff --git a/tests/performance.py b/tests/performance.py index 922718b9..0ec3413d 100644 --- a/tests/performance.py +++ b/tests/performance.py @@ -8,7 +8,7 @@ import time import sys import random -N = 10000 +N = 3000 print "sample size is %s" % N api = capi.BootAPI() diff --git a/triggers/restart-services.trigger b/triggers/restart-services.trigger deleted file mode 100644 index 588fc524..00000000 --- a/triggers/restart-services.trigger +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/python - -import cobbler.api as capi -import os -import sys - -bootapi = capi.BootAPI() -settings = bootapi.settings() -manage_dhcp = str(settings.manage_dhcp).lower() -manage_dns = str(settings.manage_dns).lower() -manage_xinetd = str(settings.manage_xinetd).lower() -restart_dhcp = str(settings.restart_dhcp).lower() -restart_dns = str(settings.restart_dns).lower() -restart_xinetd = str(settings.restart_xinetd).lower() -omapi_enabled = settings.omapi_enabled -omapi_port = settings.omapi_port - -# load up our DHCP and DNS modules -bootapi.get_sync() -# bootapi.dhcp and bootapi.dns are now module references - -# special handling as we don't want to restart it twice -has_restarted_dnsmasq = False - -rc = 0 -if manage_dhcp != "0": - if bootapi.dhcp.what() == "isc": - if not omapi_enabled and restart_dhcp: - rc = os.system("/usr/sbin/dhcpd -t") - if rc != 0: - print "/usr/sbin/dhcpd -t failed" - sys.exit(rc) - rc = os.system("/sbin/service dhcpd restart") - elif bootapi.dhcp.what() == "dnsmasq": - if restart_dhcp: - rc = os.system("/sbin/service dnsmasq restart") - has_restarted_dnsmasq = True - else: - print "- error: unknown DHCP engine: %s" % bootapi.dhcp.what() - rc = 411 - -if manage_dns != "0" and restart_dns != "0": - if bootapi.dns.what() == "bind": - rc = os.system("/sbin/service named restart") - elif bootapi.dns.what() == "dnsmasq" and not has_restarted_dnsmasq: - rc = os.ssytem("/sbin/service dnsmasq restart") - -if manage_xinetd != "0" and restart_xinetd != "0": - rc = os.system("/sbin/service xinetd restart") - -sys.exit(rc) - diff --git a/triggers/status_post.trigger b/triggers/status_post.trigger deleted file mode 100644 index f69afe2c..00000000 --- a/triggers/status_post.trigger +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/python - -import os -import sys -import time - -objtype = sys.argv[1] # "system" or "profile" -name = sys.argv[2] # name of system or profile -ip = sys.argv[3] # ip or "?" - -fd = open("/var/log/cobbler/install.log","a+") -fd.write("%s\t%s\t%s\tstop\t%s\n" % (objtype,name,ip,time.time())) -fd.close() - -sys.exit(0) diff --git a/triggers/status_pre.trigger b/triggers/status_pre.trigger deleted file mode 100644 index 95df1fbf..00000000 --- a/triggers/status_pre.trigger +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/python - -import os -import sys -import time - -objtype = sys.argv[1] # "system" or "profile" -name = sys.argv[2] # name of system or profile -ip = sys.argv[3] # ip or "?" - -fd = open("/var/log/cobbler/install.log","a+") -fd.write("%s\t%s\t%s\tstart\t%s\n" % (objtype,name,ip,time.time())) -fd.close() - -sys.exit(0) diff --git a/webui_content/cobblerweb.css b/webui_content/cobblerweb.css index a1cc86a3..a535d89e 100644 --- a/webui_content/cobblerweb.css +++ b/webui_content/cobblerweb.css @@ -40,11 +40,13 @@ tr.roweven { background-color: #3f3d3d; } table.sortable th { background-color: #363a4e; margin: 0; + white-space: nowrap; } table.sortable caption { background-color: #202331; margin: 0; + white-space: nowrap; } td.nicedit { background-color: #444444; } diff --git a/webui_templates/blank.tmpl b/webui_templates/blank.tmpl index 0b0d4f7d..7b33dd50 100644 --- a/webui_templates/blank.tmpl +++ b/webui_templates/blank.tmpl @@ -1,8 +1,12 @@ #extends cobbler.webui.master #block body +#if $getVar('more_blank','') == '' You are now logged in to <A HREF="http://fedorahosted.org/cobbler">Cobbler</A>. Main screen turn on. - +#else +Cobbler awaits your command. +<!-- for great justice --> +#end if #end block body diff --git a/webui_templates/checkboxes.tmpl b/webui_templates/checkboxes.tmpl new file mode 100644 index 00000000..c4f5d97a --- /dev/null +++ b/webui_templates/checkboxes.tmpl @@ -0,0 +1,43 @@ + ## USAGE: # include "/path/to/this/file" + +<script language="Javascript"> +function items_check_all() +{ + var checkall=document.getElementById("itemsall").checked + var items=document.getElementsByName("items") + for( i = 0; i < items.length; ++i ) { + var item = items[i]; + item.checked=checkall; + } +} + +function items_checked_count() +{ + var items=document.getElementsByName("items") + var j=0; + for( i = 0; i < items.length; ++i ) { + var item = items[i]; + if (item.checked) { + j++; + } + } + return j; +} + +function items_checked_values() +{ + var items=document.getElementsByName("items") + var s=""; + for( i = 0; i < items.length; ++i ) { + var item = items[i]; + if (item.checked) { + if (s=="") { + s=item.value + } else { + s=s+" "+item.value; + } + } + } + return s; +} +</script> diff --git a/webui_templates/distro_edit.tmpl b/webui_templates/distro_edit.tmpl index f8cbf7d2..9c844800 100644 --- a/webui_templates/distro_edit.tmpl +++ b/webui_templates/distro_edit.tmpl @@ -162,6 +162,12 @@ function disablename(value) #else <input type="radio" name="arch" id="arch" value="ppc64">ppc64 #end if + + #if $distro and $distro.arch == "s390" + <input type="radio" name="arch" id="arch" value="s390" checked>s390 + #else + <input type="radio" name="arch" id="arch" value="s390">s390 + #end if #if $distro and $distro.arch == "s390x" <input type="radio" name="arch" id="arch" value="s390x" checked>s390x @@ -285,11 +291,40 @@ checked>Ubuntu <tr> <td> + <label for="redhatmanagementserver">Management Server</label> + </td> + <td> + <input type="text" size="255" style="width: 400px;" name="redhatmanagementserver" id="redhatmanagementserver" + #if $distro + value="$distro.redhat_management_server" + #end if + /> + <p class="context-tip">RHN, Satellite, or Spacewalk server</p> + </td> + </tr> + + <tr> + <td> + <label for="mgmt_classes">Management Classes</label> + </td> + <td> + #if $distro + #set joined = " ".join($distro.mgmt_classes) + <input type="text" size="255" style="width: 400px;" name="mgmt_classes" id="mgmt_classes" value="$joined" /> + #else + <input type="text" size="255" style="width: 400px;" name="mgmt_classes" id="mgmt_classes" value="" /> + #end if + <p class="context-tip">Management classes (space delimited) for use with external configuration management system.</p> + </td> + </tr> + + <tr> + <td> <label for="owners">Access Allowed For</label> </td> <td> #if $distro - #set ownerslist = ','.join($distro.owners) + #set ownerslist = ' '.join($distro.owners) #end if <input type="text" size="255" style="width: 400px;" name="owners" id="owners" #if $distro @@ -298,7 +333,7 @@ checked>Ubuntu value="$user" #end if /> - <p class="context-tip">Applies only if using authz_ownership module, comma-delimited</p> + <p class="context-tip">Applies only if using authz_ownership module, space delimited</p> </td> </tr> diff --git a/webui_templates/empty.tmpl b/webui_templates/empty.tmpl index 7a76ddad..9f6d6725 100644 --- a/webui_templates/empty.tmpl +++ b/webui_templates/empty.tmpl @@ -1,5 +1,9 @@ #extends cobbler.webui.master #block body +#if $getVar('search','') == '' No items found. Add some using the links on the left. +#else +No matches found. +#end if #end block body diff --git a/webui_templates/image_edit.tmpl b/webui_templates/image_edit.tmpl index f4e95884..8c369db1 100644 --- a/webui_templates/image_edit.tmpl +++ b/webui_templates/image_edit.tmpl @@ -336,8 +336,6 @@ function disablename(value) <option value="$it" #if $image and $image.image_type == $it selected="1" - #else if $it == "iso" - selected="1" #end if >$it</option> #end for @@ -355,7 +353,7 @@ function disablename(value) </td> <td> #if $image - #set ownerslist = ','.join($image.owners) + #set ownerslist = ' '.join($image.owners) #end if <input type="text" size="255" style="width: 400px;" name="owners" id="owners" #if $image @@ -364,7 +362,7 @@ function disablename(value) value="$user" #end if /> - <p class="context-tip">Applies only if using authz_ownership module, comma-delimited</p> + <p class="context-tip">Applies only if using authz_ownership module, space delimited</p> </td> </tr> diff --git a/webui_templates/master.tmpl b/webui_templates/master.tmpl index 2ef2d2a6..80865605 100644 --- a/webui_templates/master.tmpl +++ b/webui_templates/master.tmpl @@ -29,43 +29,54 @@ <li><a href="/cobbler/webui/wui.html" class="menu">Docs</a></li> <li><a href="$base_url?mode=settings_view" class="menu">Settings</a></li> <li><hr/></li> - <li><a href="$base_url?mode=distro_list" class="menu">Distros</a></li> - #if $mode == "distro_list" + <li><a class="menu">Distros</span></li> + ##if $mode.find("distro") != -1 <ul id="navaction"> + <li><a href="$base_url?mode=distro_list" class="menu">View</a></li> + <li><a href="$base_url?mode=distro_search" class="menu">Search</a></li> <li><a href="$base_url?mode=distro_edit" class="menu">Add</a></li> </ul> - #end if - <li><a href="$base_url?mode=profile_list" class="menu">Profiles</a></li> - #if $mode == "profile_list" + ##end if + <li><a class="menu">Profiles</span></li> + ##if $mode.find("profile") != -1 <ul id="navaction"> + <li><a href="$base_url?mode=profile_list" class="menu">View</a></li> + <li><a href="$base_url?mode=profile_search" class="menu">Search</a></li> <li><a href="$base_url?mode=profile_edit" class="menu">Add</a></li> <li><a href="$base_url?mode=subprofile_edit" class="menu">Add child</a></li> </ul> - #end if - <li><a href="$base_url?mode=system_list" class="menu">Systems</a></li> - #if $mode == "system_list" + ##end if + <li><a class="menu">Systems</span></li> + ##if $mode.find("system") != -1 <ul id="navaction"> - <li><a href="$base_url?mode=system_edit" class="menu">Add</a></li> + <li><a href="$base_url?mode=system_list" class="menu">View</a></li> + <li><a href="$base_url?mode=system_search" class="menu">Search</a></li> + <li><a href="$base_url?mode=system_edit_new" class="menu">Add</a></li> </ul> - #end if - <li><a href="$base_url?mode=ksfile_list" class="menu">Kickstarts</a></li> - #if $mode == "ksfile_list" + ##end if + <li><a class="menu">Kickstarts</span></li> + ##if $mode.find("ksfile") != -1 <ul id="navaction"> + <li><a href="$base_url?mode=ksfile_list" class="menu">View</a></li> <li><a href="$base_url?mode=ksfile_new" class="menu">Add</a></li> </ul> - #end if - <li><a href="$base_url?mode=repo_list" class="menu">Repos</a></li> - #if $mode == "repo_list" + ##end if + <li><a class="menu">Repos</span></li> + ##if $mode.find("repo") != -1 <ul id="navaction"> + <li><a href="$base_url?mode=repo_list" class="menu">View</a></li> + <li><a href="$base_url?mode=repo_search" class="menu">Search</a></li> <li><a href="$base_url?mode=repo_edit" class="menu">Add</a></li> </ul> - #end if - <li><a href="$base_url?mode=image_list" class="menu">Images</a></li> - #if $mode == "image_list" + ##end if + <li><a class="menu">Images</span></li> + ##if $mode.find("image") != -1 <ul id="navaction"> + <li><a href="$base_url?mode=image_list" class="menu">View</a></li> + <li><a href="$base_url?mode=image_search" class="menu">Search</a></li> <li><a href="$base_url?mode=image_edit" class="menu">Add</a></li> </ul> - #end if + ##end if <li><hr/><br/></li> <li><a class="button sync" href="$base_url?mode=sync">Sync</a></li> </ul> diff --git a/webui_templates/paginate.tmpl b/webui_templates/paginate.tmpl index 9b20e560..66bf6345 100644 --- a/webui_templates/paginate.tmpl +++ b/webui_templates/paginate.tmpl @@ -1,3 +1,4 @@ +#if $page > 0 ## USAGE: # set global what="system" ## # include "/path/to/this/file" @@ -28,4 +29,4 @@ <br/> <br/> - +#end if diff --git a/webui_templates/profile_edit.tmpl b/webui_templates/profile_edit.tmpl index 960d5800..1091de5a 100644 --- a/webui_templates/profile_edit.tmpl +++ b/webui_templates/profile_edit.tmpl @@ -246,15 +246,30 @@ function disablename(value) <td> #if $profile #set joined = " ".join($profile.name_servers) - <input type="text" name="name_servers" id="name_servers" value="$joined"> + <input type="text" size="255" style="width: 400px;" name="name_servers" id="name_servers" value="$joined" /> #else - <input type="text" name="name_servers" id="name_servers" value=""> + <input type="text" size="255" style="width: 400px;" name="name_servers" id="name_servers" value="" /> #end if <p class="context-tip">Name servers, space delimited, if not provided by DHCP</p> </td> </tr> <tr> + <td> + <label for="name_servers_search">Name Servers Search Path</label> + </td> + <td> + #if $profile + #set joined = " ".join($profile.name_servers_search) + <input type="text" size="255" style="width: 400px;" name="name_servers_search" id="name_servers_search" value="$joined" /> + #else + <input type="text" size="255" style="width: 400px;" name="name_servers_search" id="name_servers_search" value="" /> + #end if + <p class="context-tip">Name servers search path, space delimited, if not provided by DHCP</p> + </td> + </tr> + + <tr> <td class="virtedit"> <label for="virtfilesize">Virt Disk (GB)</label> </td> @@ -430,6 +445,36 @@ redhatmanagementkey" </td> </tr> + <tr> + <td> + <label for="redhatmanagementserver">Management Server</label> + </td> + <td> + <input type="text" size="255" style="width: 400px;" name="redhatmanagementserver" id=" +redhatmanagementserver" + #if $profile + value="$profile.redhat_management_server" + #end if + /> + <p class="context-tip">RHN, Satellite, or Spacewalk server</p> + </td> + </tr> + + <tr> + <td> + <label for="mgmt_classes">Management Classes</label> + </td> + <td> + #if $profile + #set joined = " ".join($profile.mgmt_classes) + <input type="text" size="255" style="width: 400px;" name="mgmt_classes" id="mgmt_classes" value="$joined" /> + #else + <input type="text" size="255" style="width: 400px;" name="mgmt_classes" id="mgmt_classes" value="" /> + #end if + <p class="context-tip">Management classes (space delimited) for use with external configuration management system.</p> + </td> + </tr> + <tr> <td> @@ -437,7 +482,7 @@ redhatmanagementkey" </td> <td> #if $profile - #set ownerslist = ','.join($profile.owners) + #set ownerslist = ' '.join($profile.owners) #end if <input type="text" size="255" style="width: 400px;" name="owners" id="owners" #if $profile @@ -447,7 +492,7 @@ redhatmanagementkey" #end if /> - <p class="context-tip">Applies only if using authz_ownership module, comma-delimited</p> + <p class="context-tip">Applies only if using authz_ownership module, space-delimited</p> </td> </tr> diff --git a/webui_templates/repo_edit.tmpl b/webui_templates/repo_edit.tmpl index 6ac2de48..62f0e9ed 100644 --- a/webui_templates/repo_edit.tmpl +++ b/webui_templates/repo_edit.tmpl @@ -184,7 +184,7 @@ function disablename(value) value="$repo.rpm_list" #end if /> - <p class="context-tip">Blank, or a list of specific RPMs (and only those RPMs) to mirror.</p> + <p class="context-tip">Blank, or a list of specific RPMs (and only those RPMs) to mirror, space delimited.</p> </td> </tr> @@ -228,6 +228,11 @@ function disablename(value) #else <input type="radio" name="arch" id="arch" value="ppc64">ppc64 #end if + #if $repo and $repo.arch == "s390" + <input type="radio" name="arch" id="arch" value="s390" checked>s390 + #else + <input type="radio" name="arch" id="arch" value="s390">s390 + #end if #if $repo and $repo.arch == "s390x" <input type="radio" name="arch" id="arch" value="s390x" checked>s390x #else @@ -238,6 +243,19 @@ function disablename(value) #else <input type="radio" name="arch" id="arch" value="ia64">ia64 #end if + #if $repo and $repo.arch == "noarch" + <input type="radio" name="arch" id="arch" value="noarch" checked>noarch + #else + <input type="radio" name="arch" id="arch" value="noarch">noarch + #end if + #if $repo and $repo.arch == "src" + <input type="radio" name="arch" id="arch" value="src" checked>src + #else + <input type="radio" name="arch" id="arch" value="src">src + #end if + + + <p class="context-tip">What architecture is the repo?</p> </td> </tr> @@ -278,7 +296,7 @@ function disablename(value) </td> <td> #if $repo - #set ownerslist = ','.join($repo.owners) + #set ownerslist = ' '.join($repo.owners) #end if <input type="text" size="255" style="width: 400px;" name="owners" id="owners" #if $repo @@ -287,7 +305,7 @@ function disablename(value) value="$user" #end if /> - <p class="context-tip">Applies only if using authz_ownership module, comma-delimited</p> + <p class="context-tip">Applies only if using authz_ownership module, space delimited</p> </td> </tr> diff --git a/webui_templates/search.tmpl b/webui_templates/search.tmpl new file mode 100644 index 00000000..f3fca6cb --- /dev/null +++ b/webui_templates/search.tmpl @@ -0,0 +1,45 @@ +#extends cobbler.webui.master +#block body + +<script language="javascript"> +</script> + +#set $form_dest=$base_url+"?mode="+$what+"_search_execute" + +<form method="POST" action="$form_dest"> +<fieldset id="cform"> + + <legend>$caption</legend> + <table border=0> + + <tr> + + <td> + <select id="key1" name="key1"> + <option value="name">name</option> + #if $what == "distro": + <option value="kernel">kernel</option> + <option value="initrd">initrd</option> + #elif $what == "profile" + <option value="distro">distro</option> + #elif $what == "system" + <option value="profile">profile</option> + <option value="image">image</option> + ## FIMXE: more values for more fields, add images, add repos + #end if + </select> + </td> + + ## FIXME copy and paste key2,value2 and key3,value3 in a loop + <td> + <input type="text" id="value1" name="value1"/> + </td> + + </table> + + <input type="submit" value="search"/> + +</fieldset> + +</form> +#end block body diff --git a/webui_templates/system_delete.tmpl b/webui_templates/system_delete.tmpl new file mode 100644 index 00000000..a798ddcc --- /dev/null +++ b/webui_templates/system_delete.tmpl @@ -0,0 +1,39 @@ +#extends cobbler.webui.master + +#block body + +<form name="myform" method="post" action="$base_url?mode=system_delete"> + <input type="hidden" name="targetlist" id="targetlist" value="$targetlist"/> + + <table class="sortable"> + <thead> + <caption>Confirm deleting the following systems</caption> + <tr> + <th class="text">Name</th> + </tr> + </thead> + <tbody> + #set $evenodd = 1 + #for $system in $systems + #if $evenodd % 2 == 0 + #set $tr_class = "roweven" + #else + #set $tr_class = "rowodd" + #end if + #set $evenodd += 1 + + <tr class="$tr_class"> + <td> + ${system.name} + </td> + </tr> + #end for + </tbody> + </table> + + </p> + + <input type="submit" name="delete" value="Delete Systems"/> + <input type="button" name="cancel" onclick="javascript:history.go(-1)" value="Cancel"/> +</form> +#end block body diff --git a/webui_templates/system_edit.tmpl b/webui_templates/system_edit.tmpl index fd471c64..cf047258 100644 --- a/webui_templates/system_edit.tmpl +++ b/webui_templates/system_edit.tmpl @@ -120,12 +120,6 @@ function on_interface_delete() selected = get_selected_interface() interfaces = document.getElementById("interfaces") - - if (interfaces.value == no_delete) { - alert("the default interface cannot be deleted") - return - } - if (interfaces.length == 1) { alert("systems must always have at least one interface") return @@ -320,6 +314,7 @@ function on_form_submit() } } document.getElementById("interface_list").value = listing + document.getElementById("name").disabled= false document.myform.submit() } #end raw @@ -328,7 +323,6 @@ function page_onload() { interface_table = build_interface_table() last_interface = get_selected_interface() load_intf() - no_delete = "eth0" } </script> @@ -343,16 +337,14 @@ function page_onload() { <fieldset id="cform"> <input name="interface_list" type="hidden" value="" id="interface_list"/> - - #if $system - <input type="hidden" name="new_or_edit" value="edit"/> - <input type="hidden" name="oldname" value="$system.name"/> + <input type="hidden" name="editmode" value="$editmode"/> + + #if $editmode == "new" + <legend>Add a System</legend> #else - <input type="hidden" name="new_or_edit" value="new"/> + <legend>Edit a System</legend> #end if - - <legend>Edit a System</legend> - + <table border=0> <tr> @@ -360,14 +352,10 @@ function page_onload() { <label for="name">System Name</label> </td> <td> - #if $system - <input type="text" size="128" style="width: 150px;" name="name" id="name" disabled="true" - #else <input type="text" size="128" style="width: 150px;" name="name" id="name" + #if $editmode == "edit" + value="$system.name" disabled="true" #end if - #if $system - value="$system.name" - #end if /> <p class="context-tip">Example: vanhalen</p> </td> @@ -376,22 +364,6 @@ function page_onload() { #if $system <tr> <td> - <label for="mode">Edit Mode</label> - </td> - <td> - <input type="radio" name="editmode" value="edit" checked onclick="javascript:disablename(true)">Edit - <input type="radio" name="editmode" value="rename" onclick="javascript:disablename(false)">Rename + Edit - <input type="radio" name="editmode" value="copy" onclick="javascript:disablename(false)">Copy + Edit - <p class="context-tip">How do you want to modify this object?</p> - </td> - </tr> - #else - <input type="hidden" name="editmode" value="new"/> - #end if - - #if $system - <tr> - <td> <label>Created</label> </td> <td> @@ -538,11 +510,26 @@ redhatmanagementkey" <tr> <td> + <label for="redhatmanagementserver">Management Server</label> + </td> + <td> + <input type="text" size="255" style="width: 400px;" name="redhatmanagementserver" id=" +redhatmanagementserver" + #if $system + value="$system.redhat_management_server" + #end if + /> + <p class="context-tip">RHN, Satellite, or Spacewalk server</p> + </td> + </tr> + + <tr> + <td> <label for="owners">Access Allowed For</label> </td> <td> #if $system - #set ownerslist = ','.join($system.owners) + #set ownerslist = ' '.join($system.owners) #end if <input type="text" size="255" style="width: 400px;" name="owners" id="owners" #if $system @@ -551,7 +538,7 @@ redhatmanagementkey" value="$user" #end if /> - <p class="context-tip">Applies only if using authz_ownership module, comma-delimited</p> + <p class="context-tip">Applies only if using authz_ownership module, space delimited</p> </td> </tr> @@ -561,9 +548,9 @@ redhatmanagementkey" </td> <td class="netedit"> #if $system - <input type="text" size="64" style="width: 150px;" name="hostname" id="hostname" value="$system.hostname" /> + <input type="text" size="64" style="width: 200px;" name="hostname" id="hostname" value="$system.hostname" /> #else - <input type="text" size="64" style="width: 150px;" name="hostname" id="hostname" /> + <input type="text" size="64" style="width: 200px;" name="hostname" id="hostname" /> #end if <p class="context-tip">Ex: "vanhalen.example.org". Used for /etc/sysconfig/network.</p> </td> @@ -590,16 +577,46 @@ redhatmanagementkey" </td> <td class="netedit"> #if $system - #set joined = " ".join($profile.name_servers) - <input type="text" name="name_servers" id="name_servers" value="$joined"> + #set joined = " ".join($system.name_servers) + <input type="text" size="255" style="width: 400px;" name="name_servers" id="name_servers" value="$joined" /> #else - <input type="text" name="name_servers" id="name_servers" value="<<inherit>>"> + <input type="text" size="255" style="width: 400px;" name="name_servers" id="name_servers" value="<<inherit>>" /> #end if <p class="context-tip">Name servers, space delimited, if not provided by DHCP</p> </td> </tr> <tr> + <td class="netedit"> + <label for="name_servers_search">Name Servers Search Path</label> + </td> + <td class="netedit"> + #if $system + #set joined = " ".join($system.name_servers_search) + <input type="text" size="255" style="width: 400px;" name="name_servers_search" id="name_servers_search" value="$joined" /> + #else + <input type="text" size="255" style="width: 400px;" name="name_servers_search" id="name_servers_search" value="<<inherit>>" /> + #end if + <p class="context-tip">Name servers search path, space delimited, if not provided by DHCP</p> + </td> + </tr> + + <tr> + <td class="netedit"> + <label for="mgmt_classes">Management Classes</label> + </td> + <td class="netedit"> + #if $system + #set joined = " ".join($system.mgmt_classes) + <input type="text" size="255" style="width: 400px;" name="mgmt_classes" id="mgmt_classes" value="$joined" /> + #else + <input type="text" size="255" style="width: 400px;" name="mgmt_classes" id="mgmt_classes" value="" /> + #end if + <p class="context-tip">Management classes (space delimited) for use with external configuration management system.</p> + </td> + </tr> + + <tr> <td class="virtedit"> <label for="virtfilesize">Virt Disk (GB)</label> </td> @@ -705,7 +722,7 @@ redhatmanagementkey" </td> <td class="poweredit"> <select name="power_type" id="power_type"> - #set valid_power = [ "bullpap", "wti", "apc_snmp", "ether-wake", "ipmilan", "drac", "ipmitool", "ilo", "rsa", "lpar", "bladecenter", "virsh" ] + #set valid_power = [ "bullpap", "wti", "apc_snmp", "ether-wake", "ipmilan", "drac", "ipmitool", "ilo", "rsa", "lpar", "bladecenter", "virsh" ,"integrity"] #set nothing = valid_power.sort() #for $value in $valid_power: @@ -969,19 +986,6 @@ redhatmanagementkey" </td> </tr> - #if $system and $editable == True - <tr> - <td> - <label for="delete">Delete</label> - </td> - <td> - <input type="checkbox" name="delete1" value="delete1">Yes - <input type="checkbox" name="delete2" value="delete2">Really - <p class="context-tip">Check both buttons and click save to delete this object</p> - </td> - </tr> - #end if - #if $editable == True <tr> <td> diff --git a/webui_templates/system_list.tmpl b/webui_templates/system_list.tmpl index 6ab5face..9899ca51 100644 --- a/webui_templates/system_list.tmpl +++ b/webui_templates/system_list.tmpl @@ -7,14 +7,71 @@ #include "/usr/share/cobbler/webui_templates/paginate.tmpl" ## ==== END PAGE NAVIGATION ==== -<table class="sortable"> + ## ==== BEGIN CHECKBOX SUPPORT ==== + #include "/usr/share/cobbler/webui_templates/checkboxes.tmpl" + ## ==== END CHECKBOX SUPPORT ==== + +<script language="Javascript"> +function action_selected(actionname) +{ + var itemcount=items_checked_count(); + if ( + (itemcount == 0) && + ( + (actionname == "delete") || + (actionname == "netboot") || + (actionname == "profile") || + (actionname == "power") + ) + ) + { + alert("Select the systems to " + actionname+" first"); + return; + } + else if ( + (itemcount != 1) && + ( + (actionname == "copy") || + (actionname == "rename") + ) + ) + { + alert("Select only one system to " + actionname); + return; + } + + document.getElementById("targetlist").value=items_checked_values(); + document.getElementById("actionname").value=actionname; + + document.myform.submit(); +} + +function action_single(actionname,target) +{ + document.getElementById("targetlist").value=target; + document.getElementById("actionname").value=actionname; + + document.myform.submit(); +} +</script> + +<form name="myform" method="post" action="$base_url?mode=system_list_action"> + <input type="hidden" name="actionname" id="actionname" value=""/> + <input type="hidden" name="targetlist" id="targetlist" value=""/> + + <table class="sortable"> <thead> <caption>Cobbler Systems</caption> <tr> + <th class="text"><input type="checkbox" id="itemsall" onclick="javascript:items_check_all();"></th> <th class="text">Name</th> + <th class="text">Netboot</th> <th class="text">Profile</th> <th class="text">Kickstart</th> + <th class="text"></th> + <th class="text"></th> + <th class="text"></th> </tr> </thead> <tbody> @@ -29,8 +86,21 @@ <tr class="$tr_class"> <td> + <input type="checkbox" name="items" value="${system.name}"> + </td> + + <td> <a href="$base_url?mode=system_edit&name=${system.name}">${system.name}</a> </td> + + <td> + #if str($system.netboot_enabled) != "False" + Yes + #else + No + #end if + </td> + <td> <a href="$base_url?mode=profile_edit&name=${system.profile}">${system.profile}</a> </td> @@ -55,10 +125,33 @@ #end if #end if </td> + + <td> + <input type="button" name="edit" onClick="javascript:action_single('edit','${system.name}')" value="Edit"/> + </td> + + <td> + <input type="button" name="rename" onClick="javascript:action_single('rename','${system.name}')" value="Rename"/> + </td> + + <td> + <input type="button" name="copy" onClick="javascript:action_single('copy','${system.name}')" value="Copy"/> + </td> + </tr> #end for </tbody> -</table> + </table> +</form> + + <br/> + Operations on selected systems: + <br/> + <input type="button" name="delete" onClick="javascript:action_selected('delete')" value="Delete"/> + <input type="button" name="netboot" onClick="javascript:action_selected('netboot')" value="Change Netboot"/> + <input type="button" name="profile" onClick="javascript:action_selected('profile')" value="Change Profile"/> + <input type="button" name="power" onClick="javascript:action_selected('power')" value="Power control"/> + #end block body diff --git a/webui_templates/system_netboot.tmpl b/webui_templates/system_netboot.tmpl new file mode 100644 index 00000000..717e27bc --- /dev/null +++ b/webui_templates/system_netboot.tmpl @@ -0,0 +1,59 @@ +#extends cobbler.webui.master + +#block body + +<script language="Javascript"> +function submit_netboot(value) +{ + document.getElementById("netboot").value=value; + document.myform.submit(); +} +</script> + +<form name="myform" method="post" action="$base_url?mode=system_netboot"> + <input type="hidden" name="targetlist" id="targetlist" value="$targetlist"/> + <input type="hidden" name="netboot" id="netboot" value="0"/> + + <table class="sortable"> + <thead> + <caption>Change netboot of the following systems</caption> + <tr> + <th class="text">Name</th> + <th class="text">Current Netboot</th> + </tr> + </thead> + <tbody> + #set $evenodd = 1 + #for $system in $systems + #if $evenodd % 2 == 0 + #set $tr_class = "roweven" + #else + #set $tr_class = "rowodd" + #end if + #set $evenodd += 1 + + <tr class="$tr_class"> + <td> + ${system.name} + </td> + + <td> + #if str($system.netboot_enabled) != "False" + Yes + #else + No + #end if + </td> + + </tr> + #end for + </tbody> + </table> + + </p> + + <input type="button" name="pxeenable" onclick="javascript:submit_netboot(1)" value="Enable Netboot"/> + <input type="button" name="pxedisable" onclick="javascript:submit_netboot(0)" value="Disable Netboot"/> + <input type="button" name="cancel" onclick="javascript:history.go(-1)" value="Cancel"/> +</form> +#end block body diff --git a/webui_templates/system_power.tmpl b/webui_templates/system_power.tmpl new file mode 100644 index 00000000..9f0011e7 --- /dev/null +++ b/webui_templates/system_power.tmpl @@ -0,0 +1,66 @@ +#extends cobbler.webui.master + +#block body + +<script language="Javascript"> +function submit_power(value) +{ + document.getElementById("power").value=value; + document.myform.submit(); +} +</script> + +<form name="myform" method="post" action="$base_url?mode=system_power"> + <input type="hidden" name="targetlist" id="targetlist" value="$targetlist"/> + <input type="hidden" name="power" id="power" value=""/> + + <table class="sortable"> + <thead> + <caption>Control power of the following systems</caption> + <tr> + <th class="text">Name</th> + <th class="text">Power Type</th> + <th class="text">Power Address</th> + </tr> + </thead> + <tbody> + #set $evenodd = 1 + #for $system in $systems + #if $evenodd % 2 == 0 + #set $tr_class = "roweven" + #else + #set $tr_class = "rowodd" + #end if + #set $evenodd += 1 + + <tr class="$tr_class"> + <td> + ${system.name} + </td> + + <td> + #set valid_power = [ "none", "bullpap", "wti", "apc_snmp", "ether-wake", "ipmilan", "drac", "ipmitool", "ilo", "rsa", "lpar", "bladecenter" ] + #set nothing = valid_power.sort() + #for $value in $valid_power: + #if $system and (($system.power_type == $value) or ($system.power_type == "" and $value == "none")) + $value + #end if + #end for + </td> + + <td> + ${system.power_address} + </td> + </tr> + #end for + </tbody> + </table> + + </p> + + <input type="button" name="reboot" onclick="javascript:submit_power('reboot')" value="Reboot"/> + <input type="button" name="poweron" onclick="javascript:submit_power('on')" value="Power On"/> + <input type="button" name="poweroff" onclick="javascript:submit_power('off')" value="Power Off"/> + <input type="button" name="cancel" onclick="javascript:history.go(-1)" value="Cancel"/> +</form> +#end block body diff --git a/webui_templates/system_profile.tmpl b/webui_templates/system_profile.tmpl new file mode 100644 index 00000000..d230da68 --- /dev/null +++ b/webui_templates/system_profile.tmpl @@ -0,0 +1,63 @@ +#extends cobbler.webui.master + +#block body + +<script language="Javascript"> +function submit_netboot(value) +{ + document.getElementById("netboot").value=value; + document.myform.submit(); +} +</script> + +<form name="myform" method="post" action="$base_url?mode=system_profile"> + <input type="hidden" name="targetlist" id="targetlist" value="$targetlist"/> + + <table class="sortable"> + <thead> + <caption>Change profile of the following systems</caption> + <tr> + <th class="text">Name</th> + <th class="text">Current Profile</th> + </tr> + </thead> + <tbody> + #set $evenodd = 1 + #for $system in $systems + #if $evenodd % 2 == 0 + #set $tr_class = "roweven" + #else + #set $tr_class = "rowodd" + #end if + #set $evenodd += 1 + + <tr class="$tr_class"> + <td> + ${system.name} + </td> + + <td> + ${system.profile} + </td> + + </tr> + #end for + </tbody> + </table> + + <p> + <select name="profile" id="profile"> + #for $profile in $profiles: + <option name="$profile.name" + #if $systems[0].profile == $profile.name + selected="1" + #end if + >$profile.name</option> + #end for + </select> + </p> + + <input type="submit" name="change" value="Change profile"/> + <input type="button" name="cancel" onclick="javascript:history.go(-1)" value="Cancel"/> +</form> +#end block body diff --git a/webui_templates/system_rename.tmpl b/webui_templates/system_rename.tmpl new file mode 100644 index 00000000..14036529 --- /dev/null +++ b/webui_templates/system_rename.tmpl @@ -0,0 +1,41 @@ +#extends cobbler.webui.master + +#block body + +<form name="myform" method="post" action="$base_url?mode=system_rename"> + <input type="hidden" name="targetlist" id="targetlist" value="$targetlist"/> + + <table class="sortable"> + <thead> + <caption>Rename the following system</caption> + <tr> + <th class="text">Name</th> + </tr> + </thead> + <tbody> + #set $evenodd = 1 + #for $system in $systems + #if $evenodd % 2 == 0 + #set $tr_class = "roweven" + #else + #set $tr_class = "rowodd" + #end if + #set $evenodd += 1 + + <tr class="$tr_class"> + <td> + ${system.name} + </td> + </tr> + #end for + </tbody> + </table> + + <p> + New name: <input type="text" size="128" style="width: 150px;" name="name" id="name" value=$systems[0].name> + </p> + + <input type="submit" name="rename" value="Rename System"/> + <input type="button" name="cancel" onclick="javascript:history.go(-1)" value="Cancel"/> +</form> +#end block body |