diff options
| author | Nathan Straz <nstraz@redhat.com> | 2009-02-02 18:36:40 -0500 |
|---|---|---|
| committer | Nathan Straz <nstraz@redhat.com> | 2009-02-02 18:36:40 -0500 |
| commit | 7e1247b426ae86d7c660a9ca87f76d43773670f6 (patch) | |
| tree | b91f593102a185a998bec50255e52ef0022de948 | |
| download | steeltoe-7e1247b426ae86d7c660a9ca87f76d43773670f6.tar.gz steeltoe-7e1247b426ae86d7c660a9ca87f76d43773670f6.tar.xz steeltoe-7e1247b426ae86d7c660a9ca87f76d43773670f6.zip | |
Import a sanitized version of Steel Toe, the provisioning system.
34 files changed, 2750 insertions, 0 deletions
@@ -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 $< @@ -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> |
