summaryrefslogtreecommitdiffstats
path: root/cobbler
diff options
context:
space:
mode:
authorMichael DeHaan <mdehaan@redhat.com>2007-11-12 13:25:23 -0500
committerMichael DeHaan <mdehaan@redhat.com>2007-11-12 13:25:23 -0500
commit57f449cc30b59970b97d7ee4d74cc02a98e07e30 (patch)
tree24b59435eba407c06645eecfeed882c0b31c240b /cobbler
parenta4dcebbff2db26dc22aadcc0f84328c19d52e48d (diff)
downloadthird_party-cobbler-57f449cc30b59970b97d7ee4d74cc02a98e07e30.tar.gz
third_party-cobbler-57f449cc30b59970b97d7ee4d74cc02a98e07e30.tar.xz
third_party-cobbler-57f449cc30b59970b97d7ee4d74cc02a98e07e30.zip
Abstract out the modules system to allow for other types of modules, and to also centralize configuration/loading/access some more.
Diffstat (limited to 'cobbler')
-rw-r--r--cobbler/api.py16
-rw-r--r--cobbler/cobbler2.py77
-rw-r--r--cobbler/command.py291
-rw-r--r--cobbler/module_loader.py22
-rw-r--r--cobbler/modules/serializer_shelve.py2
-rw-r--r--cobbler/modules/serializer_yaml.py2
-rw-r--r--cobbler/serializer.py33
-rw-r--r--cobbler/utils.py2
8 files changed, 422 insertions, 23 deletions
diff --git a/cobbler/api.py b/cobbler/api.py
index 2a51c1a..b846c81 100644
--- a/cobbler/api.py
+++ b/cobbler/api.py
@@ -38,7 +38,7 @@ class BootAPI:
self.__dict__ = self.__shared_state
if not BootAPI.has_loaded:
BootAPI.has_loaded = True
- self.modules = module_loader.load_modules()
+ module_loader.load_modules()
self._config = config.Config(self)
self.deserialize()
@@ -232,6 +232,20 @@ class BootAPI:
"""
return self._config.deserialize_raw(collection_name)
+ def get_module_by_name(self,module_name):
+ """
+ Returns a loaded cobbler module named 'name', if one exists, else None.
+ """
+ return module_loader.get_module_by_name(module_name)
+
+ def get_module_from_file(self,section,name):
+ """
+ Looks in /etc/cobbler/modules.conf for a section called 'section'
+ and a key called 'name', and then returns the module that corresponds
+ to the value of that key.
+ """
+ return module_loader.get_module_from_file(section,name)
+
if __name__ == "__main__":
api = BootAPI()
print api.version()
diff --git a/cobbler/cobbler2.py b/cobbler/cobbler2.py
new file mode 100644
index 0000000..29fe2b6
--- /dev/null
+++ b/cobbler/cobbler2.py
@@ -0,0 +1,77 @@
+#!/usr/bin/python
+
+"""
+Command line interface for cobbler, a network provisioning configuration
+library. Consult 'man cobbler' for general info.
+
+Copyright 2006-2007, Red Hat, Inc
+Michael DeHaan <mdehaan@redhat.com>
+
+This software may be freely redistributed under the terms of the GNU
+general public license.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+
+
+import glob
+import sys
+
+
+import command
+
+#FIXME: need a plug-in runtime module loader here
+from cmd_modules import call
+from cmd_modules import show
+from cmd_modules import copyfile
+from cmd_modules import listminions
+from cmd_modules import ping
+
+from func.overlord import client
+
+class FuncCommandLine(command.Command):
+ name = "func"
+ useage = "func is the commandline interface to a func minion"
+
+ subCommandClasses = [call.Call, show.Show,
+ copyfile.CopyFile, listminions.ListMinions, ping.Ping]
+
+ def __init__(self):
+
+ command.Command.__init__(self)
+
+ def do(self, args):
+ pass
+
+ def addOptions(self):
+ self.parser.add_option('', '--version', action="store_true",
+ help="show version information")
+
+ # just some ugly goo to try to guess if arg[1] is hostnamegoo or
+ # a command name
+ def _isGlob(self, str):
+ if str.find("*") or str.find("?") or str.find("[") or str.find("]"):
+ return True
+ return False
+
+ def handleArguments(self, args):
+ if len(args) < 2:
+ print "see the func manpage for usage"
+ sys.exit(411)
+ server_string = args[0]
+ # try to be clever about this for now
+ if client.isServer(server_string) or self._isGlob(server_string):
+ self.server_spec = server_string
+ args.pop(0)
+ # if it doesn't look like server, assume it
+ # is a sub command? that seems wrong, what about
+ # typo's and such? How to catch that? -akl
+ # maybe a class variable self.data on Command?
+
+ def handleOptions(self, options):
+ if options.version:
+ #FIXME
+ print "version is NOT IMPLEMENTED YET"
diff --git a/cobbler/command.py b/cobbler/command.py
new file mode 100644
index 0000000..c8de66b
--- /dev/null
+++ b/cobbler/command.py
@@ -0,0 +1,291 @@
+# -*- Mode: Python; test-case-name: test_command -*-
+# vi:si:et:sw=4:sts=4:ts=4
+
+# This file is released under the standard PSF license.
+#
+# from MOAP - https://thomas.apestaart.org/moap/trac
+# written by Thomas Vander Stichele (thomas at apestaart dot org)
+#
+
+# Modified from usage in func
+# https://hosted.fedoraproject.org/projects/func/
+
+"""
+Command class.
+"""
+
+import optparse
+import sys
+
+# from func.config import read_config, CONFIG_FILE
+# from func.commonconfig import CMConfig
+
+class CommandHelpFormatter(optparse.IndentedHelpFormatter):
+ """
+ I format the description as usual, but add an overview of commands
+ after it if there are any, formatted like the options.
+ """
+ _commands = None
+
+ def addCommand(self, name, description):
+ if self._commands is None:
+ self._commands = {}
+ self._commands[name] = description
+
+ ### override parent method
+ def format_description(self, description):
+ # textwrap doesn't allow for a way to preserve double newlines
+ # to separate paragraphs, so we do it here.
+ blocks = description.split('\n\n')
+ rets = []
+
+ for block in blocks:
+ rets.append(optparse.IndentedHelpFormatter.format_description(self,
+ block))
+ ret = "\n".join(rets)
+ if self._commands:
+ commandDesc = []
+ commandDesc.append("commands:")
+ keys = self._commands.keys()
+ keys.sort()
+ length = 0
+ for key in keys:
+ if len(key) > length:
+ length = len(key)
+ for name in keys:
+ format = " %-" + "%d" % length + "s %s"
+ commandDesc.append(format % (name, self._commands[name]))
+ ret += "\n" + "\n".join(commandDesc) + "\n"
+ return ret
+
+class CommandOptionParser(optparse.OptionParser):
+ """
+ I parse options as usual, but I explicitly allow setting stdout
+ so that our print_help() method (invoked by default with -h/--help)
+ defaults to writing there.
+ """
+ _stdout = sys.stdout
+
+ def set_stdout(self, stdout):
+ self._stdout = stdout
+
+ # we're overriding the built-in file, but we need to since this is
+ # the signature from the base class
+ __pychecker__ = 'no-shadowbuiltin'
+ def print_help(self, file=None):
+ # we are overriding a parent method so we can't do anything about file
+ __pychecker__ = 'no-shadowbuiltin'
+ if file is None:
+ file = self._stdout
+ file.write(self.format_help())
+
+class Command:
+ """
+ I am a class that handles a command for a program.
+ Commands can be nested underneath a command for further processing.
+
+ @cvar name: name of the command, lowercase
+ @cvar aliases: list of alternative lowercase names recognized
+ @type aliases: list of str
+ @cvar usage: short one-line usage string;
+ %command gets expanded to a sub-command or [commands]
+ as appropriate
+ @cvar summary: short one-line summary of the command
+ @cvar description: longer paragraph explaining the command
+ @cvar subCommands: dict of name -> commands below this command
+ @type subCommands: dict of str -> L{Command}
+ """
+ name = None
+ aliases = None
+ usage = None
+ summary = None
+ description = None
+ parentCommand = None
+ subCommands = None
+ subCommandClasses = None
+ aliasedSubCommands = None
+
+ def __init__(self, parentCommand=None, stdout=sys.stdout,
+ stderr=sys.stderr):
+ """
+ Create a new command instance, with the given parent.
+ Allows for redirecting stdout and stderr if needed.
+ This redirection will be passed on to child commands.
+ """
+ if not self.name:
+ self.name = str(self.__class__).split('.')[-1].lower()
+ self.stdout = stdout
+ self.stderr = stderr
+ self.parentCommand = parentCommand
+
+ # from Func, now removed:
+ # self.config = read_config(CONFIG_FILE, CMConfig)
+
+ # create subcommands if we have them
+ self.subCommands = {}
+ self.aliasedSubCommands = {}
+ if self.subCommandClasses:
+ for C in self.subCommandClasses:
+ c = C(self, stdout=stdout, stderr=stderr)
+ self.subCommands[c.name] = c
+ if c.aliases:
+ for alias in c.aliases:
+ self.aliasedSubCommands[alias] = c
+
+ # create our formatter and add subcommands if we have them
+ formatter = CommandHelpFormatter()
+ if self.subCommands:
+ for name, command in self.subCommands.items():
+ formatter.addCommand(name, command.summary or
+ command.description)
+
+ # expand %command for the bottom usage
+ usage = self.usage or self.name
+ if usage.find("%command") > -1:
+ usage = usage.split("%command")[0] + '[command]'
+ usages = [usage, ]
+
+ # FIXME: abstract this into getUsage that takes an optional
+ # parentCommand on where to stop recursing up
+ # useful for implementing subshells
+
+ # walk the tree up for our usage
+ c = self.parentCommand
+ while c:
+ usage = c.usage or c.name
+ if usage.find(" %command") > -1:
+ usage = usage.split(" %command")[0]
+ usages.append(usage)
+ c = c.parentCommand
+ usages.reverse()
+ usage = " ".join(usages)
+
+ # create our parser
+ description = self.description or self.summary
+ self.parser = CommandOptionParser(
+ usage=usage, description=description,
+ formatter=formatter)
+ self.parser.set_stdout(self.stdout)
+ self.parser.disable_interspersed_args()
+
+ # allow subclasses to add options
+ self.addOptions()
+
+ def addOptions(self):
+ """
+ Override me to add options to the parser.
+ """
+ pass
+
+ def do(self, args):
+ """
+ Override me to implement the functionality of the command.
+ """
+ pass
+
+ def parse(self, argv):
+ """
+ Parse the given arguments and act on them.
+
+ @rtype: int
+ @returns: an exit code
+ """
+ self.options, args = self.parser.parse_args(argv)
+
+ # FIXME: make handleOptions not take options, since we store it
+ # in self.options now
+ ret = self.handleOptions(self.options)
+ if ret:
+ return ret
+
+ # handle pleas for help
+ if args and args[0] == 'help':
+ self.debug('Asked for help, args %r' % args)
+
+ # give help on current command if only 'help' is passed
+ if len(args) == 1:
+ self.outputHelp()
+ return 0
+
+ # complain if we were asked for help on a subcommand, but we don't
+ # have any
+ if not self.subCommands:
+ self.stderr.write('No subcommands defined.')
+ self.parser.print_usage(file=self.stderr)
+ self.stderr.write(
+ "Use --help to get more information about this command.\n")
+ return 1
+
+ # rewrite the args the other way around;
+ # help doap becomes doap help so it gets deferred to the doap
+ # command
+ args = [args[1], args[0]]
+
+
+ # if we have args that we need to deal with, do it now
+ # before we start looking for subcommands
+ self.handleArguments(args)
+
+ # if we don't have subcommands, defer to our do() method
+ if not self.subCommands:
+ ret = self.do(args)
+
+ # if everything's fine, we return 0
+ if not ret:
+ ret = 0
+
+ return ret
+
+
+ # if we do have subcommands, defer to them
+ try:
+ command = args[0]
+ except IndexError:
+ self.parser.print_usage(file=self.stderr)
+ self.stderr.write(
+ "Use --help to get a list of commands.\n")
+ return 1
+
+ if command in self.subCommands.keys():
+ return self.subCommands[command].parse(args[1:])
+
+ if self.aliasedSubCommands:
+ if command in self.aliasedSubCommands.keys():
+ return self.aliasedSubCommands[command].parse(args[1:])
+
+ self.stderr.write("Unknown command '%s'.\n" % command)
+ return 1
+
+ def outputHelp(self):
+ """
+ Output help information.
+ """
+ self.parser.print_help(file=self.stderr)
+
+ def outputUsage(self):
+ """
+ Output usage information.
+ Used when the options or arguments were missing or wrong.
+ """
+ self.parser.print_usage(file=self.stderr)
+
+ def handleOptions(self, options):
+ """
+ Handle the parsed options.
+ """
+ pass
+
+ def handleArguments(self, arguments):
+ """
+ Handle the parsed arguments.
+ """
+ pass
+
+ def getRootCommand(self):
+ """
+ Return the top-level command, which is typically the program.
+ """
+ c = self
+ while c.parentCommand:
+ c = c.parentCommand
+ return c
diff --git a/cobbler/module_loader.py b/cobbler/module_loader.py
index 5b27a01..bb94fec 100644
--- a/cobbler/module_loader.py
+++ b/cobbler/module_loader.py
@@ -20,6 +20,13 @@ import os
import sys
import glob
from rhpl.translate import _, N_, textdomain, utf8
+import ConfigParser
+
+MODULE_CACHE = {}
+MODULES_BY_CATEGORY = {}
+
+cp = ConfigParser.ConfigParser()
+cp.read("/etc/cobbler/modules.conf")
plib = distutils.sysconfig.get_python_lib()
mod_path="%s/cobbler/modules" % plib
@@ -51,16 +58,25 @@ def load_modules(module_path=mod_path, blacklist=None):
errmsg = _("%(module_path)s/%(modname)s is not a proper module")
print errmsg % {'module_path': module_path, 'modname':modname}
continue
- if blip.register():
- mods[modname] = blip
+ category = blip.register()
+ if category:
+ MODULE_CACHE[modname] = blip
+ if not MODULES_BY_CATEGORY.has_key(category):
+ MODULES_BY_CATEGORY[category] = {}
+ MODULES_BY_CATEGORY[category][modname] = blip
except ImportError, e:
print e
raise
- return mods
+ return (MODULE_CACHE, MODULES_BY_CATEGORY)
+def get_module_by_name(name):
+ return MODULE_CACHE.get(name, None)
+def get_module_from_file(category,field):
+ value = cp.get("serializers",field)
+ return MODULE_CACHE.get(value, None)
if __name__ == "__main__":
print load_modules(module_path)
diff --git a/cobbler/modules/serializer_shelve.py b/cobbler/modules/serializer_shelve.py
index fa5b1d0..246a92a 100644
--- a/cobbler/modules/serializer_shelve.py
+++ b/cobbler/modules/serializer_shelve.py
@@ -67,7 +67,7 @@ def register():
"""
The mandatory cobbler module registration hook.
"""
- return True
+ return "serializer"
def serialize(obj):
"""
diff --git a/cobbler/modules/serializer_yaml.py b/cobbler/modules/serializer_yaml.py
index 4c3dfbb..6425bd1 100644
--- a/cobbler/modules/serializer_yaml.py
+++ b/cobbler/modules/serializer_yaml.py
@@ -31,7 +31,7 @@ def register():
"""
The mandatory cobbler module registration hook.
"""
- return True
+ return "serializer"
def serialize(obj):
"""
diff --git a/cobbler/serializer.py b/cobbler/serializer.py
index f7904c1..8593aad 100644
--- a/cobbler/serializer.py
+++ b/cobbler/serializer.py
@@ -17,28 +17,22 @@ import errno
import os
from rhpl.translate import _, N_, textdomain, utf8
-import yaml # Howell-Clark version
-
from cexceptions import *
import utils
import api as cobbler_api
-import modules.serializer_yaml as serializer_yaml
-import ConfigParser
-
-MODULE_CACHE = {}
-cp = ConfigParser.ConfigParser()
-cp.read("/etc/cobbler/modules.conf")
def serialize(obj):
"""
Save a collection to disk or other storage.
"""
-
storage_module = __get_storage_module(obj.collection_type())
storage_module.serialize(obj)
return True
def serialize_item(collection, item):
+ """
+ Save an item.
+ """
storage_module = __get_storage_module(collection.collection_type())
save_fn = getattr(storage_module, "serialize_item", None)
if save_fn is None:
@@ -49,6 +43,9 @@ def serialize_item(collection, item):
return save_fn(collection,item)
def serialize_delete(collection, item):
+ """
+ Delete an object from a saved state.
+ """
storage_module = __get_storage_module(collection.collection_type())
delete_fn = getattr(storage_module, "serialize_delete", None)
if delete_fn is None:
@@ -67,17 +64,19 @@ def deserialize(obj,topological=False):
return storage_module.deserialize(obj,topological)
def deserialize_raw(collection_type):
+ """
+ Return the datastructure corresponding to the serialized
+ disk state, without going through the Cobbler object system.
+ Much faster, when you don't need the objects.
+ """
storage_module = __get_storage_module(collection_type)
return storage_module.deserialize_raw(collection_type)
def __get_storage_module(collection_type):
+ """
+ Look up serializer in /etc/cobbler/modules.conf
+ """
+ capi = cobbler_api.BootAPI()
+ return capi.get_module_from_file("serializers",collection_type)
- if not MODULE_CACHE.has_key(collection_type):
- value = cp.get("serializers",collection_type)
- module = cobbler_api.BootAPI().modules[value]
- MODULE_CACHE[collection_type] = module
- return module
- else:
- return MODULE_CACHE[collection_type]
-
diff --git a/cobbler/utils.py b/cobbler/utils.py
index 46f0010..ad72feb 100644
--- a/cobbler/utils.py
+++ b/cobbler/utils.py
@@ -25,6 +25,8 @@ import logging
from cexceptions import *
from rhpl.translate import _, N_, textdomain, utf8
+MODULE_CACHE = {}
+
# import api # factor out
_re_kernel = re.compile(r'vmlinuz(.*)')