summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael DeHaan <mdehaan@redhat.com>2006-04-06 16:23:51 -0400
committerJim Meyering <jim@meyering.net>2006-04-06 16:23:51 -0400
commitecdf6cbd431de42756f3928b883854d36367b634 (patch)
tree0544161b2c9591fea668f0b65ee383d36e99a0b2
parent5b0f54d5674a1b47c112342994b6f0bae6172809 (diff)
downloadthird_party-cobbler-ecdf6cbd431de42756f3928b883854d36367b634.tar.gz
third_party-cobbler-ecdf6cbd431de42756f3928b883854d36367b634.tar.xz
third_party-cobbler-ecdf6cbd431de42756f3928b883854d36367b634.zip
Intermediate checkin. Working on various TODO features including inheritable kernel options, moving kickstarts to URLs, improved listing, etc. This commit breaks some things that will be fixed shortly...
-rw-r--r--Makefile9
-rw-r--r--TODO32
-rw-r--r--api.py34
-rwxr-xr-xbootconf9
-rw-r--r--bootconf.man217
-rw-r--r--bootconf.pod99
-rw-r--r--check.py2
-rw-r--r--config.py99
-rw-r--r--msg.py65
-rw-r--r--sync.py76
-rw-r--r--test.py28
-rw-r--r--util.py17
12 files changed, 537 insertions, 150 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4e6e435
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,9 @@
+all: manpage
+
+manpage:
+ pod2man bootconf.pod > bootconf.1
+ gzip bootconf.1
+ cp bootconf.1.gz /usr/share/man/man1
+
+install:
+ echo "(install not implemented)"
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..4951185
--- /dev/null
+++ b/TODO
@@ -0,0 +1,32 @@
+bootconf TODO list
+
+IMPORTANT ITEMS
+
+T - Do kernel copying into directories named after each distro
+ to resolve name conflicts
+I - Manpage (in progress) .. continuously...
+T - Optional kernel parameter input to replace defaults
+ show what kernel file is *currently* picked as latest.
+ on a per system, group, or distro basis. Priority
+ resolved to give precendence to system, then group, then
+ distro, then defaults.
+ PARTIAL: input allowed and serialized but *NOT USED YET*
+ - dlutter mentions directories for kernels might not resolve
+ to full paths. Check into this and fix.
+
+NICE TO HAVE
+
+T - Move program writeable section of config to /var
+ to conform with FS rules
+ - Make list show resolved values as currently set
+ for things like initrd, kernel, kickstart
+ rather than just the directories or partial filenames.
+ - Try pulling the kernel and initrd from the kickstart information?
+
+
+LEGEND
+
+T = DONE? BEING TESTED
+D = DONE
+P = PARTIAL
+I = IN PROGRESS
diff --git a/api.py b/api.py
index 36a736d..20d5a6b 100644
--- a/api.py
+++ b/api.py
@@ -22,17 +22,17 @@ class BootAPI:
self.utils = util.BootUtil(self,self.config)
# if the file already exists, load real data now
try:
- if os.path.exists(self.config.config_file):
+ if not self.config.files_exist():
self.config.deserialize()
except:
# traceback.print_exc()
- util.warning(m("no_cfg"))
+ print m("no_cfg")
try:
self.config.serialize()
except:
# traceback.print_exc()
pass
- if not os.path.exists(self.config.config_file):
+ if not self.config.files_exist():
self.config.serialize()
"""
@@ -200,7 +200,7 @@ class Groups(Collection):
Remove element named 'name' from the collection
"""
def remove(self,name):
- for k,v in self.api.get_groups().listing.items():
+ for k,v in self.api.get_systems().listing.items():
if v.group == name:
self.api.last_error = m("orphan_system")
return False
@@ -251,6 +251,9 @@ class Item:
self.name = name
return True
+ def set_kernel_options(self,options_string):
+ self.kernel_options = options_string
+
def to_datastruct(self):
raise "not implemented"
@@ -266,10 +269,12 @@ class Distro(Item):
self.name = None
self.kernel = None
self.initrd = None
+ self.kernel_options = ""
if seed_data is not None:
self.name = seed_data['name']
self.kernel = seed_data['kernel']
self.initrd = seed_data['initrd']
+ self.kernel_options = seed_data['kernel_options']
def set_kernel(self,kernel):
if self.api.utils.find_kernel(kernel):
@@ -294,11 +299,12 @@ class Distro(Item):
return {
'name': self.name,
'kernel': self.kernel,
- 'initrd' : self.initrd
+ 'initrd' : self.initrd,
+ 'kernel_options' : self.kernel_options
}
def __str__(self):
- return "%s : kernel=%s | initrd=%s |" % (self.name,self.kernel,self.initrd)
+ return "%s : kernel=%s | initrd=%s | opts=%s" % (self.name,self.kernel,self.initrd,self.kernel_options)
#---------------------------------------------
@@ -309,10 +315,12 @@ class Group(Item):
self.name = None
self.distro = None # a name, not a reference
self.kickstart = None
+ self.kernel_options = ""
if seed_data is not None:
self.name = seed_data['name']
self.distro = seed_data['distro']
self.kickstart = seed_data['kickstart']
+ self.kernel_options = seed_data['kernel_options']
def set_distro(self,distro_name):
if self.api.get_distros().find(distro_name):
@@ -329,7 +337,7 @@ class Group(Item):
return False
def is_valid(self):
- for x in (self.name, self.distro, self.kickstart):
+ for x in (self.name, self.distro):
if x is None:
return False
return True
@@ -338,11 +346,12 @@ class Group(Item):
return {
'name' : self.name,
'distro' : self.distro,
- 'kickstart' : self.kickstart
+ 'kickstart' : self.kickstart,
+ 'kernel_options' : self.kernel_options
}
def __str__(self):
- return "%s : distro=%s | kickstart=%s" % (self.name, self.distro, self.kickstart)
+ return "%s : distro=%s | kickstart=%s | opts=%s" % (self.name, self.distro, self.kickstart, self.kernel_options)
#---------------------------------------------
@@ -352,9 +361,11 @@ class System(Item):
self.api = api
self.name = None
self.group = None # a name, not a reference
+ self.kernel_options = ""
if seed_data is not None:
self.name = seed_data['name']
self.group = seed_data['group']
+ self.kernel_options = seed_data['kernel_options']
"""
A name can be a resolvable hostname (it instantly resolved and replaced with the IP),
@@ -386,10 +397,11 @@ class System(Item):
def to_datastruct(self):
return {
'name' : self.name,
- 'group' : self.group
+ 'group' : self.group,
+ 'kernel_options' : self.kernel_options
}
def __str__(self):
- return "%s : group=%s" % (self.name,self.group)
+ return "%s : group=%s | opts=%s" % (self.name,self.group,self.kernel_options)
diff --git a/bootconf b/bootconf
index b370cdd..3a6865a 100755
--- a/bootconf
+++ b/bootconf
@@ -136,7 +136,8 @@ class BootCLI:
sys = self.api.new_system()
commands = {
'--name' : lambda(a) : sys.set_name(a),
- '--group' : lambda(a) : sys.set_group(a)
+ '--group' : lambda(a) : sys.set_group(a),
+ '--kopts' : lambda(a) : sys.set_kernel_opts(a)
}
on_ok = lambda: self.api.get_systems().add(sys)
return self.apply_args(args,commands,on_ok,True)
@@ -149,7 +150,8 @@ class BootCLI:
commands = {
'--name' : lambda(a) : group.set_name(a),
'--distro' : lambda(a) : group.set_distro(a),
- '--kickstart' : lambda(a) : group.set_kickstart(a)
+ '--kickstart' : lambda(a) : group.set_kickstart(a),
+ '--kopts' : lambda(a) : group.set_kernel_opts(a)
}
on_ok = lambda: self.api.get_groups().add(group)
return self.apply_args(args,commands,on_ok,True)
@@ -162,7 +164,8 @@ class BootCLI:
commands = {
'--name' : lambda(a) : distro.set_name(a),
'--kernel' : lambda(a) : distro.set_kernel(a),
- '--initrd' : lambda(a) : distro.set_initrd(a)
+ '--initrd' : lambda(a) : distro.set_initrd(a),
+ '--kopts' : lambda(a) : distro.set_kopts(a)
}
on_ok = lambda: self.api.get_distros().add(distro)
return self.apply_args(args,commands,on_ok,True)
diff --git a/bootconf.man b/bootconf.man
new file mode 100644
index 0000000..7073b62
--- /dev/null
+++ b/bootconf.man
@@ -0,0 +1,217 @@
+.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
+.\"
+.\" Standard preamble:
+.\" ========================================================================
+.de Sh \" Subsection heading
+.br
+.if t .Sp
+.ne 5
+.PP
+\fB\\$1\fR
+.PP
+..
+.de Sp \" Vertical space (when we can't use .PP)
+.if t .sp .5v
+.if n .sp
+..
+.de Vb \" Begin verbatim text
+.ft CW
+.nf
+.ne \\$1
+..
+.de Ve \" End verbatim text
+.ft R
+.fi
+..
+.\" Set up some character translations and predefined strings. \*(-- will
+.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
+.\" double quote, and \*(R" will give a right double quote. | will give a
+.\" real vertical bar. \*(C+ will give a nicer C++. Capital omega is used to
+.\" do unbreakable dashes and therefore won't be available. \*(C` and \*(C'
+.\" expand to `' in nroff, nothing in troff, for use with C<>.
+.tr \(*W-|\(bv\*(Tr
+.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
+.ie n \{\
+. ds -- \(*W-
+. ds PI pi
+. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
+. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch
+. ds L" ""
+. ds R" ""
+. ds C` ""
+. ds C' ""
+'br\}
+.el\{\
+. ds -- \|\(em\|
+. ds PI \(*p
+. ds L" ``
+. ds R" ''
+'br\}
+.\"
+.\" If the F register is turned on, we'll generate index entries on stderr for
+.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
+.\" entries marked with X<> in POD. Of course, you'll have to process the
+.\" output yourself in some meaningful fashion.
+.if \nF \{\
+. de IX
+. tm Index:\\$1\t\\n%\t"\\$2"
+..
+. nr % 0
+. rr F
+.\}
+.\"
+.\" For nroff, turn off justification. Always turn off hyphenation; it makes
+.\" way too many mistakes in technical documents.
+.hy 0
+.if n .na
+.\"
+.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
+.\" Fear. Run. Save yourself. No user-serviceable parts.
+. \" fudge factors for nroff and troff
+.if n \{\
+. ds #H 0
+. ds #V .8m
+. ds #F .3m
+. ds #[ \f1
+. ds #] \fP
+.\}
+.if t \{\
+. ds #H ((1u-(\\\\n(.fu%2u))*.13m)
+. ds #V .6m
+. ds #F 0
+. ds #[ \&
+. ds #] \&
+.\}
+. \" simple accents for nroff and troff
+.if n \{\
+. ds ' \&
+. ds ` \&
+. ds ^ \&
+. ds , \&
+. ds ~ ~
+. ds /
+.\}
+.if t \{\
+. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
+. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
+. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
+. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
+. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
+. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
+.\}
+. \" troff and (daisy-wheel) nroff accents
+.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
+.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
+.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
+.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
+.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
+.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
+.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
+.ds ae a\h'-(\w'a'u*4/10)'e
+.ds Ae A\h'-(\w'A'u*4/10)'E
+. \" corrections for vroff
+.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
+.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
+. \" for low resolution devices (crt and lpr)
+.if \n(.H>23 .if \n(.V>19 \
+\{\
+. ds : e
+. ds 8 ss
+. ds o a
+. ds d- d\h'-1'\(ga
+. ds D- D\h'-1'\(hy
+. ds th \o'bp'
+. ds Th \o'LP'
+. ds ae ae
+. ds Ae AE
+.\}
+.rm #[ #] #H #V #F C
+.\" ========================================================================
+.\"
+.IX Title "BOOTCONF 1"
+.TH BOOTCONF 1 "2006-04-05" "perl v5.8.8" "User Contributed Perl Documentation"
+bootconf \- simple configuration of \s-1PXE\s0 boot + kickstart environments
+.SH "SYNOPSIS"
+.IX Header "SYNOPSIS"
+bootconf command [subcommand] [\-\-arg1=] [\-\-arg2=]
+See '\s-1INSTRUCTIONS\s0'.
+.SH "DESCRIPTION"
+.IX Header "DESCRIPTION"
+Configuration of a boot server involves a lot of things. Perhaps too many. These items include a tftpd server, a dhcpd server, syslinux, and a lot of copying files around and creating other config files. Bootconf makes these things simpler.
+.PP
+bootconf requires root access.
+.SH "INSTRUCTIONS"
+.IX Header "INSTRUCTIONS"
+\&\fBbefore you start...\fR
+.PP
+First install dhcpd, tftpd, and syslinux.
+You'll also need \s-1FTP\s0, \s-1HTTP\s0, or \s-1NFS\s0 to serve kickstarts (if you want them)
+And you'll also have to edit dhcpd.conf for your particular \s-1DHCP\s0 environment.
+Yes, \s-1DHCP\s0 is a required piece.
+.PP
+.Vb 3
+\& yum install dhcp tftp-server syslinux
+\& ...
+\& vi /etc/dhcpd.conf
+.Ve
+.PP
+\&\fBbootconf check\fR
+.PP
+This verifies that everything you need to set up is set up.
+This will mention any missing services and obvious configuration errors.
+Errors? Correct any problems reported, then run check until there are none.
+.PP
+\&\fBbootconf distro add \-\-name=distro_name \-\-kernel=path \-\-initrd=path\fR
+.PP
+Defines a distribution as a matched pair of a kernel and an initrd, and
+gives it a name. This could be 'fc5\-i386' or 'foosball\-73', it's your name.
+.PP
+\&\fBbootconf group add \-\-name=group_name \-\-kickstart=path \-\-distro=distro_name\fR
+.PP
+Defines a provisioning group, which is a distro (you did define a distro, right?) and a kickstart. Kickstarts are served up by URLs, though this needs to be a file. The distro parameter is whatever named you used earlier.
+.PP
+\&\fBbootconf system add \-\-name=ip|mac|hostname \-\-group=group_name\fR
+.PP
+Defines a system. A system can't have a free form name, it has to be a hostname that resolves to an \s-1IP\s0, a \s-1MAC\s0 address, or an actual \s-1IP\s0. The group parameter is the group name you used with \*(L"group add\*(R".
+.PP
+\&\fBbootconf distro list\fR
+.PP
+Gives a list of distributions that are currently configured. If you specified any kernels or initrd's by directories instead of full paths, it will indicate which ones are currently picked.
+.PP
+\&\fBbootconf group list\fR
+.PP
+Gives a list of groups that are currently configured.
+.PP
+\&\fBbootconf system list\fR
+.PP
+Gives a list of the sytems that are currently configured.
+.PP
+\&\fBbootconf distro remove \-\-name=distro_name\fR
+.PP
+Deletes a distro from the stored configuration. You can't delete a distro if any groups reference that distro, you'd have to delete all of the systems using that distro first. Deleting a distro just means that you no longer want it configured for booting, it does not delete any files.
+.PP
+\&\fBbootconf group remove \-\-name=group_name\fR
+.PP
+Deletes a group from the stored configuration. You can't delete a group if any systems reference it. Does not actually delete any kickstart files.
+.PP
+\&\fBbootconf system remove \-\-name=system_name\fR
+.PP
+Deletes a system from the stored configuration. You can always delete a system.
+.PP
+\&\fBbootconf sync [\-\-dryrun]\fR
+.PP
+Applies the stored configuration to the system. If you have any configuration errors, bootconf sync will ask you to run 'bootconf check' and fix them first.
+.PP
+Usage of the dryrun option will show you the pending changes without actually making them.
+.SH "CONFIGURATION_FILES"
+.IX Header "CONFIGURATION_FILES"
+bootconf uses /etc/bootconf.conf to store it's internal state. You may want to manually edit the global options at the top of the file for some reason. If you do so, run 'bootconf check' again to ensure there are no errors introduced from this. In a worse case scenario, you can delete the file and the next run of bootconf will recreate it.
+.SH "BUGS"
+.IX Header "BUGS"
+It's a new product, there could be one. Somewhere.
+.SH "EXIT_STATUS"
+.IX Header "EXIT_STATUS"
+bootconf returns a zero for success and non-zero for failure.
+.SH "AUTHOR"
+.IX Header "AUTHOR"
+Michael DeHaan <mdehaan@redhat.com>
diff --git a/bootconf.pod b/bootconf.pod
new file mode 100644
index 0000000..c4251af
--- /dev/null
+++ b/bootconf.pod
@@ -0,0 +1,99 @@
+=head1 NAME
+
+bootconf - simple configuration of PXE boot + kickstart environments
+
+=head1 SYNOPSIS
+
+bootconf command [subcommand] [--arg1=] [--arg2=]
+
+=head1 DESCRIPTION
+
+Configuration of a boot server involves a lot of things. Perhaps too many. These items include a tftpd server, a dhcpd server, syslinux, and a lot of copying files around and creating other config files. Bootconf makes these things simpler.
+
+bootconf requires root access.
+
+=head1 INSTRUCTIONS
+
+B<before you start...>
+
+First install dhcpd, tftpd, and syslinux.
+You'll also need FTP, HTTP, or NFS to serve kickstarts (if you want them)
+And you'll also have to edit dhcpd.conf for your particular DHCP environment.
+Yes, DHCP is a required piece.
+
+ yum install dhcp tftp-server syslinux
+ ...
+ vi /etc/dhcpd.conf
+
+B<bootconf check>
+
+This verifies that everything you need to set up is set up.
+This will mention any missing services and obvious configuration errors.
+Errors? Correct any problems reported, then run check until there are none.
+
+Do not forget to look at /etc/bootconf.conf and modify all parameters to your liking. Of particular note: The kernel_opts field is the default kernel options list for all netbooting systems. It can be overriden by distro, group, or system specific settings.
+
+B<bootconf distro add --name=distro_name --kernel=path --initrd=path [--kopts="string"]>
+
+Defines a distribution as a matched pair of a kernel and an initrd, and
+gives it a name. This could be 'fc5-i386' or 'foosball-73', it's your name. Kernel options are inherited from the kernel_opts parameter in /etc/bootconf.conf, though you can override them.
+
+B<bootconf group add --name=group_name --distro=distro_name [--kickstart=url] [--kopts="string"]>
+
+Defines a provisioning group, which is a distro (you did define a distro, right?) and optionally a kickstart. Kickstarts are served up by URLs, so these need to be HTTP, NFS, or FTP URLs that point to a kickstart file. Kernel options are inherited from the distro, but you can optionally override them. If you don't want a kickstart, that's fine.
+
+B<bootconf system add --name=ip|mac|hostname --group=group_name [--kopts="string"]>
+
+Defines a system. A system can't have a free form name, it has to be a hostname that resolves to an IP, a MAC address, or an actual IP. The group parameter is the group name you used with "group add". Kernel options are inherited from the group, you probably don't want to override them, but you can.
+
+B<bootconf distro list>
+
+Gives a list of distributions that are currently configured.
+
+B<bootconf group list>
+
+Gives a list of all of the groups in a system that are currently configured.
+
+B<bootconf system list>
+
+Gives a list of the sytems that are currently configured.
+
+B<bootconf distro remove --name=distro_name>
+
+Deletes a distro from the stored configuration. You can't delete a distro if any groups reference that distro, you'd have to delete all of the systems using that distro first. Deleting a distro just means that you no longer want it configured for booting, it does not delete any files.
+
+B<bootconf group remove --name=group_name>
+
+Deletes a group from the stored configuration. You can't delete a group if any systems reference it. Does not actually delete any kickstart files.
+
+B<bootconf system remove --name=system_name>
+
+Deletes a system from the stored configuration. You can always delete a system.
+
+B<bootconf sync [--dryrun]>
+
+Applies the stored configuration to the system. If you have any configuration errors, bootconf sync will ask you to run 'bootconf check' and fix them first.
+
+Usage of the dryrun option will show you the pending changes without actually making them.
+
+=head1 CONFIGURATION_FILES
+
+bootconf uses /etc/bootconf.conf to store basic settings and /var/bootconf/bootconf.conf for it's internal state.
+
+After editing /etc/bootconf.conf, run 'bootconf check' againt o ensure that no errors were introduced.
+
+If you happen to screw up /var/bootconf/bootconf.conf or /etc/bootconf.conf, you can delete them and they will be recreated.
+
+=head1 BUGS
+
+It's a new product, there could be one. Somewhere.
+
+=head1 EXIT_STATUS
+
+bootconf returns a zero for success and non-zero for failure.
+
+=head1 AUTHOR
+
+Michael DeHaan <mdehaan@redhat.com>
+
+
diff --git a/check.py b/check.py
index 311c23f..8a156fc 100644
--- a/check.py
+++ b/check.py
@@ -104,7 +104,5 @@ class BootCheck:
status.append(m("no_exist") % self.config.dhcpd_conf)
if not os.path.exists(self.config.kernel_root):
status.append(m("no_dir2") % (self.config.kernel_root, 'kernel_root'))
- if not os.path.exists(self.config.kickstart_root):
- status.append(m("no_dir2") % (self.config.kickstart_root, 'kickstart_root'))
diff --git a/config.py b/config.py
index 7a3adcc..163c588 100644
--- a/config.py
+++ b/config.py
@@ -22,10 +22,14 @@ class BootConfig:
"""
def __init__(self,api):
self.api = api
- self.config_file = "/etc/bootconf.conf"
+ self.settings_file = "/etc/bootconf.conf"
+ self.state_file = "/var/bootconf/bootconf.conf"
self.set_defaults()
self.clear()
+ def files_exist(self):
+ return os.path.exists(self.settings_file) and os.path.exists(self.state_file)
+
"""
Establish an empty list of groups, distros, and systems.
"""
@@ -38,9 +42,6 @@ class BootConfig:
Set some reasonable defaults in case no values are available
"""
def set_defaults(self):
- self.servername = "your_server_ip"
- self.kickstart_root = "/var/www/bootconf"
- self.kickstart_url = "http://%s/kickstart" % (self.servername)
self.kernel_root = "/var/www/bootconf"
self.tftpboot = "/tftpboot"
self.dhcpd_conf = "/etc/dhcpd.conf"
@@ -48,6 +49,7 @@ class BootConfig:
self.pxelinux = "/usr/lib/syslinux/pxelinux.0"
self.tftpd_bin = "/usr/sbin/in.tftpd"
self.dhcpd_bin = "/usr/sbin/dhcpd"
+ self.kernel_options = "append devfs=nomount ramdisk_size=16438 lang= vga=788 ksdevice=eth0 console=ttyS0,38400n8" #initrd and ks added programmatically
"""
Access the current groups list
@@ -75,9 +77,6 @@ class BootConfig:
# idea: add list of properties to self.properties
# and use method_missing to write accessors???
data = {}
- data['servername'] = self.servername
- data['kickstart_root'] = self.kickstart_root
- data['kickstart_url'] = self.kickstart_url
data['kernel_root'] = self.kernel_root
data['tftpboot'] = self.tftpboot
data['dhcpd_conf'] = self.dhcpd_conf
@@ -92,9 +91,6 @@ class BootConfig:
"""
def config_from_hash(self,hash):
try:
- self.servername = hash['servername']
- self.kickstart_root = hash['kickstart_root']
- self.kickstart_url = hash['kickstart_url']
self.kernel_root = hash['kernel_root']
self.tftpboot = hash['tftpboot']
self.dhcpd_conf = hash['dhcpd_conf']
@@ -103,27 +99,34 @@ class BootConfig:
self.tftpd_bin = hash['tftpd_bin']
self.dhcpd_bin = hash['dhcpd_bin']
except:
+ print "WARNING: config file error: %s" % (self.settings_file)
self.set_defaults()
"""
- Convert *everything* Boot knows about to a nested hash
+ Convert all items bootconfig knows about to a nested hash.
+ There are seperate hashes for the /etc and /var portions.
"""
- def to_hash(self):
+ def to_hash(self,is_etc):
world = {}
- world['config'] = self.config_to_hash()
- world['distros'] = self.get_distros().to_datastruct()
- world['groups'] = self.get_groups().to_datastruct()
- world['systems'] = self.get_systems().to_datastruct()
+ if is_etc:
+ world['config'] = self.config_to_hash()
+ else:
+ world['distros'] = self.get_distros().to_datastruct()
+ world['groups'] = self.get_groups().to_datastruct()
+ world['systems'] = self.get_systems().to_datastruct()
return world
"""
- Convert a hash representation of a Boot config to 'reality'
+ Convert a hash representation of a bootconfig to 'reality'
+ There are seperate hashes for the /etc and /var portions.
"""
- def from_hash(self,hash):
- self.config_from_hash(hash['config'])
- self.distros = api.Distros(self.api, hash['distros'])
- self.groups = api.Groups(self.api, hash['groups'])
- self.systems = api.Systems(self.api, hash['systems'])
+ def from_hash(self,hash,is_etc):
+ if is_etc:
+ self.config_from_hash(hash['config'])
+ else:
+ self.distros = api.Distros(self.api, hash['distros'])
+ self.groups = api.Groups(self.api, hash['groups'])
+ self.systems = api.Systems(self.api, hash['systems'])
# ------------------------------------------------------
# we don't care about file formats until below this line
@@ -134,13 +137,32 @@ class BootConfig:
could use YAML later if we wanted.
"""
def serialize(self):
+
+ settings = None
+ state = None
+
+ # ------
+ # dump global config (pathing, urls, etc)...
try:
- conf = open(self.config_file,"w+")
+ settings = open(self.settings_file,"w+")
except IOError:
- self.api.last_error = m("cant_create: %s" % self.config_file)
+ self.api.last_error = m("cant_create: %s" % self.settings_file)
return False
- data = self.to_hash()
- conf.write(yaml.dump(data))
+ data = self.to_hash(True)
+ settings.write(yaml.dump(data))
+
+ # ------
+ # dump internal state (distros, groups, systems...)
+ if not os.path.isdir(os.path.dirname(self.state_file)):
+ os.mkdir(os.path.dirname(self.state_file))
+ try:
+ state = open(self.state_file,"w+")
+ except:
+ self.api.last_error = m("cant_create: %s" % self.state_file)
+ data = self.to_hash(False)
+ state.write(yaml.dump(data))
+
+ # all good
return True
"""
@@ -149,14 +171,29 @@ class BootConfig:
could use YAML later if we wanted.
"""
def deserialize(self):
+
+ # -----
+ # load global config (pathing, urls, etc)...
try:
- conf = yaml.loadFile(self.config_file)
- raw_data = conf.next()
+ settings = yaml.loadFile(self.settings_file)
+ raw_data = settings.next()
if raw_data is not None:
- self.from_hash(raw_data)
- return True
+ self.from_hash(raw_data,True)
except:
self.api.last_error = m("parse_error")
return False
-
+ # -----
+ # load internal state(distros, systems, groups...)
+ try:
+ state = yaml.loadFile(self.state_file)
+ raw_data = state.next()
+ if raw_data is not None:
+ self.from_hash(raw_data,False)
+ except:
+ self.api.last_error = m("parse_error")
+ return False
+
+ # all good
+ return True
+
diff --git a/msg.py b/msg.py
index b2a3006..ed9147e 100644
--- a/msg.py
+++ b/msg.py
@@ -34,7 +34,7 @@ msg_table = {
"delete_nothing" : "can't delete something that doesn't exist",
"no_distro" : "distro does not exist",
"no_group" : "group does not exist",
- "no_kickstart" : "kickstart file not found",
+ "no_kickstart" : "kickstart must be an http://, ftp:// or nfs:// URL",
"no_kernel" : "the kernel needs to be a directory containing a kernel, or a full path. Kernels must be named just 'vmlinuz' or in the form 'vmlinuz-AA.BB.CC-something'",
"no_initrd" : "the initrd needs to be a directory containing an initrd, or a full path. Initrds must be named just 'initrd.img' or in the form 'initrd-AA.BB.CC-something.img",
"check_ok" : """
@@ -44,68 +44,7 @@ Manual editing of /etc/dhcpd.conf and /etc/bootconf.conf is suggested to tailor
Good luck.
""",
- "help" : """
-bootconf is a simple network boot configuration tool.
-It helps you set up Linux networks for PXE booting.
-
-***INSTRUCTIONS****
-
-First install dhcpd, tftpd, and syslinux.
-You'll also need FTP, HTTP, or NFS to serve kickstarts (if you want them)
-And you'll also have to edit dhcpd.conf.
-
-
- yum install dhcp tftp-server syslinux
- ...
- vi /etc/dhcpd.conf
-
-Verify that everything you need is set up.
-This will mention missing/stopped services and configuration errors.
-Errors? Correct any problems it reports, then run it again.
-
- bootconf check
-
-Define your distributions, and give them names
-A good example would be 'fc5-i386' or 'fc5-x86_64'
-Paths should be on a mounted filesystem.
-
- bootconf distro add --name="distro1" --kernel=path --initrd=path
-
-Define your provisioning "groups", and give them names too.
-Groups might be called 'webservers' or 'qamachines' or 'desktops'.
-Each group needs to know it's distribution.
-Kickstart can be done over NFS, FTP, or HTTP url, or just 'off'.
-
- bootconf group add --name="group1" --distro="name1" --kickstart=url|off
-
-Now add your systems to groups
-
- bootconf system add --name=mac|ipaddr|hostname --group="group1"
-
-Should you want to review things...
-
- bootconf distros list
- bootconf groups list
- bootconf systems list
-
-Should you need to delete anything ...
-
- bootconf distro remove --name="distro1"
- bootconf group remove --name="group1"
- bootconf system remove --name=ipaddr|mac|hostname
-
-Too much work? If you're brave, you can also edit '/etc/bootconf.conf'
-Make a backup first.
-
- vi /etc/bootconf.conf
-
-Now make all of that bootable (immediately)
-
- bootconf sync -dryrun # for the paranoid
- bootconf sync
-
-That's it!
- """
+ "help" : "see 'man bootconf'"
}
"""
diff --git a/sync.py b/sync.py
index 63be50c..024b6dd 100644
--- a/sync.py
+++ b/sync.py
@@ -72,18 +72,20 @@ class BootSync:
self.rmtree(os.path.join(self.api.config.tftpboot, "images"), True)
self.mkdir(images)
for d in self.api.get_distros().contents():
+ distro_dir = os.path.join(images,d.name)
+ self.mkdir(distro_dir)
kernel = self.api.utils.find_kernel(d.kernel) # full path
initrd = self.api.utils.find_initrd(d.initrd) # full path
- if kernel is None:
+ if kernel is None or not os.path.isfile(kernel):
self.api.last_error = "Kernel for distro (%s) cannot be found and needs to be fixed: %s" % (d.name, d.kernel)
raise "error"
- if kernel is None:
- self.api.last_error = "Initrd for distro (%s) cannot be found and needs to be fixed: %s" % (d.initrd, d.kernel)
+ if initrd is None or not os.path.isfile(initrd):
+ self.api.last_error = "Initrd for distro (%s) cannot be found and needs to be fixed: %s" % (d.name, d.initrd)
raise "error"
b_kernel = os.path.basename(kernel)
b_initrd = os.path.basename(initrd)
- self.copyfile(kernel, os.path.join(images, b_kernel))
- self.copyfile(initrd, os.path.join(images, b_initrd))
+ self.copyfile(kernel, os.path.join(distro_dir, b_kernel))
+ self.copyfile(initrd, os.path.join(distro_dir, b_initrd))
"""
Similar to what we do for distros, ensure all the kickstarts
@@ -151,9 +153,9 @@ class BootSync:
that would appear inside the system object in api.py
"""
def write_pxelinux_file(self,filename,system,group,distro):
- kernel_path = os.path.join("/images",os.path.basename(distro.kernel))
- initrd_path = os.path.join("/images",os.path.basename(distro.initrd))
- kickstart_path = self.api.config.kickstart_url + "/" + os.path.basename(group.kickstart)
+ kernel_path = os.path.join("/images",distro.name,os.path.basename(distro.kernel))
+ initrd_path = os.path.join("/images",distro.name,os.path.basename(distro.initrd))
+ kickstart_path = group.kickstart
self.sync_log("writing: %s" % filename)
self.sync_log("---------------------------------")
if self.dry_run:
@@ -165,10 +167,19 @@ class BootSync:
self.tee(file,"timeout 1\n")
self.tee(file,"label linux\n")
self.tee(file," kernel %s\n" % kernel_path)
- # FIXME: leave off kickstart if no kickstart...
- # FIXME: allow specifying of other (system specific?)
- # parameters in bootconf.conf ???
- self.tee(file," append devfs=nomount ramdisk_size=16438 lang= vga=788 ksdevice=eth0 initrd=%s ks=%s console=ttyS0,38400n8\n" % (initrd_path, kickstart_path))
+ # FIXME: allow leaving off the kickstart if no kickstart...
+ # FIXME: if the users kernel_options string has zero chance of
+ # booting we *could* try to detect it and warn them.
+ kopts = self.blend_kernel_options((
+ self.api.config.kernel_options,
+ group.kernel_options,
+ distro.kernel_options,
+ system.kernel_options
+ ))
+ nextline = " append %s initrd=%s" % (kopts,initrd_path)
+ if kickstart_path is not None and kickstart_path != "":
+ nextline = nextline + " ks=%s" % kickstart_path
+ self.tee(file, nextline)
if not self.dry_run:
file.close()
self.sync_log("--------------------------------")
@@ -216,8 +227,41 @@ class BootSync:
else:
print message
-
- # FUTURE: would be nice to check if dhcpd and tftpd are running...
- # and whether kickstart url works (nfs, http, ftp)
- # at least those that work with open-uri
+ """
+ Given a list of kernel options, take the values used by the
+ first argument in the list unless overridden by those in the
+ second (or further on), according to --key=value formats.
+
+ This is used such that we can have default kernel options
+ in /etc and then distro, group, and system options with various
+ levels of configurability.
+ """
+ def blend_kernel_options(self, list_of_opts):
+ internal = {}
+ results = []
+ # for all list of kernel options
+ for items in list_of_opts:
+ # get each option
+ tokens=items.split(" ")
+ # deal with key/value pairs and single options alike
+ for token in tokens:
+ key_value = tokens.split("=")
+ if len(key_value) == 1:
+ internal[key_value[0]] = ""
+ else:
+ internal[key_value[0]] = key_value[1]
+ # now go back through the final list and render the single
+ # items AND key/value items
+ for key in internal.keys():
+ data = internal[key]
+ if key == "ks" or key == "initrd" or key == "append":
+ # the user REALLY doesn't want to do this...
+ next
+ if data == "":
+ results.append(key)
+ else:
+ results.append("%s=%s" % (key,internal[key]))
+ # end result is a new fragment of a kernel options string
+ return results.join(" ")
+
diff --git a/test.py b/test.py
index 70f192c..afb6d88 100644
--- a/test.py
+++ b/test.py
@@ -16,7 +16,7 @@ FAKE_INITRD3="/tmp/initrd-1.8.18-3.9999_FAKE.img"
FAKE_KERNEL="/tmp/vmlinuz-2.6.15-1.2054_FAKE"
FAKE_KERNEL2="/tmp/vmlinuz-2.5.16-2.2055_FAKE"
FAKE_KERNEL3="/tmp/vmlinuz-1.8.18-3.9999_FAKE"
-FAKE_KICKSTART="/tmp/fake.ks"
+FAKE_KICKSTART="http://127.0.0.1/fake.ks"
class BootTest(unittest.TestCase):
def setUp(self):
@@ -28,8 +28,7 @@ class BootTest(unittest.TestCase):
self.api = api.BootAPI()
self.hostname = os.uname()[1]
create = [FAKE_INITRD,FAKE_INITRD2,FAKE_INITRD3,
- FAKE_KERNEL,FAKE_KERNEL2,FAKE_KERNEL3,
- FAKE_KICKSTART]
+ FAKE_KERNEL,FAKE_KERNEL2,FAKE_KERNEL3]
for fn in create:
f = open(fn,"w+")
self.make_basic_config()
@@ -75,14 +74,13 @@ class Utilities(BootTest):
self.assertTrue(self.api.utils.find_initrd("/tmp") == FAKE_INITRD)
def test_kickstart_scan(self):
- self.assertTrue(self.api.utils.find_kickstart(FAKE_INITRD))
+ self.assertFalse(self.api.utils.find_kickstart(FAKE_INITRD))
self.assertFalse(self.api.utils.find_kickstart("filedoesnotexist"))
self.assertFalse(self.api.utils.find_kickstart("/tmp"))
- # encapsulation is violated, but hey, this is a test case...
- self.api.config.kickstart_root="/tmp"
- self.assertTrue(self.api.utils.find_kickstart(FAKE_KICKSTART))
- self.assertTrue(self.api.utils.find_kickstart(os.path.basename(FAKE_KICKSTART)))
- # need a case for files that aren't kickstarts (inside)
+ self.assertTrue(self.api.utils.find_kickstart("http://bar"))
+ self.assertTrue(self.api.utils.find_kickstart("ftp://bar"))
+ self.assertTrue(self.api.utils.find_kickstart("nfs://bar"))
+ self.assertFalse(self.api.utils.find_kickstart("gopher://bar"))
def test_matching(self):
self.assertTrue(self.api.utils.is_mac("00:C0:B7:7E:55:50"))
@@ -123,14 +121,18 @@ class Additions(BootTest):
self.assertFalse(self.api.get_groups().add(group))
self.assertFalse(self.api.get_groups().find("testgroup2"))
- def test_invalid_group_non_referenced_kickstart(self):
+ def test_invalid_group_kickstart_not_url(self):
group = self.api.new_group()
self.assertTrue(group.set_name("testgroup12"))
self.assertTrue(group.set_distro("testdistro0"))
self.assertFalse(group.set_kickstart("kickstartdoesntexist"))
- self.assertFalse(self.api.get_groups().add(group))
- self.assertFalse(self.api.get_groups().find("testgroup3"))
- pass
+ # since kickstarts are optional, you can still add it
+ self.assertTrue(self.api.get_groups().add(group))
+ self.assertTrue(self.api.get_groups().find("testgroup12"))
+ # now verify the other kickstart forms would still work
+ self.assertTrue(group.set_kickstart("http://bar"))
+ self.assertTrue(group.set_kickstart("ftp://bar"))
+ self.assertTrue(group.set_kickstart("nfs://bar"))
def test_invalid_system_bad_name_host(self):
system = self.api.new_system()
diff --git a/util.py b/util.py
index f5450e4..8752a35 100644
--- a/util.py
+++ b/util.py
@@ -129,17 +129,12 @@ class BootUtil:
return None
"""
- Similar to find_kernel and find_initrd, see if a path or filename
- references a kickstart...
+ Check if a kickstart url looks like an http, ftp, or nfs url.
"""
- def find_kickstart(self,path):
- # Kickstarts must be explicit.
- # FUTURE: Look in configured kickstart path and don't require full paths to kickstart
- # FUTURE: Open kickstart file and validate that it's real
- if os.path.isfile(path):
- return path
- joined = os.path.join(self.config.kickstart_root, path)
- if os.path.isfile(joined):
- return joined
+ def find_kickstart(self,url):
+ x = url.lower()
+ for y in ["http://","nfs://","ftp://"]:
+ if x.startswith(y):
+ return url
return None