summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathan Straz <nstraz@redhat.com>2009-02-02 18:36:40 -0500
committerNathan Straz <nstraz@redhat.com>2009-02-02 18:36:40 -0500
commit7e1247b426ae86d7c660a9ca87f76d43773670f6 (patch)
treeb91f593102a185a998bec50255e52ef0022de948
downloadsteeltoe-7e1247b426ae86d7c660a9ca87f76d43773670f6.tar.gz
steeltoe-7e1247b426ae86d7c660a9ca87f76d43773670f6.tar.xz
steeltoe-7e1247b426ae86d7c660a9ca87f76d43773670f6.zip
Import a sanitized version of Steel Toe, the provisioning system.
-rw-r--r--DESIGN166
-rw-r--r--Makefile38
-rw-r--r--README390
-rw-r--r--autoks-args.xsl12
-rw-r--r--dhcpd.xsl17
-rw-r--r--eliloconf.xsl21
-rw-r--r--kickstart.xsl84
-rw-r--r--list.xsl36
-rw-r--r--pxeconf.xsl19
-rw-r--r--select-ks.xsl11
-rw-r--r--setup.py9
-rw-r--r--st_web/README34
-rw-r--r--st_web/__init__.py0
-rw-r--r--st_web/context_processors.py4
-rw-r--r--st_web/st_config.py114
-rw-r--r--st_web/templates/st_web.css109
-rw-r--r--st_web/templates/st_web/confirm.html23
-rw-r--r--st_web/templates/st_web/index.html14
-rw-r--r--st_web/templates/st_web/install.html129
-rw-r--r--st_web/templates/st_web/perform.html15
-rw-r--r--st_web/templates/st_web/virtconfirm.html39
-rw-r--r--st_web/templates/st_web/virthosts.html170
-rw-r--r--st_web/templates/st_web/virtperform.html15
-rw-r--r--st_web/templates/st_web/virtvolumes.html65
-rw-r--r--st_web/templatetags/__init__.py0
-rw-r--r--st_web/templatetags/ifin.py72
-rw-r--r--st_web/urls.py11
-rw-r--r--st_web/views.py262
-rw-r--r--st_web/widgets.py71
-rw-r--r--steeltoe.sh528
-rw-r--r--steeltoe.spec107
-rw-r--r--steeltoe.xml140
-rw-r--r--tftp.conf7
-rw-r--r--yaboot.xsl18
34 files changed, 2750 insertions, 0 deletions
diff --git a/DESIGN b/DESIGN
new file mode 100644
index 0000000..24fe638
--- /dev/null
+++ b/DESIGN
@@ -0,0 +1,166 @@
+Design
+======
+
+Steel Toe is a system for automating kickstart installs. It aims to be
+as hands off as possible.
+
+
+The control chain
+-----------------
+
+There are several pieces that are required in order for a network
+kickstart install to work. Here is a list of those pieces and what
+Steel Toe does and does not do for each piece.
+
+ DHCP Server
+ A DHCP server is required to answer the PXE request from the
+ client machine's BIOS/EFI. The DHCP server will respond with an
+ address, network information, a filename, and the next server to
+ contact (usually the TFTP server).
+
+ Steel Toe does not manage the DHCP server's config file or
+ object database. It can generate a section that can be
+ integrated into the ISC DHCPD's config file. The idea here is
+ that the DHCP config information doesn't need to change unless
+ there is a major configuration change (MAC address change,
+ subnet migration, etc). The network information should not
+ change, nor the filename of the bootloader (pxelinux.0 or
+ elilo.efi), nor the address of the TFTP server between installs.
+
+ TFTP server
+ A TFTP server is required to hand out the boot loaders and
+ config files to client machines.
+
+ Steel Toe does not provide a TFTP server, your OS should.
+
+ PXE/EFI boot loader
+ These files should be placed in your TFTP server directory.
+ Your dhcpd config file should point each client machine at the
+ correct file.
+
+ Steel Toe does not provide the boot loader files.
+
+ PXE/EFI config files
+ These files should be placed in your TFTP server directory.
+ There will be one file for each client machine.
+
+ Steel Toe will have to generate these files.
+ Depending how much indirection we want to put on the file
+ system, these could be generated once.
+
+ Kernel and initrd images
+ These files are referenced in the PXE/EFI config files.
+ They need to be "in" the TFTP server directory so PXE/EFI can
+ fetch them.
+
+ Steel Toe will need to know where to find these files so it can
+ symlink them into the right host tree.
+
+ Kickstart file
+ These file are also referenced in the PXE/EFI config files.
+ They don't actually need to be on the file system. They could
+ be generated on the fly by a CGI script.
+
+ Steel Toe will be responsible for generating kickstart files.
+
+ Install tree
+ These are the files that make up a full distribution install
+ tree. It contains the kernel images, anaconda stage two images,
+ and all RPM packages for the distribution.
+
+ Steel Toe will be responsible for knowing where these bits are
+ and storing some classification information. It will not be
+ responsible for putting the files where they need to be. It
+ will provide a command to learn when new bits are available,
+ where the new bits are, and how they should be classified.
+
+DHCP config
+-----------
+
+filename option should look like "/tftpboot/hosts/$hostname/pxelinux.0"
+The rest is up to the admin.
+
+TFTP Structure
+--------------
+
+Steel Toe will generate and use the following type of directory
+structure inside the TFTP root.
+
+hosts/
+ $hostname/
+ pxelinux.0 # could be a link to another file
+ pxelinux.cfg -> . # so the config file is here
+ default # PXE config file
+ vmlinuz -> real-vmlinuz # symlink to vmlinuz to boot with
+ initrd.img -> real-... # symlink to initrd.img to boot with
+ ks.cfg # kickstart file
+
+Here are the benefits for this method:
+
+ * We can always use the "default" config file. We don't have to figure
+ out what the MAC address is, or convert the IP address to
+ hexadecimal.
+
+ * The PXE config file doesn't have to change. They can point to
+ vmlinuz and initrd.img in the current directory. We can just change
+ the symlinks to point to whatever we want to install.
+
+ * We can easily tell Apache to pull the tftpboot structure into the web
+ name space. If we do that we don't have to keep track of another
+ directory to put generated files.
+
+Usage Cases
+===========
+
+Add new machine
+---------------
+
+ 1. Create XML document with new information
+ 2. Add document to database
+
+Add new install tree
+--------------------
+
+ 1. Specify path and classification information
+ 2. Create XML document
+ 3. Add document to database
+
+Install tree on machine
+-----------------------
+
+ 1. Specify machine and distro identifier
+ 2. Change vmlinuz and initrd.img symlinks to point to new distro
+ 3. Find kickstart block to use for machine
+ 4. Generate kickstart file
+ 5. Force machine to PXE boot
+
+Remove install tree
+-------------------
+
+Remove machine
+--------------
+
+Data Files
+==========
+
+There is only one data file that Steel Toe cares about,
+/etc/steeltoe/steeltoe.xml. This file includes global config options,
+host config options, and install tree metadata. If the file gets to
+large to manage you can take advantage of any tools available to you to
+make it more manageable. You can use anything from XIncludes to
+generating the file with a shell script. You just need to make sure the
+file is while when you run the Steel Toe commands.
+
+ Note: I ran some simple tests with a "large" config file that included
+ 500 install trees and 200 clients. Running xsltproc on such a
+ config file still took less than 30ms.
+
+
+Caveats
+=======
+
+The root disk is stored in the kickstart section, but Steel Toe only
+supports one name for the root disk. If the device name changes, i.e.
+from hda to sda, between different install trees, Steel Toe will not
+handle it.
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..14135dd
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,38 @@
+
+PYTHON_SITELIB := $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")
+
+TARGETS := steeltoe
+
+all: $(TARGETS) build-python
+
+build-python: setup.py
+ python setup.py build
+
+XSL := $(wildcard *.xsl)
+
+install: all
+ install -D steeltoe $(DESTDIR)/usr/bin/steeltoe
+ install -D tftp.conf $(DESTDIR)/etc/httpd/conf.d/tftp.conf
+ install -D steeltoe.xml $(DESTDIR)/etc/steeltoe/steeltoe.xml
+ for i in $(XSL); do install -D $$i $(DESTDIR)/etc/steeltoe/$$i; done
+ install -d $(DESTDIR)$(PYTHON_SITELIB)/st_web/
+ python setup.py install --skip-build --root $(DESTDIR)
+ python setup.py install -O1 --skip-build --root $(DESTDIR)
+ cp -R st_web/templates $(DESTDIR)$(PYTHON_SITELIB)/st_web/
+
+clean:
+ $(RM) $(TARGETS)
+
+
+VERSION := $(shell awk '/^Version:/ { print $$2 }' steeltoe.spec)
+steeltoe-$(VERSION).tar.bz2: steeltoe.spec Makefile
+ -$(RM) $@
+ tar cjf $@ --exclude=.svn --exclude=*.pyc .
+
+tarball: steeltoe-$(VERSION).tar.bz2
+
+rpm: steeltoe-$(VERSION).tar.bz2 steeltoe.spec
+ rpmbuild --target=noarch -ta $<
+
+srpm: steeltoe-$(VERSION).tar.bz2 steeltoe.spec
+ rpmbuild -ts $<
diff --git a/README b/README
new file mode 100644
index 0000000..412ae43
--- /dev/null
+++ b/README
@@ -0,0 +1,390 @@
+Steel Toe - Taking the pain out of kickstart
+
+QUICKSTART
+
+ 1. Install steeltoe rpm or "make install"
+ 2. Modify /etc/steeltoe/steeltoe.xml for your environment
+ 3. steeltoe gendhcpd -o /etc/dhcpd.steeltoe.conf
+ 4. Modify /etc/dhcpd.conf to include /etc/dhcpd.steeltoe.conf
+ 5. Configure hosts to boot from hard disk before ethernet
+ 6. steeltoe install <tree> <host>
+
+What does Steel Toe do?
+
+ Steel Toe makes it easier to do kickstart installs on lots of hosts.
+ It generates the config files needed to PXE install systems using
+ kickstart.
+
+What does Steel Toe not do?
+
+ Steel Toe does not provide a way to retrieve the images that Steel Toe
+ installs. Mirroring distribution trees is a separate step that Steel Toe
+ does not try to solve.
+
+How does Steel Toe do it all?
+
+ Steel Toe makes extensive use of XML and related technologies. All
+ data is stored in one flat XML file that can be spread out into
+ multiple files using XInclude. Data is pulled from the file using
+ XPath expression. Config files are generated using XSLT and XPath.
+ Everything is tied together with a bash script.
+
+ Steel Toe uses a unique TFTP directory organization method to keep
+ things simple. Each host has its own directory under the TFTP root
+ directory to hold its bootloader, bootloader config, kickstart config,
+ kernel, and initial ramdisk.
+
+ Steel Toe works on the principle that systems should not have to check
+ in with Steel Toe when they are done an install. The systems should
+ be configured to boot from the hard disk _before_ the network. This
+ means that when a system successfully installs it does not have to
+ check in with Steel Toe to disable PXE booting. It does mean that if
+ an install fails, manual intervention may be required to get the
+ system to retry the install.
+
+ Steel Toe was not meant to be a finished solution. It is supposed to
+ be a starting point for automating kickstart installs. The config
+ files and templates are meant to be customized for each site. That is
+ why they are installed in /etc/steeltoe rather than
+ /usr/share/steeltoe.
+
+Steel Toe Configuration - /etc/steeltoe/steeltoe.xml
+
+ Steel Toe uses one XML document for all configuration and data. This
+ includes all trees that can be installed and all hosts that can be
+ installed to. The document's root element should be <steeltoe>.
+
+ Global Configuration
+
+ The global configuration is contained in a <config> element which
+ should be a child of the <steeltoe> element. The following tags are
+ currently used in Steel Toe. You can define additional tags as
+ needed to hold your configuration data. To access these tags in a
+ XML stylesheet use an XPath expression like "/steeltoe/config/foo."
+
+ <tftpserver>
+
+ This is the hostname of the TFTP server.
+
+ <tftproot>
+
+ This is the directory where all Steel Toe generated config files
+ will go so hosts can access them through TFTP.
+
+ <yabootetc>
+
+ Yaboot insists that config files be put in /etc on the TFTP server.
+ Set this value so Steel Toe can find this directory and place
+ symlinks from here to the host's yaboot.conf in its directory.
+
+ <tftpbase>
+
+ This is the base directory that hosts will use to access files
+ through the TFTP server.
+
+ <kickstartbase>
+
+ This is the base URL which hosts will use to find their kickstart
+ file. Steel Toe saves the generated kickstart file to the TFTP
+ directory for each host, so this URL should point to the same
+ directory as <tftpbase>, but using a method allowed in the ks=
+ kernel argument.
+
+ <remoteshell>
+
+ This is the remote shell command to use when Steel Toe wants to run
+ commands on the host. This is used for configuring the boot loader or
+ rebooting the host.
+
+ <remotecopy>
+
+ This is the remote copy command to use when Steel Toe wants to copy
+ files to the host. This is used for copying kernels and ramdisks to
+ the host for auto-kickstart installing.
+
+ <treeserver>
+
+ This is the host name of the server that holds all trees. It is
+ used to generate the HTTP, FTP, or NFS URLs needed to install the
+ trees.
+
+ <bootloader>
+
+ This is a list of boot loaders and their full path. This is used by
+ Steel Toe to link the bootloader for each host into its respective
+ TFTP directory. Example:
+
+ <bootloader>
+ <pxelinux.0>/tftpboot/pxelinux/pxelinux.0</pxelinux.0>
+ <elilo.efi>/tftpboot/efi/elilo.efi</elilo.efi>
+ </bootloader>
+
+ Trees
+
+ Trees are installable distribution trees. They correspond to the
+ "os" directory in a Red Hat Enterprise Linux build. Each tree has a
+ name, a path element, and a meta section. The current templates
+ expect that <path> should work for all access methods (local, NFS,
+ HTTP, FTP, etc). The names do not have to be unique. meta/arch is
+ used a lot to match hosts with the correct trees.
+
+ Use one entry per path, so each arch and variant of a release will
+ have its own entry.
+
+ <tree name="RHEL4-U4">
+ <path>/dist/released/RHEL-4/U4/AS/i386/tree</path>
+ <meta>
+ <family>RHELU4</family><variant>AS</variant><arch>i386</arch><kernel>2.6.9-42.EL</kernel>
+ </meta>
+ </tree>
+
+ Hosts
+
+ Hosts are systems that can be installed with Steel Toe. Each host
+ has a name, a set of options, and a set of kickstart options.
+
+ <host name="dash-02">
+ <macaddr>00:12:3F:2A:AF:96</macaddr>
+ <fqdn>dash-02.lab.example.com</fqdn>
+ <arch>x86_64</arch>
+ <bootloader>pxelinux.0</bootloader>
+ <kickstart>
+ <ksparams>nostorage</ksparams>
+ <ksdevice>eth0</ksdevice>
+
+ <device type="scsi">ata_piix</device>
+ <device type="scsi">qla2xxx</device>
+ <rootdisk>sda</rootdisk>
+ </kickstart>
+ </host>
+
+ name="foo"
+
+ This is the short name for the host. It will be used to select the
+ host for installation.
+
+ <macaddr>
+
+ This is the MAC address of the ethernet card used for PXE booting.
+ Steel Toe needs this to generate a dhcpd.conf snippet for the host.
+
+ <fqdn>
+
+ This is the Fully Qualified Domain Name of the host.
+
+ <arch>
+
+ This is the architecture of the host. This is used to match against
+ the meta/arch element of the trees.
+
+ <bootloader>
+
+ The bootloader used to network boot the host. This should have a
+ corresponding element in /steeltoe/config/bootloader.
+
+ <kickstart>
+
+ This section defines values used in the kickstart file. Kickstart
+ sections can specify a family to match against. The select-ks.xsl
+ file determines which kickstart section is used to generate the
+ kickstart file.
+
+ Inside the <kickstart> section are several elements that are used to
+ modify how the kickstart file is generated and what kernel arguments
+ are used.
+
+ <ksparams>
+
+ The kickstart parameters to pass to the kernel via the boot
+ loader.
+
+ <ksdevice>
+
+ The ethernet device to use to fetch the kickstart file.
+
+ <device type="scsi">
+
+ The element corresponds directly to the device kickstart option.
+ It allows you to specify the order in which scsi drivers are
+ loaded.
+
+ <rootdisk>
+
+ The device to use as the root disk.
+
+Steel Toe Command Line - steeltoe
+
+ Steel Toe is held together with a single bash script called
+ "steeltoe." steeltoe provides subcommands which makes Steel Toe go.
+
+ gendhcpd [ -o outputfile ]
+
+ This command generates host declarations for all hosts defined in
+ steeltoe.xml. This can be included directly into an ISC DHCPD config
+ file. It does not generate subnet declarations for the hosts. Modify
+ dhcpd.conf to alter the output of this command.
+
+ install [-r][-m http|ftp|nfs] tree host [host ...]
+
+ Install the specified tree on the listed hosts. This is the primary
+ subcommand of steeltoe. It creates the TFTP directory for each host
+ and populates it with the bits required to install the specified tree.
+ The -r option tells Steel Toe to reboot the node. The -m option
+ allows the user to specify what installation method to try.
+
+ list [-v] <host|tree>
+
+ This is a simple subcommand to list all of the trees or hosts in
+ steeltoe.xml. It needs work.
+
+ genks [ -o outputfile ] host tree
+
+ This command generates a kickstart file for the specified host and
+ tree. Host and tree should be in steeltoe.xml. This command is used
+ internally by the install subcommand. Modify kickstart.xsl to alter
+ the output of this command.
+
+ genpxe [ -o outputfile ] host tree
+
+ This command generates a PXELINUX config to install the specified tree
+ on the specified host. This command is used internally by the install
+ subcommand. Modify pxeconf.xsl to alter the output of this command.
+
+ genelilo [ -o outputfile ] host tree
+
+ This command generates a ELILO config to install the specified tree on
+ the specified host. This command is used internally by the install
+ subcommand. Modify eliloconf.xsl to alter the output of this command.
+
+Steel Toe Helper Tools
+
+ Steel Toe relies on two commands to help with processing XML files.
+ These commands are part of the xpe package.
+
+ xpe - XPath Expression
+
+ This tool makes it trivial to extract information from an XML file.
+ It is used to extract configuration information in steeltoe.sh and is
+ useful for testing XPath expressions before they are added to .xsl
+ files. See built in help for usage.
+
+ xpm - XPath Merge
+
+ This tool makes it trivial to add or remove sections of XML documents.
+ It takes an XML document from standard input and attaches it to the
+ specified document in the location specified. This makes it easy for
+ a tree mirroring script to generate the <tree> section and add it to
+ steeltoe.xml.
+
+Steel Toe Template files
+
+ Steel Toe depends on XSL Transformations to function. These .xsl
+ files are meant to be customized to work for your environment. Here
+ is a guide to the .xsl files included in Steel Toe.
+
+ dhcpd.xsl
+
+ This is used by the gendhcpd subcommand to create a dhcpd.conf
+ snippet. It uses the tftpbase and tftpserver global config options
+ and the name, macaddr, and bootloader host config options.
+
+ select-ks.xsl
+
+ This file is used by other templates to select the appropriate
+ kickstart section from the host configuration. It expects the XSLT
+ variables $host and $dist to be set. It in turn defines a $ks
+ variable that references the correct kickstart section.
+
+ eliloconf.xsl
+
+ This is used by the genelilo subcommand to create a elilo.conf for
+ systems that use ELILO to boot. It makes use of two parameters which
+ should be passed on the command line, hostname and treename. It uses
+ the kickstartbase global config option.
+
+ pxeconf.xsl
+
+ This is used by the genpxe subcommand to create a PXELINUX config
+ file. It makes use of two parameters which should be passed on the
+ command line, hostname and treename. It uses the kickstartbase global
+ config option.
+
+ yaboot.xsl
+
+ This is used by the genyaboot subcommand to create a yaboot config
+ file.
+
+ autoks-args.xsl
+
+ This is used to generate the kickstart arguments used during auto-
+ kickstart installs. It takes advantage of select-ks.xsl to get the
+ parameters right for the selected tree.
+
+ kickstart.xsl
+
+ This is the template for making kickstart files. This will probably
+ be the file you customize the most.
+
+ list.xsl
+
+ This file is used to generate the list subcommand's output. Look away
+ before you go blind.
+
+
+Configuring hosts for use with Steel Toe
+
+ Steel Toe expects hosts to be configured in a certain way. Here are
+ settings to chanage on each host before using Steel Toe.
+
+ All Hosts
+
+ Steel Toe expects all hosts to boot from the hard disk before booting
+ from the network. The theory is that we only want to boot over the
+ network when we need to reinstall the host. Otherwise the host should
+ happily boot from the local hard disk and continue running the
+ installed environment.
+
+ x86 and x86-64 PXE Bootable Hosts
+
+ In the BIOS settings, set the hard disk to boot before the network
+ interfaces.
+
+ ia64 Hosts
+
+ Network booting is handled through EFI on ia64 systems. Create an EFI
+ boot entry with the label "Steeltoe" which boots from the network.
+
+ IBM pSeries hosts
+
+ In the SMS menus, configure the system to boot from the hard disk,
+ then the network.
+
+ Steel Toe requires yaboot 1.3.15 or later which includes the netboot
+ fixes. This allows Steel Toe to use yaboot config files instead of
+ the "boot-file" nvram variable to set the kernel parameters. You
+ should clear the boot-file variable.
+
+ nvsetenv boot-file ""
+
+How to set up steeltoe
+
+ 1. Install the steeltoe package
+
+software requirements
+ isc dhcpd
+
+integrating with isc dhcpd
+
+BIOS settings to use on nodes
+ HDD before ethernet
+
+
+Config files and stylesheets are meant to be customized
+
+Getting boot loaders (pxelinux.0 from syslinux, elilo.efi)
+
+
+Recommended Reading
+
+XML Path Language (XPath) - http://www.w3.org/TR/xpath
+XSL Transformations (XSLT) - http://www.w3.org/TR/xslt
diff --git a/autoks-args.xsl b/autoks-args.xsl
new file mode 100644
index 0000000..d9eaa36
--- /dev/null
+++ b/autoks-args.xsl
@@ -0,0 +1,12 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ version="1.0">
+ <xsl:param name="hostname"/>
+ <xsl:param name="treename"/>
+ <xsl:variable name="host" select="//host[@name=$hostname]"/>
+ <xsl:variable name="dist" select="//tree[@name=$treename and meta/arch=$host/arch]"/>
+ <xsl:include href="select-ks.xsl"/>
+ <xsl:output method="text" indent="no"/>
+ <xsl:template match="/" xml:space="preserve"><xsl:if test="$host/console">console=<xsl:value-of select="$host/console"/></xsl:if> ks=<xsl:value-of select="/steeltoe/config/kickstartbase"/>/<xsl:value-of select="$hostname"/>/ks.cfg ksdevice=<xsl:value-of select="$ks/ksdevice"/> <xsl:value-of select="$ks/ksparams"/>
+</xsl:template>
+</xsl:stylesheet>
diff --git a/dhcpd.xsl b/dhcpd.xsl
new file mode 100644
index 0000000..eea5183
--- /dev/null
+++ b/dhcpd.xsl
@@ -0,0 +1,17 @@
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="text" indent="no"/>
+ <xsl:template match="/">
+ <xsl:apply-templates select="//host" />
+ </xsl:template>
+ <xsl:template match="//host">
+host <xsl:value-of select="@name" xml:space="preserve"/> {
+ hardware ethernet <xsl:value-of select="macaddr"/>;
+ fixed-address <xsl:value-of select="@name"/>;
+ option host-name "<xsl:value-of select="@name"/>";
+ <xsl:if test="bootloader">
+ filename "<xsl:value-of select="/steeltoe/config/tftpbase"/>/<xsl:value-of select="@name"/>/<xsl:value-of select="bootloader"/>";
+ next-server <xsl:value-of select="/steeltoe/config/tftpserver"/>;
+ </xsl:if>
+}
+</xsl:template>
+</xsl:stylesheet>
diff --git a/eliloconf.xsl b/eliloconf.xsl
new file mode 100644
index 0000000..64b6b06
--- /dev/null
+++ b/eliloconf.xsl
@@ -0,0 +1,21 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ version="1.0">
+ <xsl:param name="hostname"/>
+ <xsl:param name="treename"/>
+ <xsl:variable name="host" select="//host[@name=$hostname]"/>
+ <xsl:variable name="dist" select="//tree[@name=$treename and meta/arch=$host/arch]"/>
+ <xsl:include href="select-ks.xsl"/>
+ <xsl:output method="text" indent="no"/>
+ <xsl:template match="/" xml:space="preserve">
+prompt
+timeout=50
+relocatable
+
+image=vmlinuz
+ label=linux
+ initrd=initrd.img
+ read-only
+ append="<xsl:if test="$host/console">console=<xsl:value-of select="$host/console"/></xsl:if> ks=<xsl:value-of select="/steeltoe/config/kickstartbase"/>/<xsl:value-of select="$hostname"/>/ks.cfg ksdevice=<xsl:value-of select="$ks/ksdevice"/> <xsl:value-of select="$ks/ksparams"/> "
+</xsl:template>
+</xsl:stylesheet>
diff --git a/kickstart.xsl b/kickstart.xsl
new file mode 100644
index 0000000..e0a752b
--- /dev/null
+++ b/kickstart.xsl
@@ -0,0 +1,84 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ version="1.0">
+ <xsl:param name="hostname"/>
+ <xsl:param name="treename"/>
+ <xsl:param name="method" select="'http'"/>
+ <xsl:param name="virtinstall"/>
+ <xsl:variable name="host" select="//host[@name=$hostname]"/>
+ <xsl:variable name="dist" select="//tree[@name=$treename and meta/arch=$host/arch]"/>
+ <xsl:include href="select-ks.xsl"/>
+ <xsl:output method="text"/>
+ <xsl:template match="/" xml:space="preserve">
+# Kickstart file generated by Steel Toe
+
+install
+<xsl:choose>
+<xsl:when test="$method='http'">url --url http://<xsl:value-of select="/steeltoe/config/treeserver"/><xsl:value-of select="$dist/path"/></xsl:when>
+<xsl:when test="$method='ftp'">url --url ftp://<xsl:value-of select="/steeltoe/config/treeserver"/><xsl:value-of select="$dist/path"/></xsl:when>
+<xsl:when test="$method='nfs'">nfs --server <xsl:value-of select="/steeltoe/config/treeserver"/> --dir <xsl:value-of select="$dist/path"/></xsl:when>
+</xsl:choose>
+lang en_US.UTF-8
+langsupport --default=en_US.UTF-8 en_US.UTF-8
+# custom devices here
+<xsl:for-each select="$ks/device[@type='scsi']">
+device scsi <xsl:value-of select="."/>
+</xsl:for-each>
+keyboard us
+mouse none
+cmdline
+skipx
+network --device <xsl:value-of select="$ks/ksdevice"/> --bootproto dhcp --hostname <xsl:value-of select="$hostname"/>
+rootpw root
+firewall --disabled
+<xsl:if test="$dist/meta/family != 'RHEL3'">
+selinux --disabled
+</xsl:if>
+authconfig --enableshadow --enablemd5
+timezone --utc US/Central
+bootloader --location=mbr
+reboot
+# The following is the partition information you requested
+# Note that any partitions you deleted are not expressed
+# here so unless you clear all partitions first, this is
+# not guaranteed to work
+clearpart --initlabel --drives=<xsl:value-of select="$ks/rootdisk"/> --all
+autopart
+
+%packages --resolvedeps
+@ system-tools
+@ text-internet
+@ compat-arch-support
+@ server-cfg
+@ development-tools
+
+%post
+#
+# Make Sure DNS works in chroot
+#
+%post --nochroot
+/bin/cp /etc/resolv.conf /mnt/sysimage/etc/resolv.conf
+
+%post
+
+<![CDATA[
+export PATH=/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/root/bin
+
+cat << __EOT__ > /etc/sysctl.conf
+# Enables the magic-sysrq key
+kernel.sysrq = 1
+__EOT__
+
+]]>
+yum mspqa http://<xsl:value-of select="/steeltoe/config/treeserver"/>/dist/qe
+yum <xsl:value-of select="$treename"/> http://<xsl:value-of select="/steeltoe/config/treeserver"/><xsl:value-of select="$dist/path"/>
+__EOT__
+
+echo "Disabling GPG signatures in up2date"
+sed -i -e 's/useGPG=1/useGPG=0/' /etc/sysconfig/rhn/up2date
+echo | up2date --configure
+
+up2date -i qarsh-server
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/list.xsl b/list.xsl
new file mode 100644
index 0000000..9b08012
--- /dev/null
+++ b/list.xsl
@@ -0,0 +1,36 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:str="http://exslt.org/strings"
+ version="1.0">
+ <xsl:param name="class"/>
+ <xsl:param name="verbose" select="0"/>
+ <xsl:output method="text"/>
+ <xsl:template match="/steeltoe">
+ <xsl:choose>
+ <xsl:when test="$class = 'host'">
+ <xsl:apply-templates select="//host" />
+ </xsl:when>
+ <xsl:when test="$class = 'group'">
+ <xsl:apply-templates select="//group" />
+ </xsl:when>
+ <xsl:when test="$class = 'tree'">
+ <xsl:apply-templates select="//tree" />
+ </xsl:when>
+ </xsl:choose>
+ </xsl:template>
+
+ <!-- INSTALLTREE TEMPLATE -->
+ <xsl:template match="tree" xml:space="preserve"><xsl:value-of select="str:align(@name, ' ', 'left')" /> <xsl:value-of select="path"/><xsl:if test="$verbose">
+ <xsl:apply-templates select="meta"/></xsl:if>
+</xsl:template>
+
+ <!-- METADATA TEMPLATE FOR INSTALLTREE -->
+ <xsl:template match="meta"><xsl:for-each select="*" xml:space="preserve"><xsl:value-of select="name()"/>: <xsl:value-of select="." /> </xsl:for-each></xsl:template>
+
+ <!-- HOST TEMPLATE -->
+ <xsl:template match="host" xml:space="preserve"><xsl:value-of select="str:align(@name, ' ', 'left')" /><xsl:value-of select="str:align(fqdn, ' ','left')"/><xsl:value-of select="str:align(arch,' ', 'right')"/>
+</xsl:template>
+
+ <!-- GROUP TEMPLATE -->
+ <xsl:template match="group" xml:space="preserve"><xsl:value-of select="str:align(@name, ' ', 'left')"/><xsl:value-of select="."/>
+</xsl:template>
+</xsl:stylesheet>
diff --git a/pxeconf.xsl b/pxeconf.xsl
new file mode 100644
index 0000000..8de97b1
--- /dev/null
+++ b/pxeconf.xsl
@@ -0,0 +1,19 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ version="1.0">
+ <xsl:param name="hostname"/>
+ <xsl:param name="treename"/>
+ <xsl:variable name="host" select="//host[@name=$hostname]"/>
+ <xsl:variable name="dist" select="//tree[@name=$treename and meta/arch=$host/arch]"/>
+ <xsl:include href="select-ks.xsl"/>
+ <xsl:output method="text" indent="no"/>
+ <xsl:template match="/" xml:space="preserve">
+default 0
+timeout 100
+prompt 1
+
+label 0
+ kernel vmlinuz
+ append initrd=initrd.img <xsl:if test="$host/console">console=<xsl:value-of select="$host/console"/></xsl:if> ks=<xsl:value-of select="/steeltoe/config/kickstartbase"/>/<xsl:value-of select="$hostname"/>/ks.cfg ksdevice=<xsl:value-of select="$ks/ksdevice"/> <xsl:value-of select="$ks/ksparams"/>
+</xsl:template>
+</xsl:stylesheet>
diff --git a/select-ks.xsl b/select-ks.xsl
new file mode 100644
index 0000000..7f321c9
--- /dev/null
+++ b/select-ks.xsl
@@ -0,0 +1,11 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ version="1.0">
+ <xsl:variable name="ks-rft">
+ <xsl:choose>
+ <xsl:when test="$host/kickstart/@family = $dist/meta/family"><xsl:copy-of select="//host[@name=$hostname]/kickstart[@family=$dist/meta/family]"/></xsl:when>
+ <xsl:otherwise><xsl:copy-of select="//host[@name=$hostname]/kickstart[count(@*)=0]"/></xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+ <xsl:variable name="ks" select="exsl:node-set($ks-rft)/kickstart"/>
+</xsl:stylesheet>
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..d035e54
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,9 @@
+from distutils.core import setup
+
+setup(name='steeltoe',
+ version="0.9.1",
+ description="Steel Toe - Kickstart automation tool",
+ author="Nate Straz",
+ author_email="nstraz@redhat.com",
+ packages=["st_web", "st_web.templatetags"],
+ )
diff --git a/st_web/README b/st_web/README
new file mode 100644
index 0000000..940ee42
--- /dev/null
+++ b/st_web/README
@@ -0,0 +1,34 @@
+Steel Toe Web Interface
+
+Requirements:
+
+This is a Django application which was developed using Django 0.96.
+You will still need to generate a project which includes this
+application. Make the following changes to your project.
+
+In settings.py:
+
+INSTALLED_APPS = (
+ ...
+ 'st_web',
+)
+
+In urls.py:
+urlpatterns += patterns('',
+ (r'^steeltoe/', include('st_web.urls')),
+)
+
+
+It depends on the media context processor which was not part of
+Django 0.96, but is available in development versions of Django. A
+version of it is included in this application and should be added to the
+template context processors by including this snippet in your
+settings.py:
+
+from django.conf.global_settings import TEMPLATE_CONTEXT_PROCESSORS
+TEMPLATE_CONTEXT_PROCESSORS += (
+ 'st_web.context_processors.media',
+)
+
+The stylesheet is included in the templates directory, you'll need to
+copy it to settings.MEDIA_ROOT/st_web/st_web.css.
diff --git a/st_web/__init__.py b/st_web/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/st_web/__init__.py
diff --git a/st_web/context_processors.py b/st_web/context_processors.py
new file mode 100644
index 0000000..482af15
--- /dev/null
+++ b/st_web/context_processors.py
@@ -0,0 +1,4 @@
+from django.conf import settings
+
+def media(request):
+ return {'MEDIA_URL': settings.MEDIA_URL}
diff --git a/st_web/st_config.py b/st_web/st_config.py
new file mode 100644
index 0000000..7869e10
--- /dev/null
+++ b/st_web/st_config.py
@@ -0,0 +1,114 @@
+
+from xml.sax import make_parser, handler
+from sets import Set
+from os.path import getmtime
+
+class SteelToeXML(handler.ContentHandler):
+
+ def __init__(self, filename="/etc/steeltoe/steeltoe.xml"):
+ self.filename = filename
+ self.reload()
+
+ def reload(self):
+ self.mtimes = {self.filename: getmtime(self.filename)}
+ self.config = {'bootloader': {}}
+ self.hosts = []
+ self.hostnames = Set()
+ self.groups = []
+ self.groupnames = Set()
+ self.trees = []
+ self.treenames = Set()
+ self.meta = {} # tree metadata index
+ self._context = []
+ self._curname = ""
+ parser = make_parser()
+ parser.setContentHandler(self)
+ parser.parse(self.filename)
+
+ def refresh(self):
+ reload = False
+ for (fn, mtime) in self.mtimes.items():
+ if mtime != getmtime(fn):
+ reload = True
+ if reload:
+ self.reload()
+
+ def get_group(self, name):
+ return [x for x in self.groups if x['name'] == name][0]
+
+ def get_host(self, name):
+ return [x for x in self.hosts if x['name'] == name][0]
+
+ def characters(self, content):
+ self._text = self._text + content
+
+ def startElement(self, name, attrs):
+ if name in ['config', 'tree', 'host', 'bootloader', 'kickstart', 'meta']:
+ self._context.append(name)
+ if name in ['group', 'tree', 'host']:
+ self._curname = attrs['name']
+ if name == 'repo':
+ if not self._curtree.has_key(name):
+ self._curtree[name] = [attrs['name']]
+ else:
+ self._curtree[name].append(attrs['name'])
+ if name == 'host':
+ self._curhost = {'name': self._curname}
+ self.hostnames.add(self._curname)
+ if name == 'tree':
+ self._curtree = {'name': self._curname, 'meta': {}}
+ self.treenames.add(self._curname)
+ if name == 'group':
+ self.groupnames.add(self._curname)
+ # Hack to handle simple XIncludes
+ if name == 'xi:include':
+ href = attrs['href']
+ self.mtimes[href] = getmtime(href)
+ subp = make_parser()
+ subp.setContentHandler(self)
+ subp.parse(href)
+ self._text = ""
+
+ def _parent(self):
+ if len(self._context):
+ return self._context[-1]
+ else:
+ None
+
+ def endElement(self, name):
+ self._text = self._text.strip()
+ if self._parent() == name:
+ self._context.pop()
+ if self._parent() == 'config':
+ self.config[name] = self._text
+ elif self._parent() == 'bootloader':
+ self.config['bootloader'][name] = self._text
+ elif name == 'group':
+ self.groups.append({ 'name': self._curname,
+ 'members': self._text.split()})
+ self._curname = None
+ elif self._parent() == 'kickstart' or name == 'kickstart':
+ # Ignore the kickstart sections for now
+ pass
+ elif self._parent() == 'host':
+ self._curhost[name] = self._text
+ elif name == 'host':
+ self.hosts.append(self._curhost)
+ self._curname = None
+ elif self._parent() == 'meta':
+ self._curtree['meta'][name] = self._text
+ if not self.meta.has_key(name):
+ self.meta[name] = {self._text: Set([self._curname])}
+ elif not self.meta[name].has_key(self._text):
+ self.meta[name][self._text] = Set([self._curname])
+ else:
+ self.meta[name][self._text].add(self._curname)
+ elif self._parent() == 'tree':
+ if name == 'path':
+ self._curtree[name] = self._text
+ elif name == 'tree':
+ self.trees.append(self._curtree)
+ self._curname = None
+
+
+steeltoe = SteelToeXML()
diff --git a/st_web/templates/st_web.css b/st_web/templates/st_web.css
new file mode 100644
index 0000000..b04c130
--- /dev/null
+++ b/st_web/templates/st_web.css
@@ -0,0 +1,109 @@
+h1 {
+ text-align: center;
+}
+
+h2 {
+ font-size: large;
+ font-weight: normal;
+}
+.yui-navset {
+ margin: 0 0 1em 5%;
+ width: 90%;
+}
+
+.yui-navset .yui-nav li {
+ padding: 1px 0 0;
+ background: #800000;
+}
+
+.yui-navset .yui-nav .selected {
+}
+
+.yui-navset .yui-nav a {
+ padding: 0 1em;
+ color: #000;
+ text-decoration: none;
+}
+
+.yui-navset .yui-nav .selected a,
+.yui-navset .yui-nav .selected a:focus,
+.yui-navset .yui-nav .selected a:hover {
+ background: #C00;
+ color: #FFF;
+}
+
+.yui-navset .yui-nav a:hover,
+.yui-navset .yui-nav a:focus {
+ background: #cc0000;
+ color: #FFF;
+ outline: 0;
+}
+
+.yui-content {
+ height: 20em;
+ overflow: auto;
+ border-left: 2px solid #C00;
+ border-right: 2px solid #C00;
+ border-top: 5px solid #C00;
+ border-bottom: 5px solid #C00;
+ padding: 0.25em 0;
+ background: white;
+}
+.yui-content p {
+ margin: 1em 1em;
+}
+
+.yui-content table {
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.yui-content table td {
+ padding: 1px 1em;
+ border-left: 1px dotted;
+ border-right: 1px dotted;
+}
+.yui-content table tr.heading {
+ border-bottom: 2px solid #000;
+}
+
+.yui-content table tr.odd {
+ background: #FFF;
+}
+
+.yui-content table tr.even {
+ background: #FED;
+}
+
+.yui-content table td.input {
+ text-align: center;
+ width: 1em;
+}
+
+ul.errorlist {
+ margin: 0 0 1em 5%;
+ width: 75%;
+ background-color: #C00;
+ border: 2px solid #C00;
+}
+.errorlist li {
+ display: block;
+ background: #FDD;
+ padding: 0.25em 1em;
+}
+
+input {
+ background: #FED;
+ border: 1px solid #999;
+ color: black;
+}
+
+pre {
+ width: 75%;
+ margin: 0 0 1em 5%;
+ padding: 1em;
+ background: #FED;
+ border: 2px solid #C00;
+}
diff --git a/st_web/templates/st_web/confirm.html b/st_web/templates/st_web/confirm.html
new file mode 100644
index 0000000..a780a75
--- /dev/null
+++ b/st_web/templates/st_web/confirm.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+<title>Steel Toe Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+</head>
+<body>
+<h1>Steel Toe Install Confirmation</h1>
+<h2>Please verify that you want to install:</h2>
+<ul><li>{{ tree }}</li></ul>
+<h2>on the following hosts:</h2>
+<ul>
+{% for host in hosts %}
+<li>{{ host }}</li>
+{% endfor %}
+</ul>
+<form action="." method="post">
+{{ form.tree.as_hidden }}
+{{ form.hosts.as_hidden }}
+<input type="submit" name="install" value="Confirm"/>
+<input type="submit" name="install" value="Cancel"/>
+</form>
+</body>
+</html>
diff --git a/st_web/templates/st_web/index.html b/st_web/templates/st_web/index.html
new file mode 100644
index 0000000..cb2ef9f
--- /dev/null
+++ b/st_web/templates/st_web/index.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<title>Steel Toe</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+</head>
+<body>
+<h1>Steel Toe</h1>
+<h2>Available Actions</h2>
+<ul>
+<li><a href="{% url st_web.views.install %}">Install</a></li>
+<li><a href="{% url st_web.views.virtinstall %}">Virtual Machine Install</a></li>
+</ul>
+</body>
+</html>
diff --git a/st_web/templates/st_web/install.html b/st_web/templates/st_web/install.html
new file mode 100644
index 0000000..e5870b0
--- /dev/null
+++ b/st_web/templates/st_web/install.html
@@ -0,0 +1,129 @@
+{% load ifin %}
+<html>
+<head>
+<title>Steel Toe Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}yui/tabview/assets/skins/sam/tabview.css" />
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/yahoo-dom-event/yahoo-dom-event.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/element/element-beta-min.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/tabview/tabview-min.js"></script>
+</head>
+<body>
+<h1>Steel Toe Install</h1>
+<form action="{% url st_web.views.install %}" method="post">
+
+{% if form.errors %}
+{{ form.non_field_errors }}
+{% endif %}
+
+<h2>Select Tree to install</h2>
+{{ form.tree.errors }}
+<div id="treeselect" class="yui-navset">
+ <ul class="yui-nav">
+ <li class="selected"><a href="#tall">All</a></li>
+ <li><a href="#trecent">Recently Used</a></li>
+ <li><a href="#tinstalled">Installed</a></li>
+ </ul>
+ <div class="yui-content">
+ <div id="tall">
+ {% regroup steeltoe.trees|dictsort:"name" by name as trees %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Tree Name</th><th>Kernel</th><th>Links</th></tr>
+ {% for tree in trees %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="radio" name="tree" value="{{ tree.grouper }}" {% ifequal tree.grouper form.tree.data %}checked="checked"{% endifequal %}></td>
+ <td>{{ tree.grouper }}</td>
+ <td>{{ tree.list.0.meta.kernel }}</td>
+ <td>{% for limb in tree.list %}
+ <a href="http://{{ steeltoe.config.treeserver }}{{ limb.path }}">{{ limb.meta.arch }}</a>
+ {% endfor %}
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+
+ </div>
+ <div id="trecent">
+ {% if request.session.trees %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Tree Name</th><th>Times installed</th></tr>
+ {% for tree in request.session.trees %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="radio" name="tree" value="{{ tree.name }}" {% ifequal tree.name form.tree.data %}checked="checked"{% endifequal %}></td>
+ <td>{{ tree.name }}</td>
+ <td>{{ tree.count }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <p>A list of recently installed trees</p>
+ {% endif %}
+ </div>
+ <div id="tinstalled">
+ <p>A list of all trees installed and where</p>
+ </div>
+ </div>
+</div>
+
+<h2>Select Hosts on which to install</h2>
+{{ form.hosts.errors }}
+<div id="hostselect">
+ <ul class="yui-nav">
+ <li class="selected"><a href="#hall">All</a></li>
+ <li><a href="#hgroups">Groups</a></li>
+ <li><a href="#hrecent">Your Systems</a></li>
+ </ul>
+ <div class="yui-content">
+ <div id="hall">
+ <table>
+ <tr class="heading"><th>Select</th><th>Host name</th><th>Arch</th></tr>
+ {% for host in steeltoe.hosts|dictsort:"name" %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="checkbox" name="hosts" value="{{ host.name }}" {% ifin host.name form.hosts.data %}checked="checked"{% endifin %}></td>
+ <td>{{ host.name }}</td>
+ <td>{{ host.arch }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ <div id="hgroups">
+ <table>
+ <tr class="heading"><th>Select</th><th>Group name</th><th>Group Members</th></tr>
+ {% for group in steeltoe.groups|dictsort:"name" %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="checkbox" name="hosts" value="{{ group.name }}" {% ifin group.name form.hosts.data %}checked="checked"{% endifin %}></td>
+ <td>{{ group.name }}</td>
+ <td>{% for m in group.members %} {{ m }} {% endfor %}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ <div id="hrecent">
+ {% if request.session.hosts %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Host Name</th><th>Times installed</th></tr>
+ {% for host in request.session.hosts %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="checkbox" name="hosts" value="{{ host.name }}" {% ifin host.name form.hosts.data %}checked="checked"{% endifin %}></td>
+ <td>{{ host.name }}</td>
+ <td>{{ host.count }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <p>A list of hosts which you have installed on</p>
+ {% endif %}
+ </div>
+ </div>
+</div>
+
+<script>
+var tv = new YAHOO.widget.TabView('treeselect');
+var hv = new YAHOO.widget.TabView('hostselect');
+</script>
+
+<input type="reset" value="Clear Selections">
+<input type="submit" name="install" value="Install">
+</form>
+</body>
+</html>
diff --git a/st_web/templates/st_web/perform.html b/st_web/templates/st_web/perform.html
new file mode 100644
index 0000000..2d1b195
--- /dev/null
+++ b/st_web/templates/st_web/perform.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>Steel Toe Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+</head>
+<body>
+<h1>Steel Toe Install Output</h1>
+<h2>Output from Steel Toe install command.</h2>
+<pre>
+{{ output }}
+</pre>
+
+<form action="." method="get"><input type="submit" value="Do Another Install"/></form>
+</body>
+</html>
diff --git a/st_web/templates/st_web/virtconfirm.html b/st_web/templates/st_web/virtconfirm.html
new file mode 100644
index 0000000..c03dedc
--- /dev/null
+++ b/st_web/templates/st_web/virtconfirm.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+<title>Steel Toe Virtual Machine Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+</head>
+<body>
+<h1>Steel Toe Virtual Machine Install Confirmation</h1>
+
+<h2>Please verify the following operation:</h2>
+<p>You want to install <b>{{ tree }}</b>, using <b>{{ dom0 }}</b> as the installation host, on:</p>
+
+<ul>
+{% for host in hosts %}
+<li>{{ host.name }} with root device /dev/{{ host.vg }}/{{ host.lv }} ({{ host.size }}),</li>
+{% endfor %}
+</ul>
+{% if shared %}
+<p>and shared device{{ shared|length|pluralize }}</p>
+<ul>
+{% for v in shared %}
+<li>/dev/{{ v.vg }}/{{ v.lv }} ({{ v.size }})</li>
+{% endfor %}
+</ul>
+{% else %}
+<p>and no shared devices.</p>
+{% endif %}
+<form action="." method="post">
+{% for f in hostform %}
+{{ f.as_hidden }}
+{% endfor %}
+{% for f in volform %}
+{{ f.as_hidden }}
+{% endfor %}
+<input type="hidden" name="step" value="confirm"/>
+<input type="submit" name="install" value="Confirm"/>
+<input type="submit" name="install" value="Cancel"/>
+</form>
+</body>
+</html>
diff --git a/st_web/templates/st_web/virthosts.html b/st_web/templates/st_web/virthosts.html
new file mode 100644
index 0000000..27f844c
--- /dev/null
+++ b/st_web/templates/st_web/virthosts.html
@@ -0,0 +1,170 @@
+{% load ifin %}
+<html>
+<head>
+<title>Steel Toe Virtual Machine Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}yui/tabview/assets/skins/sam/tabview.css" />
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/yahoo-dom-event/yahoo-dom-event.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/element/element-beta-min.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/tabview/tabview-min.js"></script>
+</head>
+<body>
+<h1>Steel Toe Virtual Machine Install</h1>
+<form action="{% url st_web.views.virtinstall %}" method="post">
+
+{% if hostform.errors %}
+{{ hostform.non_field_errors }}
+{% endif %}
+
+<h2>Select Tree to install</h2>
+{{ hostform.tree.errors }}
+<div id="treeselect" class="yui-navset">
+ <ul class="yui-nav">
+ <li class="selected"><a href="#tall">All</a></li>
+ <li><a href="#trecent">Recently Used</a></li>
+ <li><a href="#tinstalled">Installed</a></li>
+ </ul>
+ <div class="yui-content">
+ <div id="tall">
+ {% regroup steeltoe.trees|dictsort:"name" by name as trees %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Tree Name</th><th>Kernel</th><th>Links</th></tr>
+ {% for tree in trees %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="radio" name="tree" value="{{ tree.grouper }}" {% ifequal tree.grouper hostform.tree.data %}checked="checked"{% endifequal %}></td>
+ <td>{{ tree.grouper }}</td>
+ <td>{{ tree.list.0.meta.kernel }}</td>
+ <td>{% for limb in tree.list %}
+ <a href="http://{{ steeltoe.config.treeserver }}{{ limb.path }}">{{ limb.meta.arch }}</a>
+ {% endfor %}
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+
+ </div>
+ <div id="trecent">
+ {% if request.session.trees %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Tree Name</th><th>Times installed</th></tr>
+ {% for tree in request.session.trees %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="radio" name="tree" value="{{ tree.name }}" {% ifequal tree.name hostform.tree.data %}checked="checked"{% endifequal %}></td>
+ <td>{{ tree.name }}</td>
+ <td>{{ tree.count }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <p>A list of recently installed trees</p>
+ {% endif %}
+ </div>
+ <div id="tinstalled">
+ <p>A list of all trees installed and where</p>
+ </div>
+ </div>
+</div>
+
+<h2>Select a Physical Host on which to install</h2>
+{{ hostform.dom0.errors }}
+<div id="dom0select">
+ <ul class="yui-nav">
+ <li class="selected"><a href="#hall">All</a></li>
+ <li><a href="#hrecent">Your Systems</a></li>
+ </ul>
+ <div class="yui-content">
+ <div id="hall">
+ <table>
+ <tr class="heading"><th>Select</th><th>Host name</th><th>Arch</th></tr>
+ {% for host in steeltoe.hosts|dictsort:"name" %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="radio" name="dom0" value="{{ host.name }}" {% ifin host.name hostform.dom0.data %}checked="checked"{% endifin %}></td>
+ <td>{{ host.name }}</td>
+ <td>{{ host.arch }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ <div id="hrecent">
+ {% if request.session.hosts %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Host Name</th><th>Times installed</th></tr>
+ {% for host in request.session.hosts %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="radio" name="dom0" value="{{ host.name }}" {% ifin host.name hostform.dom0.data %}checked="checked"{% endifin %}></td>
+ <td>{{ host.name }}</td>
+ <td>{{ host.count }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <p>A list of hosts which you have installed on</p>
+ {% endif %}
+ </div>
+ </div>
+</div>
+
+<h2>Select Virtual Hosts on which to install</h2>
+{{ hostform.hosts.errors }}
+<div id="hostselect">
+ <ul class="yui-nav">
+ <li class="selected"><a href="#hall">All</a></li>
+ <li><a href="#hgroups">Groups</a></li>
+ <li><a href="#hrecent">Your Systems</a></li>
+ </ul>
+ <div class="yui-content">
+ <div id="hall">
+ <table>
+ <tr class="heading"><th>Select</th><th>Host name</th><th>Arch</th></tr>
+ {% for host in steeltoe.hosts|dictsort:"name" %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="checkbox" name="hosts" value="{{ host.name }}" {% ifin host.name hostform.hosts.data %}checked="checked"{% endifin %}></td>
+ <td>{{ host.name }}</td>
+ <td>{{ host.arch }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ <div id="hgroups">
+ <table>
+ <tr class="heading"><th>Select</th><th>Group name</th><th>Group Members</th></tr>
+ {% for group in steeltoe.groups|dictsort:"name" %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="checkbox" name="hosts" value="{{ group.name }}" {% ifin group.name hostform.hosts.data %}checked="checked"{% endifin %}></td>
+ <td>{{ group.name }}</td>
+ <td>{% for m in group.members %} {{ m }} {% endfor %}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+ <div id="hrecent">
+ {% if request.session.hosts %}
+ <table>
+ <tr class="heading"><th>Select</th><th>Host Name</th><th>Times installed</th></tr>
+ {% for host in request.session.hosts %}
+ <tr class="{% cycle odd,even %}">
+ <td class="input"><input type="checkbox" name="hosts" value="{{ host.name }}" {% ifin host.name hostform.hosts.data %}checked="checked"{% endifin %}></td>
+ <td>{{ host.name }}</td>
+ <td>{{ host.count }}</td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <p>A list of hosts which you have installed on</p>
+ {% endif %}
+ </div>
+ </div>
+</div>
+
+<script>
+var tv = new YAHOO.widget.TabView('treeselect');
+var dv = new YAHOO.widget.TabView('dom0select');
+var hv = new YAHOO.widget.TabView('hostselect');
+</script>
+
+<input type="hidden" name="step" value="hosts"/>
+<input type="reset" value="Clear Selections">
+<input type="submit" name="install" value="Next">
+</form>
+</body>
+</html>
diff --git a/st_web/templates/st_web/virtperform.html b/st_web/templates/st_web/virtperform.html
new file mode 100644
index 0000000..5de5d60
--- /dev/null
+++ b/st_web/templates/st_web/virtperform.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>Steel Toe Virtual Machine Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+</head>
+<body>
+<h1>Steel Toe Virtual Machine Install Output</h1>
+<h2>Output from Steel Toe virtinstall command.</h2>
+<pre>
+{{ output }}
+</pre>
+
+<form action="." method="get"><input type="submit" value="Do Another Install"/></form>
+</body>
+</html>
diff --git a/st_web/templates/st_web/virtvolumes.html b/st_web/templates/st_web/virtvolumes.html
new file mode 100644
index 0000000..3bf3571
--- /dev/null
+++ b/st_web/templates/st_web/virtvolumes.html
@@ -0,0 +1,65 @@
+{% load ifin %}
+<html>
+<head>
+<title>Steel Toe Install</title>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}st_web/st_web.css"/>
+<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}yui/tabview/assets/skins/sam/tabview.css" />
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/yahoo-dom-event/yahoo-dom-event.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/element/element-beta-min.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}yui/tabview/tabview-min.js"></script>
+</head>
+<body>
+<h1>Steel Toe Virtual Machine Install</h1>
+<form action="{% url st_web.views.virtinstall %}" method="post">
+
+{% if volform.fields %}
+
+<h2>Match volumes with virtual machines</h2>
+
+{% if volform.errors %}
+{{ volform.non_field_errors }}
+{% endif %}
+
+<div class="yui-navset">
+<div class="yui-content">
+<table>
+<tr class="heading">
+<th>Logical Volume</th><th>Volume Group</th><th>Size</th>
+{% for choice in volform.fields.values.0.choices %}<th>{{ choice.1 }}</th>{% endfor %}
+</tr>
+{% for bf in volform %}
+<tr class="{% cycle odd,even %}"><td>{{ bf.field.lv }}</td><td>{{ bf.field.vg }}</td><td>{{ bf.field.size }}</td>
+ {{ bf }}
+{% if bf.errors %}<td>{{ bf.errors }}</td>{% endif %}
+</tr>
+{% endfor %}
+</table>
+</div>
+</div>
+
+{% for f in hostform %}
+{{ f.as_hidden }}
+{% endfor %}
+
+<input type="hidden" name="step" value="vols"/>
+<input type="submit" name="install" value="Cancel">
+<input type="reset" value="Clear Selections">
+<input type="submit" name="install" value="Install">
+{% else %}
+<h2>No volumes found</h2>
+There are many reasons we couldn't find volumes to use.
+<ul>
+<li>The remote shell could not connect to the dom0.</li>
+<li>All logical volumes are currently in use.</li>
+</ul>
+{% for f in hostform %}
+{{ f.as_hidden }}
+{% endfor %}
+
+<input type="hidden" name="step" value="vols"/>
+<input type="submit" name="install" value="Cancel">
+
+{% endif %}
+</form>
+</body>
+</html>
diff --git a/st_web/templatetags/__init__.py b/st_web/templatetags/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/st_web/templatetags/__init__.py
diff --git a/st_web/templatetags/ifin.py b/st_web/templatetags/ifin.py
new file mode 100644
index 0000000..d514985
--- /dev/null
+++ b/st_web/templatetags/ifin.py
@@ -0,0 +1,72 @@
+from django.template import resolve_variable
+from django.template import Node, NodeList
+from django.template import VariableDoesNotExist
+from django.template import Library
+
+register = Library()
+
+class IfInNode(Node):
+ def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
+ self.var1, self.var2 = var1, var2
+ self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+ self.negate = negate
+
+ def __repr__(self):
+ return "<IfInNode>"
+
+ def render(self, context):
+ try:
+ val1 = resolve_variable(self.var1, context)
+ except VariableDoesNotExist:
+ val1 = None
+ try:
+ val2 = resolve_variable(self.var2, context)
+ except VariableDoesNotExist:
+ val2 = []
+ try:
+ if (self.negate and not val1 in val2) or (not self.negate and val1 in val2):
+ return self.nodelist_true.render(context)
+ except TypeError:
+ return ""
+ return self.nodelist_false.render(context)
+
+def do_ifin(parser, token, negate):
+ bits = list(token.split_contents())
+ if len(bits) != 3:
+ raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
+ end_tag = 'end' + bits[0]
+ nodelist_true = parser.parse(('else', end_tag))
+ token = parser.next_token()
+ if token.contents == 'else':
+ nodelist_false = parser.parse((end_tag,))
+ parser.delete_first_token()
+ else:
+ nodelist_false = NodeList()
+ return IfInNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
+
+#@register.tag
+def ifin(parser, token):
+ """
+ Output the contents of the block if the first argument is in the second argument.
+
+ Examples::
+
+ {% ifin user.id comment.user_id %}
+ ...
+ {% endifin %}
+
+ {% ifnotin user.id comment.user_id %}
+ ...
+ {% else %}
+ ...
+ {% endifnotin %}
+ """
+ return do_ifin(parser, token, False)
+ifin = register.tag(ifin)
+
+#@register.tag
+def ifnotin(parser, token):
+ """Output the contents of the block if the first argument is not in the second argument. See ifin."""
+ return do_ifin(parser, token, True)
+
+ifnotin = register.tag(ifnotin)
diff --git a/st_web/urls.py b/st_web/urls.py
new file mode 100644
index 0000000..ee0152a
--- /dev/null
+++ b/st_web/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('',
+ # Example:
+ (r'^$', 'django.views.generic.simple.direct_to_template', {'template': 'st_web/index.html'}),
+ (r'^install/$', 'st_web.views.install'),
+ (r'^virtinstall/$', 'st_web.views.virtinstall'),
+
+ # Uncomment this for admin:
+# (r'^admin/', include('django.contrib.admin.urls')),
+)
diff --git a/st_web/views.py b/st_web/views.py
new file mode 100644
index 0000000..cfd3124
--- /dev/null
+++ b/st_web/views.py
@@ -0,0 +1,262 @@
+# Main views for the Steeltoe Web Interface.
+
+from sets import Set
+from commands import getoutput
+
+from django.http import *
+from django.template import RequestContext
+from django.shortcuts import render_to_response
+from django import newforms as forms
+from django.newforms.util import ErrorList
+from django.utils.datastructures import SortedDict
+
+from st_web.st_config import steeltoe
+from st_web.widgets import VolSelect
+
+def steeltoe_install_form():
+ "Generate a validatable form with choices from the steeltoe config"
+ def _get_trees():
+ return tuple([(x, x) for x in steeltoe.treenames])
+ def _get_hosts():
+ return tuple([(x, x) for x in steeltoe.hostnames | steeltoe.groupnames])
+
+ class SteeltoeInstallForm(forms.Form):
+ tree = forms.ChoiceField(choices = _get_trees(), widget=forms.RadioSelect)
+ hosts = forms.MultipleChoiceField(choices = _get_hosts(), widget=forms.CheckboxSelectMultiple)
+
+ def clean(self):
+ "Test that the arch of the hosts matches the archs of the tree"
+ errors = ErrorList()
+ t = self.clean_data.get('tree', None)
+ if t is None:
+ return self.clean_data
+ tree_archs = [x['meta']['arch'] for x in steeltoe.trees if x['name'] == t]
+ # Normalize the list into hostnames
+ hostlist = Set()
+ for host in self.clean_data.get('hosts', []):
+ if host in steeltoe.groupnames:
+ hostlist |= Set(steeltoe.get_group(host)['members'])
+ elif host in steeltoe.hostnames:
+ hostlist.add(host)
+ else:
+ errors.append("Host %s not valid" % host)
+ # Convert the list to host entries
+ hostlist = list(hostlist)
+ hostlist.sort()
+ self.clean_data['hosts'] = hostlist
+ hostlist = [steeltoe.get_host(x) for x in hostlist]
+ # Do the tests
+ for host in hostlist:
+ if not host['arch'] in tree_archs:
+ errors.append("Tree %s is not available for %s which host %s requires" % (t, host['arch'], host['name']))
+ if errors:
+ raise forms.ValidationError(errors)
+ return self.clean_data
+
+ return SteeltoeInstallForm
+
+def install(request):
+ if request.method == 'POST':
+ form = steeltoe_install_form()(request.POST)
+ if form.is_valid():
+ cmd = request.POST.get('install', 'Install')
+ if cmd == 'Install':
+ return render_to_response("st_web/confirm.html", dict(form.clean_data, form=form),
+ context_instance=RequestContext(request))
+ elif cmd == 'Confirm':
+ t = form.clean_data['tree']
+ h = ' '.join(form.clean_data['hosts'])
+ update_session(request.session, t, form.clean_data['hosts'])
+ output = getoutput("/usr/bin/steeltoe install -r %s %s" % (t, h))
+ return render_to_response("st_web/perform.html", {'output': output},
+ context_instance=RequestContext(request))
+ else:
+ steeltoe.refresh()
+ form = steeltoe_install_form()()
+ return render_to_response("st_web/install.html", { 'steeltoe': steeltoe , 'form': form },
+ context_instance=RequestContext(request))
+
+def steeltoe_virthost_form():
+ "Generate a validatable form with choices from the steeltoe config"
+ def _get_trees():
+ return tuple([(x, x) for x in steeltoe.treenames])
+ def _get_dom0s():
+ return tuple([(x, x) for x in steeltoe.hostnames])
+ def _get_hosts():
+ return tuple([(x, x) for x in steeltoe.hostnames | steeltoe.groupnames])
+
+ class SteeltoeVirtHostForm(forms.Form):
+ tree = forms.ChoiceField(choices = _get_trees(), widget=forms.RadioSelect)
+ dom0 = forms.ChoiceField(choices = _get_dom0s(), widget=forms.RadioSelect)
+ hosts = forms.MultipleChoiceField(choices = _get_hosts(), widget=forms.CheckboxSelectMultiple)
+
+ def clean(self):
+ "Test that the arch of the hosts matches the archs of the tree"
+ errors = ErrorList()
+ t = self.clean_data.get('tree', None)
+ if t is None:
+ return self.clean_data
+ tree_archs = [x['meta']['arch'] for x in steeltoe.trees if x['name'] == t]
+ d0 = self.clean_data.get('dom0', None)
+ if d0 is None:
+ return self.clean_data
+ # Normalize the list into hostnames
+ hostlist = Set()
+ for host in self.clean_data.get('hosts', []):
+ if host in steeltoe.groupnames:
+ hostlist |= Set(steeltoe.get_group(host)['members'])
+ elif host in steeltoe.hostnames:
+ hostlist.add(host)
+ else:
+ errors.append("Host %s not valid" % host)
+ # Convert the list to host entries
+ hostlist = list(hostlist)
+ hostlist.sort()
+ if d0 in hostlist:
+ errors.append("Can not install on %s from %s" % (d0, d0))
+ self.clean_data['hosts'] = hostlist
+ hostlist = [steeltoe.get_host(x) for x in hostlist]
+ # Do the tests
+ for host in hostlist:
+ if not host['arch'] in tree_archs:
+ errors.append("Tree %s is not available for %s which host %s requires" % (t, host['arch'], host['name']))
+ if errors:
+ raise forms.ValidationError(errors)
+ return self.clean_data
+
+ return SteeltoeVirtHostForm
+
+
+def steeltoe_virtvols_form(dom0, domUs):
+ "Generate a validatable form for the volumes which we're going to install the hosts to"
+ def _get_vols(host):
+ rsh = steeltoe.config.get("remoteshell", "/bin/false")
+ output = getoutput("%s -l root %s /usr/sbin/lvs --noheadings -o lv_name,vg_name,lv_size,lv_attr 2>/dev/null" % (rsh, host))
+ vols = []
+ for l in output.split('\n'):
+ if len(l.split()) != 4:
+ continue
+ d = dict(zip(('lv', 'vg', 'size', 'attr'), l.split()))
+ # skip over open volumes
+ if d['attr'][5] == 'o':
+ continue
+ vols.append(d)
+ return vols
+
+ def clean(form):
+ errors = ErrorList()
+ # Check that each host has one and only one root volume
+ hostroots = dict([(h, []) for h in form.hosts])
+ shared = []
+ for vol in form.fields:
+ host = form.clean_data[vol]
+ if host == 'none':
+ continue
+ elif host == 'shared':
+ shared.append(vol)
+ continue
+ elif not hostroots.has_key(host):
+ errors.append("Unexpected host " + host)
+ else:
+ hostroots[host].append(vol)
+ for host, roots in hostroots.items():
+ if len(roots) > 1:
+ errors.append("Too many roots assigned to %s" % host)
+ elif len(roots) == 0:
+ errors.append("Root not assigned to %s" % host)
+ form.clean_data['shared'] = [{'lv': form.fields[v].lv, 'vg': form.fields[v].vg, 'size': form.fields[v].size} for v in shared]
+ if errors:
+ raise forms.ValidationError(errors)
+ # Add an easily usable dict to look hosts' root vol
+ form.clean_data['hosts'] = [({'name': k, 'lv': form.fields[v[0]].lv, 'vg': form.fields[v[0]].vg, 'size': form.fields[v[0]].size}) for k, v in hostroots.items()]
+ return form.clean_data
+
+ vols = _get_vols(dom0)
+ choices = tuple([(h, 'Root for %s' % h) for h in domUs]) + (('shared', 'Shared Volume'), ('none', 'Not Used'))
+
+ base_fields = SortedDict()
+ for vol in vols:
+ f = forms.ChoiceField(choices, widget=VolSelect, initial='none')
+ for k, v in vol.items():
+ setattr(f, k, v)
+ base_fields["%(vg)s_%(lv)s" % vol] = f
+
+ return type('SteeltoeVirtVolForm', (forms.BaseForm,),
+ {'base_fields': base_fields,
+ 'hosts': domUs,
+ 'dom0': dom0,
+ 'vols': vols,
+ 'clean': clean,
+ })
+
+
+def virtinstall(request):
+ if request.method == 'POST':
+ # Any POST request should have a host form to validate.
+ hostform = steeltoe_virthost_form()(request.POST)
+ if hostform.is_valid():
+ step = request.POST.get('step', 'hosts')
+ cmd = request.POST.get('install', 'Next')
+ dom0 = hostform.clean_data['dom0']
+ hosts = hostform.clean_data['hosts']
+ if step == 'vols' and cmd == 'Cancel':
+ return render_to_response("st_web/virthosts.html",
+ { 'steeltoe': steeltoe , 'hostform': hostform },
+ context_instance=RequestContext(request))
+
+ if step in ('vols', 'confirm'):
+ volform = steeltoe_virtvols_form(dom0, hosts)(request.POST)
+ if volform.is_valid():
+ if step == 'vols' and cmd == 'Install':
+ data = dict(hostform.clean_data)
+ data.update(volform.clean_data)
+ data['hostform'] = hostform
+ data['volform'] = volform
+ return render_to_response("st_web/virtconfirm.html", data,
+ context_instance=RequestContext(request))
+ elif step == 'confirm' and cmd == 'Confirm':
+ t = hostform.clean_data['tree']
+ update_session(request.session, t, hostform.clean_data['hosts'])
+ output = ""
+ for h in volform.clean_data['hosts']:
+ cmdargs = ("/usr/bin/steeltoe", "virtinstall")
+ cmdargs += (t, h['name'], dom0)
+ cmdargs += "/dev/%(vg)s/%(lv)s" % h,
+ cmdargs += tuple(["/dev/%(vg)s/%(lv)s" % v for v in volform.clean_data['shared']])
+ output += getoutput(' '.join(cmdargs))
+ return render_to_response("st_web/virtperform.html", {'output': output},
+ context_instance=RequestContext(request))
+ # else: fall through to redisplay the volumes form
+ # else: fall through to redisplay the volumes form with errors
+ else:
+ volform = steeltoe_virtvols_form(dom0, hosts)()
+ # FIXME: What about back button for volumes -> hosts
+ return render_to_response("st_web/virtvolumes.html",
+ dict(hostform=hostform, volform=volform),
+ context_instance=RequestContext(request))
+ # else: host form is not valid, fall through to the first page
+ else:
+ steeltoe.refresh()
+ hostform = steeltoe_virthost_form()()
+ return render_to_response("st_web/virthosts.html", { 'steeltoe': steeltoe , 'hostform': hostform },
+ context_instance=RequestContext(request))
+
+
+def update_session(session, newtree, newhosts):
+ trees = session.get('trees', [])
+ if newtree in Set([x['name'] for x in trees]):
+ tree = [x for x in trees if x['name'] == newtree][0]
+ tree['count'] += 1
+ else:
+ trees.append({'name': newtree, 'count': 1})
+ trees.sort(lambda x, y: cmp(y['count'], x['count']) or cmp(x['name'], y['name']))
+ session['trees'] = [x for x in trees if x['name'] in steeltoe.treenames]
+ hosts = session.get('hosts', [])
+ for newhost in newhosts:
+ if newhost in Set([x['name'] for x in hosts]):
+ host = [x for x in hosts if x['name'] == newhost][0]
+ host['count'] += 1
+ else:
+ hosts.append({'name': newhost, 'count': 1})
+ hosts.sort(lambda x, y: cmp(y['count'], x['count']) or cmp(x['name'], y['name']))
+ session['hosts'] = [x for x in hosts if x['name'] in steeltoe.hostnames]
diff --git a/st_web/widgets.py b/st_web/widgets.py
new file mode 100644
index 0000000..c92424f
--- /dev/null
+++ b/st_web/widgets.py
@@ -0,0 +1,71 @@
+from django import newforms as forms
+from django.newforms.util import flatatt, StrAndUnicode, smart_unicode
+from itertools import chain
+
+__all__ = ('VolSelect')
+
+class RadioInput(StrAndUnicode):
+ "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
+ def __init__(self, name, value, attrs, choice, index):
+ self.name, self.value = name, value
+ self.attrs = attrs
+ self.choice_value = smart_unicode(choice[0])
+ self.choice_label = smart_unicode(choice[1])
+ self.index = index
+
+ def __unicode__(self):
+ return u'<label>%s %s</label>' % (self.tag(), self.choice_label)
+
+ def is_checked(self):
+ return self.value == self.choice_value
+
+ def tag(self):
+ if self.attrs.has_key('id'):
+ self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
+ final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
+ if self.is_checked():
+ final_attrs['checked'] = 'checked'
+ return u'<input%s />' % flatatt(final_attrs)
+
+class RadioFieldRenderer(StrAndUnicode):
+ "An object used by RadioSelect to enable customization of radio widgets."
+ def __init__(self, name, value, attrs, choices):
+ self.name, self.value, self.attrs = name, value, attrs
+ self.choices = choices
+
+ def __iter__(self):
+ for i, choice in enumerate(self.choices):
+ yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
+
+ def __getitem__(self, idx):
+ choice = self.choices[idx] # Let the IndexError propogate
+ return RadioInput(self.name, self.value, self.attrs.copy(), choice, idx)
+
+ def __unicode__(self):
+ "Outputs a <ul> for this set of radio fields."
+ return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])
+
+class VolInput(RadioInput):
+ "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
+ def __unicode__(self):
+ return u'<td class="input">%s</td>' % self.tag()
+
+class VolFieldRenderer(RadioFieldRenderer):
+ def __iter__(self):
+ for i, choice in enumerate(self.choices):
+ yield VolInput(self.name, self.value, self.attrs.copy(), choice, i)
+ def __getitem__(self, idx):
+ choice = self.choices[idx] # Let the IndexError propogate
+ return VolInput(self.name, self.value, self.attrs.copy(), choice, idx)
+ def __unicode__(self):
+ "Outputs the set of radio fields."
+ return u'\n'.join([u'%s' % w for w in self])
+
+
+class VolSelect(forms.RadioSelect):
+ def render(self, name, value, attrs=None, choices=()):
+ "Returns a VolFieldRenderer instance rather than a Unicode string."
+ if value is None: value = ''
+ str_value = smart_unicode(value)
+ attrs = attrs or {}
+ return VolFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices)))
diff --git a/steeltoe.sh b/steeltoe.sh
new file mode 100644
index 0000000..dcb8b20
--- /dev/null
+++ b/steeltoe.sh
@@ -0,0 +1,528 @@
+#!/bin/bash
+#
+# Steeltoe command processor
+
+fail()
+{
+ # Print error message on $1 to stderr
+ echo "$1" >&2
+ # then exit with error code $2 or 1
+ exit ${2:-1}
+}
+
+# require: make sure specified config options are set
+require()
+{
+ for vn in $*; do case $vn in
+ RSH) RSH=`xpe -f $STXML "/steeltoe/config/remoteshell"` || fail "Config option remoteshell not found";;
+ RCP) RCP=`xpe -f $STXML "/steeltoe/config/remotecopy"` || fail "Config option remotecopy not found";;
+ TFTPROOT) TFTPROOT=`xpe -f $STXML "/steeltoe/config/tftproot"` || fail "Config option tftproot not found"
+ [ -d "$TFTPROOT" -a -w "$TFTPROOT" ] || fail "Directory $TFTPROOT does not exist or is not writeable";;
+ TREESERVER) TREESERVER=`xpe -f $STXML "/steeltoe/config/treeserver"` || fail "Config option treeserver not found";;
+ KSBASE) KSBASE=`xpe -f $STXML "/steeltoe/config/kickstartbase"` || fail "Config option kickstartbase not found";;
+ YABOOTETC) YABOOTETC=`xpe -f $STXML "/steeltoe/config/yabootetc"` || fail "Config option yabootetc not found"
+ [ -d "$YABOOTETC" -a -w "$YABOOTETC" ] || fail "Directory $YABOOTETC does not exist or is not writeable";;
+ *) fail "Unknown config requirement '$vn'"
+ esac; done
+}
+
+expand_groups() {
+ for target in $*; do
+ grouplist=$(xpe -f $STXML "//group[@name='$target']")
+ if [ -z "$grouplist" ]; then
+ echo $target
+ else
+ for g in $grouplist; do echo $g; done
+ fi
+ done
+}
+
+# Set global config variables
+if [ -z "$STEELTOE_ETC" ]; then
+ STEELTOE_ETC="/etc/steeltoe"
+fi
+STXML="$STEELTOE_ETC/steeltoe.xml";
+if [ ! -f $STXML ]; then
+ fail "Steel Toe config file '$STXML' not found"
+fi
+
+XSLTOPTS="--xinclude"
+# Make the default install method http
+instmethod="http"
+
+check_instmethod()
+{
+ instmethods="http ftp nfs"
+ if ! echo "$instmethods" | grep -q $1; then
+ echo "Install method '$1' unknown. Supported methods: $instmethods" >&2
+ exit 1
+ fi
+}
+
+# Make the default kickstart template /etc/steeltoe/kickstart.xsl
+kstemplate="$STEELTOE_ETC/kickstart.xsl"
+
+check_kstemplate()
+{
+ if [ ! -f $1 ]; then
+ echo "Kickstart template '$1' does not exist" >&2
+ exit 1
+ fi
+}
+
+global_usage()
+{
+ cat >&2 << __EOT__
+Usage: $0 <cmd> <options>
+__EOT__
+ for i in $COMMANDS; do
+ ${i}_usage >&2
+ done
+}
+
+COMMANDS="$COMMANDS gendhcpd"
+gendhcpd_usage()
+{
+ cat << __EOT__
+ gendhcpd [-o outputfile ]
+ Generate ISC DHCPD configuration snippet
+__EOT__
+}
+
+gendhcpd_do()
+{
+ if [ -n "$outputfile" ]; then
+ XSLTOPTS="$XSLTOPTS -o $outputfile"
+ fi
+ xsltproc $XSLTOPTS $STEELTOE_ETC/dhcpd.xsl $STXML
+}
+
+COMMANDS="$COMMANDS genks"
+genks_usage()
+{
+ cat << __EOT__
+ genks [-k kickstart.xsl][-m http|ftp|nfs][-o outputfile ] host tree
+ Generate a Kickstart config file for host to install tree
+__EOT__
+}
+
+genks_do()
+{
+ if [ -z "$1" -o -z "$2" ]; then
+ genks_usage >&2
+ exit 1
+ fi
+ if [ -n "$outputfile" ]; then
+ XSLTOPTS="$XSLTOPTS -o $outputfile"
+ fi
+ if [ -n "$instmethod" ]; then
+ METHOD="--stringparam method $instmethod"
+ fi
+ xsltproc $XSLTOPTS $METHOD --stringparam hostname "$1" --stringparam treename "$2" $kstemplate $STXML
+}
+
+COMMANDS="$COMMANDS genpxe"
+genpxe_usage()
+{
+ cat << __EOT__
+ genpxe [-o outputfile ] host tree
+ Generate a PXE config file for host to install tree
+__EOT__
+}
+
+genpxe_do()
+{
+ if [ -z "$1" -o -z "$2" ]; then
+ genpxe_usage >&2
+ exit 1
+ fi
+ if [ -n "$outputfile" ]; then
+ XSLTOPTS="$XSLTOPTS -o $outputfile"
+ fi
+ xsltproc $XSLTOPTS --stringparam hostname "$1" --stringparam treename "$2" $STEELTOE_ETC/pxeconf.xsl $STXML
+}
+
+COMMANDS="$COMMANDS genelilo"
+genelilo_usage()
+{
+ cat << __EOT__
+ genelilo [-o outputfile ] host tree
+ Generate a ELILO config file for host to install tree
+__EOT__
+}
+
+genelilo_do()
+{
+ if [ -z "$1" -o -z "$2" ]; then
+ genelilo_usage >&2
+ exit 1
+ fi
+ if [ -n "$outputfile" ]; then
+ XSLTOPTS="$XSLTOPTS -o $outputfile"
+ fi
+ xsltproc $XSLTOPTS --stringparam hostname "$1" --stringparam treename "$2" $STEELTOE_ETC/eliloconf.xsl $STXML
+}
+
+COMMANDS="$COMMANDS genyaboot"
+genyaboot_usage()
+{
+ cat << __EOT__
+ genyaboot [-o outputfile ] host tree
+ Generate a yaboot config file for host to install tree
+__EOT__
+}
+
+genyaboot_do()
+{
+ if [ -z "$1" -o -z "$2" ]; then
+ genyaboot_usage >&2
+ exit 1
+ fi
+ if [ -n "$outputfile" ]; then
+ XSLTOPTS="$XSLTOPTS -o $outputfile"
+ fi
+ xsltproc $XSLTOPTS --stringparam hostname "$1" --stringparam treename "$2" $STEELTOE_ETC/yaboot.xsl $STXML
+}
+
+COMMANDS="install $COMMANDS"
+install_usage()
+{
+ cat << __EOT__
+ install [options] tree host|group [host|group ...]
+ Install tree on hosts
+ -a ARCH override the architecture of a host
+ -k FILE specify an alternate kickstart template to use.
+ -m meth install method (http, ftp, or nfs; default http)
+ -r reboot now
+__EOT__
+}
+
+install_do()
+{
+ if [ -z "$1" -o -z "$2" ]; then
+ install_usage >&2
+ exit 1
+ fi
+ require TFTPROOT RSH
+ treename="$1"
+ shift
+ hostlist=`expand_groups $*`
+
+ for host in $hostlist; do
+ echo "Configuring $host to install $treename"
+ tftp_do $host $treename
+ if ping -q -c 1 -w 3 $host > /dev/null && $RSH -l root $host /bin/true; then
+ autoks_do $host $treename
+ else
+ echo "$host needs to be manually PXE booted to install $treename"
+ fi
+ done
+}
+
+COMMANDS="$COMMANDS tftp"
+tftp_usage()
+{
+ cat << __EOT__
+ tftp [-m http|ftp|nfs][-a ARCH] host tree
+ Prepare host's TFTP directory for PXE installing tree.
+ -a ARCH override the architecture of a host
+ -m install method (default http)
+__EOT__
+}
+
+tftp_do()
+{
+ if [ -z "$1" -o -z "$2" ]; then
+ tftp_usage >&2
+ exit 1
+ fi
+ require TFTPROOT RSH
+ host="$1"
+ treename="$2"
+ hostdir="$TFTPROOT/$host"
+
+ # Try to lookup something under the host so we catch a non-existant host early.
+ hostarch=`xpe -f $STXML "//host[@name='$host']/arch"` || fail "Could not look up architecture of host '$host'"
+
+ # Check to see if we're overriding the arch on the command line
+ if [ -n "$forcearch" ]; then
+ hostarch=$forcearch
+ fi
+
+ # First lookup the tree information to make sure it exists.
+ treebase=`xpe -f $STXML "//tree[@name='$treename' and meta/arch='$hostarch']/path"` || fail "Could not find tree '$treename' for host '$host'"
+ # Next figure out which bootloader we need to configure
+ loadername=`xpe -f $STXML "//host[@name='$host']/bootloader"` || fail "Boot loader not specified for host '$host'"
+ # Now find the source for the bootloader link
+ loadersrc=`xpe -f $STXML "/steeltoe/config/bootloader/$loadername"` || fail "Boot loader source is not set for '$loadername'"
+ # Make sure the host's TFTP directory exists
+ [ -d "$hostdir" ] || mkdir -p $hostdir
+ # Link in the bootloader specified in the config file
+ [ -f "$hostdir/$loadername" ] || ln -f $loadersrc $hostdir/$loadername
+ # Generate the kickstart config
+ genks_do $host $treename > $hostdir/ks.cfg
+ # Handle bootloader specific configuration needs and link in the kernel and initrd
+ case $loadername in
+ pxelinux.0) genpxe_do $host $treename > $hostdir/default
+ [ -e "$hostdir/pxelinux.cfg" ] || ln -s . $hostdir/pxelinux.cfg
+ ln -sf $treebase/images/pxeboot/vmlinuz $hostdir/vmlinuz
+ ln -sf $treebase/images/pxeboot/initrd.img $hostdir/initrd.img
+ ;;
+ elilo.efi) genelilo_do $host $treename > $hostdir/elilo.conf
+ ln -sf $treebase/images/pxeboot/vmlinuz $hostdir/vmlinuz
+ ln -sf $treebase/images/pxeboot/initrd.img $hostdir/initrd.img
+ ;;
+ yaboot) genyaboot_do $host $treename > $hostdir/yaboot.conf
+ require YABOOTETC
+ etcfn="01-`xpe -f $STXML "//host[@name='$host']/macaddr" | tr [A-Z]: [a-z]-`"
+ ln -f $hostdir/yaboot.conf $YABOOTETC/$etcfn
+ # kernel and ramdisk locations differ between RHEL4 and RHEL5
+ for subdir in ppc/chrp ppc/ppc64; do if [ -f $treebase/$subdir/vmlinuz ]; then
+ ln -sf $treebase/$subdir/vmlinuz $hostdir/vmlinuz
+ ln -sf $treebase/$subdir/ramdisk.image.gz $hostdir/initrd.img
+ break
+ fi; done;;
+ esac
+}
+
+COMMANDS="$COMMANDS autoks"
+autoks_usage()
+{
+ cat << __EOT__
+ autoks [-k kickstart.xsl][-r] host tree
+ Copy installer to host and configure bootloader to boot it.
+ -k FILE Specify alternate kickstart template
+ -r reboot now
+__EOT__
+}
+
+autoks_do()
+{
+ require RSH RCP TFTPROOT
+ if [ -z "$1" -o -z "$2" ]; then
+ autoks_usage >&2
+ exit 1
+ fi
+ host="$1"
+ treename="$2"
+
+ $RSH -l root $host /bin/true || fail "Host $host must be up to auto-kickstart"
+
+ # Try to lookup something under the host so we catch a non-existant host early.
+ hostarch=`xpe -f $STXML "//host[@name='$host']/arch"` || fail "Could not look up architecture of host '$host'"
+
+ # Figure out where to copy the vmlinuz and initrd on the host
+ if [ "$hostarch" = "ia64" ]; then
+ bootdir="/boot/efi/EFI/redhat"
+ else
+ bootdir="/boot"
+ fi
+
+ # First lookup the tree information to make sure it exists.
+ ksargs=`xsltproc $XSLTOPTS --stringparam hostname "$host" --stringparam treename "$treename" $STEELTOE_ETC/autoks-args.xsl $STXML` || fail "Could not generate kickstart arguments for $host and $treename"
+ hostdir="$TFTPROOT/$host"
+ # Make sure the host's TFTP directory is populated
+ [ -d "$hostdir" ] || fail "No TFTP directory for $host, run tftp command first or install"
+ [ -e "$hostdir/vmlinuz" ] || fail "No kernel $host, run tftp command first or install"
+ [ -e "$hostdir/initrd.img" ] || fail "No ramdisk for $host, run tftp command first or install"
+
+ # Everything is in place, copy it out to host
+ echo "Copying files to $host for auto-kickstart of $treename"
+ $RCP $hostdir/vmlinuz root@$host:$bootdir/vmlinuz-steeltoe || fail "Failed to copy vmlinuz"
+ $RCP $hostdir/initrd.img root@$host:$bootdir/initrd.img-steeltoe || fail "Failed to copy initrd"
+
+ # Check to see if there is a steeltoe kernel and config on host already so we don't duplicate it.
+ $RSH -l root $host "/sbin/grubby --set-default $bootdir/vmlinuz-steeltoe"
+ defkernel=`$RSH -l root $host "/sbin/grubby --default-kernel --bad-image-okay"`
+ if [ "$defkernel" != "$bootdir/vmlinuz-steeltoe" ]; then
+ # There wasn't a bootloader entry already, make one
+ $RSH -l root $host "/sbin/grubby --make-default --title='steeltoe' --add-kernel=$bootdir/vmlinuz-steeltoe --initrd=$bootdir/initrd.img-steeltoe --args='$ksargs'" || fail "Failed to configure bootloader"
+ fi
+ $RSH -l root $host sync
+ if [ $reboot ]; then
+ echo "Rebooting $host"
+ $RSH -l root $host "reboot -fi" > /dev/null 2>&1 &
+ fi
+}
+
+
+COMMANDS="$COMMANDS virtinstall"
+virtinstall_usage()
+{
+ cat << __EOT__
+ virtinstall tree virthost installhost virtroot_device [virtshare_device [virtshare_device] [...]]
+ Install tree on virthost from installhost
+ onto virtroot_device, and configure virt guest
+ profile to use virtshare_device(s).
+__EOT__
+}
+
+virtinstall_do()
+{
+ if [[ $# < 4 ]]; then
+ virtinstall_usage >&2
+ exit 2
+ fi
+
+ treename="$1"
+ virthost="$2"
+ installhost="$3"
+ virtroot="$4"
+ shift 4
+ #virtshare is all the rest...
+
+ # Don't even bother if the virt host is up and running
+ if ping -c 1 -W 1 $virthost > /dev/null ; then
+ fail "$virthost seems to be running, cannot install on running virt guest"
+ fi
+
+ require RSH TFTPROOT TREESERVER KSBASE
+ # Let install_do do all the hard work and set up the tftp area.
+ tftp_do $virthost $treename
+
+ virtmac=$(xpe -f $STXML "//host[@name='$virthost']/macaddr") || fail "Unable to find $virthost mac address in $STXML"
+ DEFAULT_RAM=1024
+ virtram=$(xpe -f $STXML "//host[@name='$virthost']/ram")
+ virtram=${virtram:-$DEFAULT_RAM}
+
+ ks="$KSBASE/$virthost/ks.cfg"
+
+ hostarch=$(xpe -f $STXML "//host[@name='$virthost']/arch") || fail "Could not look up architecture of virthost '$virthost'"
+ treebase=$(xpe -f $STXML "//tree[@name='$treename' and meta/arch='$hostarch']/path") || fail "Could not find tree '$treename' for virthost '$virthost'"
+ location="http://$TREESERVER/$treebase"
+
+ extra="'ks=$ks ro selinux=0'"
+
+ # Rebuild the kickstart file, setting the virtnstall param on the xsltproc cmdline.
+ xsltproc --xinclude --stringparam virtinstall true --stringparam method http --stringparam hostname $virthost --stringparam treename $treename $kstemplate $STXML > $TFTPROOT/$virthost/ks.cfg
+
+ # Kick off the virtinstall on the installhost
+ $RSH -l root $installhost "virt-install -n $virthost -f $virtroot -r $virtram -m $virtmac -p -x $extra --nographics -l $location --noautoconsole"
+
+ # Add to the Xen config file created to add the share devices
+ # Blech, Xen specific stuff follows. Right now it seems we
+ # can only have xvda-xvdg as disks, so limit shared to b-g
+ XEN_CONFIGROOT=/etc/xen
+ SHARED_DEVICES=(xvdb xvdc xvdd xvde xvdf xvdg)
+ i=0
+ for vshare in $*; do
+ vshareentry="disk.append(\"phy:${vshare},${SHARED_DEVICES[$i]},w!\")"
+ echo "Adding $vshare to $virthost"
+ $RSH -l root $installhost "echo '$vshareentry' >> $XEN_CONFIGROOT/$virthost"
+ (( i = i + 1 ))
+ if [[ $i > 6 ]]; then
+ echo "Only support 5 shared devices, ignoring the rest"
+ fi
+ done
+}
+
+
+pxe_reboot()
+{
+ host="$1"
+
+ set -o pipefail
+ rootdev=`xpe -f $STXML "//host[@name='$host']/kickstart/rootdisk" | head -1` || fail "Root disk is not specified"
+ echo "Making /dev/$rootdev on $host unbootable"
+ $RSH -l root $host dd if=/dev/zero of=/dev/$rootdev bs=1 seek=510 count=2 conv=sync > /dev/null 2>&1
+ # Sync
+ $RSH -l root $host "sync"
+ # reboot
+ echo "Rebooting $host"
+ $RSH -l root $host "reboot -fin" > /dev/null 2>&1 &
+}
+
+
+efi_reboot()
+{
+ host="$1"
+
+ # Grab the EFI boot menu entry for Steeltoe
+ steeltoe=`$RSH -l root $host "efibootmgr" | awk -F\* '($1 ~ /^Boot/) && ($2 ~ /[Ss]teeltoe/) {print $1}'`
+
+ if [ -z "$steeltoe" ]; then
+ fail "Unable to find Steeltoe EFI boot entry on $host"
+ fi
+
+ # Set the NextBoot option to the found entry
+ echo "Updating EFI on $host to boot to Steeltoe"
+ $RSH -l root $host "efibootmgr -n ${steeltoe/Boot/}" || fail "Unable to set NextBoot to Steeltoe on $host"
+ # reboot
+ echo "Rebooting $host"
+ $RSH -l root $host "reboot -fin" > /dev/null 2>&1 &
+}
+
+COMMANDS="$COMMANDS list"
+list_usage()
+{
+ cat << __EOT__
+ list [-v] <host|group|tree> [substring]
+ List out hosts or trees
+__EOT__
+}
+
+list_do()
+{
+ class="$1"
+ search="$2"
+ if [ "$class" == "host" -o "$class" == "tree" -o "$class" == "group" ]; then
+ XSLTOPTS="$XSLTOPTS --stringparam class $class"
+ elif [ "$class" == "trees" ]; then
+ XSLTOPTS="$XSLTOPTS --stringparam class tree"
+ elif [ "$class" == "hosts" ]; then
+ XSLTOPTS="$XSLTOPTS --stringparam class host"
+ elif [ "$class" == "groups" ]; then
+ XSLTOPTS="$XSLTOPTS --stringparam class group"
+ else
+ list_usage >&2
+ exit 1
+ fi
+ if [ -n "$verbose" ]; then
+ XSLTOPTS="$XSLTOPTS --param verbose 1"
+ fi
+ xsltproc $XSLTOPTS $STEELTOE_ETC/list.xsl $STXML
+}
+
+
+# Make sure the first option is a command
+cmd="$1"
+if [ -z "$cmd" ]; then
+ global_usage
+ exit 1
+fi
+if ! echo $COMMANDS | grep -q -- "$cmd"; then
+ echo "Unknown command '$cmd'" >&2
+ global_usage
+ exit 1
+fi
+shift
+
+# Generate the getopts string based on command
+opts="h"
+case $cmd in
+ gendhcpd) opts="o:$opts";;
+ genpxe) opts="o:$opts";;
+ genelilo) opts="o:$opts";;
+ genyaboot) opts="o:$opts";;
+ genks) opts="m:o:k:$opts";;
+ install) opts="a:rm:k:$opts";;
+ autoks) opts="a:rm:k:$opts";;
+ virtinstall) opts="k:$opts";;
+ list) opts="v$opts";;
+esac
+
+# parse command line options for all subcommands
+while getopts "$opts" opt; do
+ case $opt in
+ a) forcearch="$OPTARG";;
+ o) outputfile="$OPTARG";;
+ r) reboot=1;;
+ v) verbose=1;;
+ m) instmethod="$OPTARG"; check_instmethod "$OPTARG";;
+ k) kstemplate="$OPTARG"; check_kstemplate "$OPTARG";;
+ *) global_usage;;
+ esac
+done
+shift $(($OPTIND - 1))
+# Run the command
+${cmd}_do $*
diff --git a/steeltoe.spec b/steeltoe.spec
new file mode 100644
index 0000000..946318e
--- /dev/null
+++ b/steeltoe.spec
@@ -0,0 +1,107 @@
+%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
+
+Name: steeltoe
+Version: 0.9.7
+Release: 1
+Summary: Steel Toe - Kickstart automation tools
+
+Group: QA
+License: GPL
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+Source0: steeltoe-%{version}.tar.bz2
+Requires: /usr/bin/xsltproc tftp-server dhcp xpe
+ExclusiveArch: noarch
+
+
+%description
+Steel Toe is a set of tools to help automate the generation of
+Kickstart and netboot config files.
+
+%package django
+Summary: Steel Toe Web Interface
+Requires: Django >= 0.96
+Group: QA
+
+%description django
+This is a web interface for Steel Toe.
+
+%prep
+%setup -q -c
+
+
+%build
+make %{?_smp_mflags}
+
+%install
+rm -rf $RPM_BUILD_ROOT
+make install DESTDIR=$RPM_BUILD_ROOT
+
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+
+%files
+%defattr(-,root,root,-)
+%doc README DESIGN
+/usr/bin/steeltoe
+%config(noreplace) /etc/steeltoe
+%config /etc/httpd/conf.d/tftp.conf
+
+%files django
+%defattr(-,root,root,-)
+%doc st_web/README
+%dir %{python_sitelib}/st_web
+%{python_sitelib}/st_web/
+
+
+%changelog
+* Tue Oct 07 2008 Nate Straz <nstraz@redhat.com> 0.9.7-1
+- Remove the virtinstall requirement of a shared device.
+
+* Tue Oct 7 2008 0.9.6-1
+- Handle the case where we can't figure out the volumes on a dom0.
+
+* Tue Jul 22 2008 0.9.5-1
+- Ping the host before using $RSH to contact it.
+
+* Fri Jun 27 2008 0.9.4-1
+- Add virtinstall command
+
+* Fri Jun 13 2008 0.9.2-2
+- Add recently used forms for trees and hosts.
+
+* Thu Jun 12 2008 Nate Straz <nstraz@redhat.com> 0.9.2-1
+- Add st_web Django application and give it the teeth to do installs
+
+* Wed Feb 13 2008 0.9-1
+- Add groups
+
+* Thu Jan 10 2008 0.8-1
+- New virtinstall command
+
+* Tue Nov 27 2007 0.7-1
+- Add an option to override the arch of a host.
+
+* Tue Jul 10 2007 0.6-3
+- Fix bz 214874
+
+* Mon Apr 16 2007 0.6-2
+- Try to get ppc support correct
+
+* Wed Jan 31 2007 0.6-1
+- Add preliminary ppc support
+
+* Wed Sep 13 2006 0.5-1
+- st_xpe and st_xpm were moved to their own package, xpe.
+
+* Mon Jun 05 2006 0.3-1
+- Add support for EFI
+- Merge up a bunch of functional changes into a package.
+
+* Tue May 16 2006 steeltoe 0.2-1
+- Add st_xpm utility
+
+* Tue Apr 04 2006 steeltoe 0.1-1
+- Initial packaging
+
diff --git a/steeltoe.xml b/steeltoe.xml
new file mode 100644
index 0000000..56357e3
--- /dev/null
+++ b/steeltoe.xml
@@ -0,0 +1,140 @@
+<!-- Steel Toe configuration and data file -->
+<steeltoe version="0.1">
+ <config><!-- Global configuration values -->
+ <tftpserver>steeltoe.lab.example.com</tftpserver>
+ <tftproot>/tftpboot/hosts</tftproot>
+ <yabootetc>/tftpboot/etc</yabootetc>
+ <tftpbase>/hosts</tftpbase>
+ <kickstartbase>http://steeltoe.lab.example.com/tftpboot/hosts</kickstartbase>
+ <bootloader>
+ <pxelinux.0>/tftpboot/pxelinux/pxelinux.0</pxelinux.0>
+ <elilo.efi>/tftpboot/efi/elilo.efi</elilo.efi>
+ <yaboot>/tftpboot/yaboot/yaboot</yaboot>
+ </bootloader>
+ <remoteshell>qarsh</remoteshell>
+ <remotecopy>qacp</remotecopy>
+ <treeserver>steeltoe.lab.example.com</treeserver>
+ </config>
+ <tree name="RHEL3-U7">
+ <path>/dist/released/RHEL-3/U7/AS/i386/tree</path>
+ <meta>
+ <family>RHEL3</family>
+ <variant>AS</variant>
+ <arch>i386</arch>
+ </meta>
+ </tree>
+ <tree name="RHEL3-U7">
+ <path>/dist/released/RHEL-3/U7/AS/x86_64/tree</path>
+ <meta>
+ <family>RHEL3</family>
+ <variant>AS</variant>
+ <arch>x86_64</arch>
+ </meta>
+ </tree>
+ <tree name="RHEL4-U3">
+ <path>/dist/released/RHEL-4/U3/AS/i386/tree</path>
+ <meta>
+ <family>RHEL4</family>
+ <variant>AS</variant>
+ <arch>i386</arch>
+ </meta>
+ </tree>
+ <tree name="RHEL4-U3">
+ <path>/dist/released/RHEL-4/U3/AS/x86_64/tree</path>
+ <meta>
+ <family>RHEL4</family>
+ <variant>AS</variant>
+ <arch>x86_64</arch>
+ </meta>
+ </tree>
+ <host name="dash-01">
+ <macaddr>00:12:3F:2A:C1:8A</macaddr>
+ <fqdn>dash-01.lab.example.com</fqdn>
+ <arch>x86_64</arch>
+ <bootloader>pxelinux.0</bootloader>
+ <kickstart>
+ <ksparams>nostorage</ksparams>
+ <ksdevice>eth0</ksdevice>
+
+ <device type="scsi">ata_piix</device>
+ <device type="scsi">qla2xxx</device>
+ <rootdisk>sda</rootdisk>
+ </kickstart>
+ </host>
+ <host name="morph-01">
+ <macaddr>00:30:48:27:a2:30</macaddr>
+ <fqdn>morph-01.lab.example.com</fqdn>
+ <arch>i386</arch>
+ <bootloader>pxelinux.0</bootloader>
+ <kickstart>
+ <ksdevice>eth0</ksdevice>
+
+ <rootdisk>hda</rootdisk>
+ </kickstart>
+ </host>
+ <host name="morph-02">
+ <macaddr>00:30:48:27:89:38</macaddr>
+ <fqdn>morph-02.lab.example.com</fqdn>
+ <arch>i386</arch>
+ <bootloader>pxelinux.0</bootloader>
+ <kickstart>
+ <ksdevice>eth0</ksdevice>
+
+ <rootdisk>hda</rootdisk>
+ </kickstart>
+ </host>
+ <host name="morph-03">
+ <macaddr>00:30:48:27:92:cc</macaddr>
+ <fqdn>morph-03.lab.example.com</fqdn>
+ <arch>i386</arch>
+ <bootloader>pxelinux.0</bootloader>
+ <kickstart>
+ <ksdevice>eth0</ksdevice>
+
+ <rootdisk>hda</rootdisk>
+ </kickstart>
+ </host>
+ <host name="morph-04">
+ <macaddr>00:30:48:27:6d:20</macaddr>
+ <fqdn>morph-04.lab.example.com</fqdn>
+ <arch>i386</arch>
+ <bootloader>pxelinux.0</bootloader>
+ <kickstart>
+ <ksdevice>eth0</ksdevice>
+
+ <rootdisk>hda</rootdisk>
+ </kickstart>
+ </host>
+ <host name="morph-05">
+ <macaddr>00:30:48:27:89:7e</macaddr>
+ <fqdn>morph-05.lab.example.com</fqdn>
+ <arch>i386</arch>
+ <bootloader>pxelinux.0</bootloader>
+ <kickstart>
+ <ksdevice>eth0</ksdevice>
+
+ <rootdisk>hda</rootdisk>
+ </kickstart>
+ </host>
+ <host name="basic">
+ <macaddr>00:11:25:C5:5F:88</macaddr>
+ <fqdn>basic.lab.example.com</fqdn>
+ <arch>ppc</arch>
+ <bootloader>yaboot</bootloader>
+ <kickstart>
+ <ksdevice>eth0</ksdevice>
+ <rootdisk>sda</rootdisk>
+ </kickstart>
+ </host>
+
+ <group name="morph">
+ morph-01
+ morph-02
+ morph-03
+ morph-04
+ morph-05
+ </group>
+</steeltoe>
+
+<!-- vim: ts=2 expandtab smartindent
+-->
diff --git a/tftp.conf b/tftp.conf
new file mode 100644
index 0000000..d768b87
--- /dev/null
+++ b/tftp.conf
@@ -0,0 +1,7 @@
+# Map /tftpboot into the web space so we can find kickstart files
+
+Alias /tftpboot /tftpboot
+
+<Location /tftpboot>
+ Options -Indexes
+</Location>
diff --git a/yaboot.xsl b/yaboot.xsl
new file mode 100644
index 0000000..92e75bc
--- /dev/null
+++ b/yaboot.xsl
@@ -0,0 +1,18 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:exsl="http://exslt.org/common"
+ version="1.0">
+ <xsl:param name="hostname"/>
+ <xsl:param name="treename"/>
+ <xsl:variable name="host" select="//host[@name=$hostname]"/>
+ <xsl:variable name="dist" select="//tree[@name=$treename and meta/arch=$host/arch]"/>
+ <xsl:include href="select-ks.xsl"/>
+ <xsl:output method="text" indent="no"/>
+ <xsl:template match="/" xml:space="preserve">
+timeout=50
+
+image=<xsl:value-of select="/steeltoe/config/tftpbase"/>/<xsl:value-of select="$host/@name"/>/vmlinuz
+ initrd=<xsl:value-of select="/steeltoe/config/tftpbase"/>/<xsl:value-of select="$host/@name"/>/ramdisk.image.gz
+ read-only
+ append="<xsl:if test="$host/console">console=<xsl:value-of select="$host/console"/> </xsl:if>ks=<xsl:value-of select="/steeltoe/config/kickstartbase"/>/<xsl:value-of select="$hostname"/>/ks.cfg ksdevice=<xsl:value-of select="$ks/ksdevice"/> <xsl:value-of select="$ks/ksparams"/>"
+</xsl:template>
+</xsl:stylesheet>