diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000000..d53b672c222 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.py[co] +build +# Emacs backup files... +*~ +.\#* +.doctrees diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..f3814032abf --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ansible"] + path = ansible + url = git://github.com/ansible/ansible.git diff --git a/Makefile b/Makefile new file mode 100644 index 00000000000..6f59a6dde81 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +#!/usr/bin/make +ASCII2HTMLMAN = a2x -D html/man/ -d manpage -f xhtml +SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()") + +all: clean docs + +docs: htmlman htmldocs + +htmlman: + mkdir -p html/man + $(ASCII2HTMLMAN) ansible/docs/man/man1/ansible.1.asciidoc + $(ASCII2HTMLMAN) ansible/docs/man/man5/ansible-modules.5.asciidoc + $(ASCII2HTMLMAN) ansible/docs/man/man5/ansible-playbook.5.asciidoc + +htmldocs: + ./build-site.py + +clean: + @echo "Cleaning up byte compiled python stuff" + find . -regex ".*\.py[co]$$" -delete + @echo "Cleaning up editor backup files" + find . -type f \( -name "*~" -or -name "#*" \) -delete + find . -type f \( -name "*.swp" \) -delete + +.PHONEY: docs manual clean +vpath %.asciidoc docs/man/man1 + + diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 6d67c09ede6..00000000000 --- a/_config.yml +++ /dev/null @@ -1,2 +0,0 @@ -markdown: rdiscount -pygments: true diff --git a/_layouts/default.html b/_layouts/default.html deleted file mode 100644 index da6c5140a9f..00000000000 --- a/_layouts/default.html +++ /dev/null @@ -1,58 +0,0 @@ - - -
- -This page provides a basic overview of correct YAML syntax.
+For ansible, every YAML script must be a list at it’s root-most +element. Each item in the list is a dictionary. These dictionaries +represent all the options you can use to write a ansible script. In +addition, all YAML files (regardless of their association with +ansible or not) should start with ---.
+In YAML a list can be represented in two ways. In one way all members +of a list are lines beginning at the same indentation level starting +with a - character:
+---
+# A list of tasty fruits
+- Apple
+- Orange
+- Strawberry
+- Mango
+In the second way a list is represented as comma separated elements +surrounded by square brackets. Newlines are permitted between +elements:
+---
+# A list of tasty fruits
+[apple, orange, banana, mango]
+A dictionary is represented in a simple key: and value form:
+---
+# An employee record
+name: John Eckersberg
+job: Developer
+skill: Elite
+Like lists, dictionaries can be represented in an abbreviated form:
+---
+# An employee record
+{name: John Eckersberg, job: Developer, skill: Elite}
+You can specify a boolean value (true/false) in several forms:
+---
+knows_oop: True
+likes_emacs: TRUE
+uses_cvs: false
+Finally, you can combine these data structures:
+---
+# An employee record
+name: John Eckersberg
+job: Developer
+skill: Elite
+employed: True
+foods:
+ - Apple
+ - Orange
+ - Strawberry
+ - Mango
+languages:
+ ruby: Elite
+ python: Elite
+ dotnet: Lame
+That’s all you really need to know about YAML to get started writing +Ansible scripts.
+See also
+
+# -*- coding: utf-8 -*-
+# Taboot - Client utility for performing deployments with Func.
+# Copyright © 2009,2011, Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from taboot.tasks.puppet import PuppetTaskResult
+from taboot.tasks.rpm import RPMTaskResult
+import re
+
+
+class _FileLikeOutputObject(object):
+ """
+ A file-like parent class.
+ """
+
+ import exceptions
+ import time as _time
+ defaults = None
+ starttime = None
+
+ def __init__(self, *args, **kwargs):
+ """
+ Creates an instance of a file-like object.
+
+ :Parameters:
+ - `args`: all non-keyword arguments.
+ - `kwargs`: all keyword arguments.
+ """
+ import ConfigParser
+ import os.path
+
+ if _FileLikeOutputObject.defaults is None:
+ if os.path.expanduser("~/.taboot.conf"):
+ _FileLikeOutputObject.defaults = ConfigParser.ConfigParser()
+ _FileLikeOutputObject.defaults.read(
+ os.path.expanduser("~/.taboot.conf"))
+
+ # Only set the start time once, not for each logger instance
+ if _FileLikeOutputObject.starttime is None:
+ import datetime
+ _FileLikeOutputObject.starttime = datetime.datetime.today()
+
+ self._pos = 0L
+ self._closed = False
+ self._setup(*args, **kwargs)
+
+ def _setup(self, *args, **kwargs):
+ """
+ Implementation specific setup.
+
+ :Parameters:
+ - `args`: all non-keyword arguments.
+ - `kwargs`: all keyword arguments.
+ """
+ pass
+
+ def flush(self):
+ """
+ We are not buffering so we always just return None.
+ """
+ return None
+
+ def read(self, *args, **kwargs):
+ """
+ We are an output only file-like object. Raise exception.
+
+ :Parameters:
+ - `args`: all non-keyword arguments.
+ - `kwargs`: all keyword arguments.
+ """
+ raise self.exceptions.NotImplementedError('Object for output only.')
+
+ def tell(self):
+ """
+ Returns the position of the file-like object.
+ """
+ return self._pos
+
+ def truncate(self, size):
+ """
+ We are an output only file-like object. Raise exception.
+
+ :Parameters:
+ - `size`: size to truncate to.
+ """
+ raise self.exceptions.NotImplementedError(
+ 'This does not support truncate.')
+
+ def writelines(self, sequence):
+ """
+ Writes a sequence of lines.
+
+ :Parameters:
+ - `sequence`: iterable sequence of data to write.
+ """
+ for item in sequence:
+ self.write(item)
+
+ def write(self, item):
+ """
+ Writer wrapper (not rapper, beav). Simply calls _write which is
+ implementation specific and updates the position.
+
+ :Parameters:
+ - `item`: the item to write.
+ """
+ self._write(item)
+ self._pos += 1
+
+ def _write(self, item):
+ """
+ Implementation of writing data.
+
+ :Parameters:
+ - `item`: the item to write.
+ """
+ raise self.exceptions.NotImplementedError(
+ '_write must be overriden.')
+
+ def close(self):
+ """
+ Close wrapper (again, not rapper, beav). Simply calls _close which
+ is implementation specific and updates the closed property.
+ """
+ self._close()
+ self._closed = True
+
+ def _close(self):
+ """
+ Implementation of closing the file-like object.
+ By default nothing occurs.
+ """
+ pass
+
+ # Read aliases
+ readline = read
+ readlines = read
+ xreadlines = read
+ seek = read
+
+ # Read-only Properties
+ closed = property(lambda self: self._closed)
+ timestamp = property(lambda self: self._time.strftime(
+ "%Y-%m-%d %H:%M:%S", self._time.localtime()))
+
+
+[docs]class CLIOutput(_FileLikeOutputObject):
+ """
+ Output a :class:`taboot.tasks.TaskResult` to the command line
+ with pretty formatting and colors.
+ """
+
+ def _setup(self, host, task):
+ """
+ Implementation specific setup for outputting to the CLI.
+
+ :Parameters:
+ - `host`: name of the host
+ - `task`: name of the task
+ """
+ import Colors
+ import sys
+ self._c = Colors.Colors()
+ self._sys = sys
+ self._sys.stdout.write('%s:\n' % (
+ self._c.format_string(host, 'blue')))
+ self._sys.stdout.write('%s Starting Task[%s]\n' % (
+ self.timestamp, self._c.format_string(task, 'white')))
+
+ def _write(self, result):
+ """
+ Implementation of writing to the CLI.
+
+ :Parameters:
+ - `result`: result object to inspect and write
+ """
+ import types
+
+ # Set output color
+ output_color = 'red'
+ if result.success:
+ output_color = 'green'
+
+ self._sys.stdout.write("%s:\n" % (
+ self._c.format_string(result.host, 'blue')))
+ self._sys.stdout.write("%s Finished Task[%s]:\n" % (
+ self.timestamp, self._c.format_string(
+ result.task, output_color)))
+
+ if isinstance(result, PuppetTaskResult):
+ # If result is an instance of PuppetTaskResult,
+ # colorize the puppet output
+ lines = result.output.splitlines()
+ for line in lines:
+ if re.match('info:', line):
+ self._sys.stdout.write("%s\n" % self._c.format_string(
+ line.strip(), 'green'))
+ elif re.match('notice:', line):
+ self._sys.stdout.write("%s\n" % self._c.format_string(
+ line.strip(), 'blue'))
+ elif re.match('warning:', line):
+ self._sys.stdout.write("%s\n" % self._c.format_string(
+ line.strip(), 'yellow'))
+ elif re.match('err:', line):
+ self._sys.stdout.write("%s\n" % self._c.format_string(
+ line.strip(), 'red'))
+ else:
+ self._sys.stdout.write("%s\n" % self._c.format_string(
+ line.strip(), 'normal'))
+ elif isinstance(result, RPMTaskResult):
+ # If result is an instance of RPMTaskResult,
+ # colorize the rpm.PostManifest output
+ lines = result.output.splitlines()
+ for line in lines:
+ if line.startswith('-'):
+ self._sys.stdout.write("%s\n" % self._c.format_string(
+ line.strip(), 'red'))
+ elif line.startswith('+'):
+ self._sys.stdout.write("%s\n" % self._c.format_string(
+ line.strip(), 'green'))
+ else:
+ self._sys.stdout.write("%s\n" % self._c.format_string(
+ line.strip(), 'normal'))
+ else:
+ # Use standard pass/fall coloring for output
+ if isinstance(result.output, types.ListType):
+ for r in result.output:
+ self._sys.stdout.write("%s\n" % self._c.format_string(
+ r.strip(), output_color))
+ else:
+ self._sys.stdout.write("%s\n" % self._c.format_string(
+ result.output.strip(), output_color))
+
+
+[docs]class LogOutput(_FileLikeOutputObject):
+ """
+ Output a :class:`taboot.tasks.TaskResult` to a logfile.
+ """
+
+ def _setup(self, host, task, logfile='taboot.log'):
+ """
+ Implementation specific setup for outputting to a log.
+
+ :Parameters:
+ - `logfile`: name of the logfile to write to.
+ """
+ self._logfile = logfile
+ if self._logfile in ('-', 'stdout', '1'):
+ import sys
+ self._log_fd = sys.stdout
+ else:
+ self._log_fd = open(logfile, 'a')
+ self._log_fd.write('%s:\n%s Starting Task[%s]\n\n' % (
+ host, self.timestamp, task))
+
+ def _write(self, result):
+ """
+ Implementation of writing to a log.
+
+ :Parameters:
+ - `result`: result object to inspect and write
+ """
+ import types
+
+ if result.success:
+ success_str = 'OK'
+ else:
+ success_str = 'FAIL'
+
+ self._log_fd.write("%s:\n%s Finished Task[%s]: %s\n" % (
+ result.host, self.timestamp, result.task, success_str))
+
+ if isinstance(result.output, types.ListType):
+ for r in result.output:
+ self._log_fd.write("%s\n\n" % r.strip())
+ else:
+ self._log_fd.write("%s\n\n" % result.output.strip())
+
+
+[docs]class EmailOutput(_FileLikeOutputObject):
+ """
+ Output a :class:`taboot.tasks.TaskResult` to a logfile.
+ """
+
+ def _setup(self, to_addr, from_addr='taboot@redhat.com'):
+ """
+ Implementation specific setup for outputting to a log.
+
+ :Parameters:
+ - `to_addr`: who to send the email to.
+ - `from_addr`: who the email is from.
+ """
+ try:
+ import cStringIO as StringIO
+ except ImportError, ie:
+ import StringIO
+ self._to_addr = to_addr
+ self._from_addr = from_addr
+ self._buffer = StringIO.StringIO()
+
+ def _write(self, result):
+ """
+ Implementation of writing out to an email.
+
+ :Parameters:
+ - `result`: result object to inspect and write
+ """
+ if result.success:
+ success_str = 'OK'
+ else:
+ success_str = 'FAIL'
+
+ self._buffer.write("%s: %s" % (task_result.task, success_str))
+
+[docs] def flush(self):
+ """
+ Flushing sends the email with the buffer.
+ """
+ import smtplib
+ from email.mime.text import MIMEText
+
+ self._buffer.flush()
+ msg = self.MIMEText(self._buffer.read())
+ msg['Subject'] = task_result.host
+ msg['From'] = self._from_addr
+ msg['To'] = self._to_addr
+
+ smtp = self.smtplib.SMTP()
+ smtp.connect()
+ smtp.sendmail(self._from_addr, [self._to_addr], msg.as_string())
+ smtp.close()
+
+ def __del__(self):
+ """
+ If the buffer is not empty before destroying, flush.
+ """
+ if self._buffer.pos < self._buffer.len:
+ self.flush()
+
+
+[docs]class HTMLOutput(_FileLikeOutputObject):
+ """
+ Output a :class:`taboot.tasks.TaskResult` to the command line
+ with pretty formatting and colors.
+
+ .. document private functions
+ .. automethod:: _write
+ """
+
+ logfile_path = None
+
+ def _expand_starttime(self, param):
+ """
+ Expand any instances of "%s" in `param`
+ """
+ if '%s' in param:
+ p = param % HTMLOutput.starttime
+ return p.replace(" ", "-")
+ else:
+ return param
+
+ def _setup(self, host, task, logfile="taboot-%s.html", destdir="."):
+ """
+ Implementation specific setup for outputting to an HTML file.
+
+ :Parameters:
+ - `host`: name of the host
+ - `task`: name of the task
+ - `logfile`: name of the file to log to, '%s' is substituted
+ with a datestamp
+ - `destdir`: directory in which to save the log file to
+ """
+ import Colors
+ import sys
+ import os.path
+ import os
+
+ _default_logfile = "taboot-%s.html"
+ _default_destdir = "."
+
+ # Pick if the parameter is changed
+ # Pick if above is false and logfile is set in defaults
+ # Else, use parameter
+ if not logfile == _default_logfile:
+ _logfile = logfile
+ elif HTMLOutput.defaults is not None and \
+ HTMLOutput.defaults.has_option("HTMLOutput", "logfile"):
+ _logfile = HTMLOutput.defaults.get("HTMLOutput", "logfile")
+ else:
+ _logfile = logfile
+
+ # Expand %s into a time stamp if necessary
+ _logfile = self._expand_starttime(_logfile)
+
+ if not destdir == _default_destdir:
+ _destdir = destdir
+ elif HTMLOutput.defaults is not None and \
+ HTMLOutput.defaults.has_option("HTMLOutput", "destdir"):
+ _destdir = HTMLOutput.defaults.get("HTMLOutput", "destdir")
+ else:
+ _destdir = destdir
+
+ # Figured it all out, now we join them together!
+ self._logfile_path = os.path.join(_destdir, _logfile)
+ if not os.path.exists(_destdir):
+ os.makedirs(_destdir, 0755)
+
+ self._c = Colors.HTMLColors()
+ self._log_fd = open(self._logfile_path, 'a')
+
+ # Lets only print this when it is set or changed
+ if HTMLOutput.logfile_path is None or \
+ not HTMLOutput.logfile_path == self._logfile_path:
+ sys.stderr.write("Logging HTML Output to %s\n" % \
+ self._logfile_path)
+ HTMLOutput.logfile_path = self._logfile_path
+ sys.stderr.flush()
+
+ # Log the start of this task
+ name = self._fmt_anchor(self._fmt_hostname(host))
+ start_msg = """<p><tt>%s:</tt></p>
+<p><tt>%s Starting Task[%s]\n</tt>""" % (name, self.timestamp, task)
+ self._log_fd.write(start_msg)
+ self._log_fd.flush()
+
+ def _fmt_anchor(self, text):
+ """
+ Format an #anchor and a clickable link to it
+ """
+ h = hash(self.timestamp)
+ anchor_str = "<a name='%s' href='#%s'>%s</a>" % (h, h, text)
+ return anchor_str
+
+ def _fmt_hostname(self, n):
+ """
+ Standardize the hostname formatting
+ """
+ return "<b>%s</b>" % self._c.format_string(n, 'blue')
+
+[docs] def _write(self, result):
+ """
+ Write a tasks `result` out to HTML. Handles enhanced stylizing
+ for task results that support such as:
+
+ - :py:mod:`taboot.tasks.puppet.PuppetTaskResult`
+ - :py:mod:`taboot.tasks.rpm.RPMTaskResult`
+ """
+ import types
+ import sys
+ import cgi
+
+ name = self._fmt_hostname(result.host)
+
+ # escape any html in result.output
+ result.output = cgi.escape(result.output)
+
+ if result.success:
+ success_str = self._c.format_string('<b>OK</b>', 'green')
+ else:
+ success_str = self._c.format_string('<b>FAIL</b>', 'red')
+
+ self._log_fd.write("<p><tt>%s:\n</tt></p>\n<p><tt>%s "\
+ "Finished Task[%s]: %s</tt></p>\n" %
+ (name, self.timestamp, result.task, success_str))
+
+ if isinstance(result, PuppetTaskResult):
+ # If result is an instance of PuppetTaskResult,
+ # colorize the puppet output
+ lines = result.output.splitlines()
+ for line in lines:
+ if re.match('info:', line):
+ self._log_fd.write("%s<br />\n" %
+ self._c.format_string(line.strip(),
+ 'green'))
+ elif re.match('notice:', line):
+ self._log_fd.write("%s<br />\n" %
+ self._c.format_string(line.strip(),
+ 'blue'))
+ elif re.match('warning:', line):
+ self._log_fd.write("%s<br />\n" %
+ self._c.format_string(line.strip(),
+ 'yellow'))
+ elif re.match('err:', line):
+ self._log_fd.write("%s<br />\n" %
+ self._c.format_string(line.strip(),
+ 'red'))
+ else:
+ self._log_fd.write("%s<br />\n" %
+ self._c.format_string(line.strip(),
+ 'normal'))
+ self._log_fd.write("<br /><br />\n")
+ elif isinstance(result, RPMTaskResult):
+ # If result is an instance of RPMTaskResult,
+ # colorize the rpm.PostManifest output
+ lines = result.output.splitlines()
+ for line in lines:
+ if line.startswith('-'):
+ self._log_fd.write("%s<br />\n" %
+ self._c.format_string(line.strip(),
+ 'red'))
+ elif line.startswith('+'):
+ self._log_fd.write("%s<br />\n" %
+ self._c.format_string(line.strip(),
+ 'green'))
+ else:
+ self._log_fd.write("%s<br />\n" %
+ self._c.format_string(line.strip(),
+ 'normal'))
+ self._log_fd.write("<br /><br />\n")
+ else:
+ # Use standard pass/fall coloring for output
+ if isinstance(result.output, types.ListType):
+ for r in result.output:
+ self._log_fd.write("<pre>%s</pre>\n<br /><br />\n" %
+ r.strip())
+ else:
+ self._log_fd.write("<pre>%s</pre>\n<br /><br />\n" %
+ result.output.strip())
+
+ self._log_fd.flush()
+
+# -*- coding: utf-8 -*-
+# Taboot - Client utility for performing deployments with Func.
+# Copyright © 2009-2011, Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import threading
+from taboot.util import instantiator
+from log import *
+
+
+[docs]class Runner(object):
+ """
+ The Runner, responsible for running a taboot job.
+ """
+
+ import threading
+ import taboot.output
+
+ def __init__(self, script, config, expand_globs=True):
+ """
+ :Parameters:
+ - `script`: an instance of tabootScript
+
+ - `expand_globs`: whether to expand the globs or just leave
+ them as is.
+ """
+ self._config = config
+ self._hosts = script.getHosts()
+ self._tasks = script.getTaskTypes()
+ self._output = script.getOutputTypes()
+ self._task_q = []
+ self._fail_event = self.threading.Event()
+
+ if expand_globs:
+ self._hosts = self._expand_globs()
+ log_debug("Will operate on %s host(s).", len(self._hosts))
+
+ # Prefight threading
+ self._preflight_tasks = script.getPreflightTypes()
+ self._preflight_semaphore = self.threading.Semaphore(
+ len(self._hosts))
+ self._preflight_tasks_q = []
+
+ # Main task-body threading
+ if script.getConcurrency() == "all":
+ self._concurrency = len(self._hosts)
+ else:
+ self._concurrency = int(script.getConcurrency())
+ log_debug("Setting task-body concurrency to %s.",
+ self._concurrency)
+ self._semaphore = self.threading.Semaphore(self._concurrency)
+
+ def _run_preflight(self):
+ """
+ Run the jobs in a prefilght section.
+ """
+ import signal
+
+ rdy_msg = "\nPre-Flight complete, press enter to continue: "
+
+ for host in self._hosts:
+ t = TaskRunner(host, self._preflight_tasks,
+ self._preflight_semaphore,
+ self._output, self._fail_event)
+ t.start()
+ self._preflight_tasks_q.append(t)
+
+ signal.signal(signal.SIGINT, self.__sighandler)
+
+ for task in self._task_q:
+ while task.isAlive():
+ task.join(0.1)
+
+ while len(self.threading.enumerate()) > 1:
+ # Even though all the threads may have been joined we
+ # should still wait for them to terminate. If we don't
+ # wait for that we will likely see the 'continue?' prompt
+ # before the preflight output gets a chance to print.
+ pass
+
+ if not self._config["onlypreflight"]:
+ ready = raw_input(rdy_msg)
+
+ if self._fail_event.isSet():
+ return False
+ return True
+
+ def _run_tasks(self):
+ """
+ Run a task body.
+ """
+ import signal
+
+ for host in self._hosts:
+ t = TaskRunner(host, self._tasks, self._semaphore, self._output,
+ self._fail_event)
+ t.start()
+ self._task_q.append(t)
+
+ signal.signal(signal.SIGINT, self.__sighandler)
+
+ for task in self._task_q:
+ while task.isAlive():
+ task.join(0.1)
+
+ if self._fail_event.isSet():
+ return False
+ return True
+
+[docs] def run(self):
+ """
+ Run the preflight/tasks-body
+ """
+
+ if len(self._preflight_tasks) > 0:
+ if not self._run_preflight():
+ return False
+
+ if self._config["onlypreflight"]:
+ return True
+ else:
+ return self._run_tasks()
+
+ def _expand_globs(self):
+ """
+ Returns the hosts that expand out from globs.
+
+ This is kind of a dirty hack around how Func returns minions
+ in an arbitrary order.
+ """
+
+ import func.overlord.client as fc
+
+ if not self._hosts:
+ return []
+ if isinstance(self._hosts, basestring):
+ glob = self._hosts
+ c = fc.Client(glob)
+ return c.list_minions()
+ else:
+ # Iterate over each given item, expand it, and then push
+ # it onto our list. But only if it doesn't exist already!
+ found_hosts = []
+ for h in self._hosts:
+ c = fc.Client(h)
+ new_hosts = filter(lambda h: h not in found_hosts,
+ c.list_minions())
+ found_hosts.extend(new_hosts)
+ # for found_host in c.list_minions():
+ # h = filter
+ # if not found_host in found_hosts:
+ # found_hosts.append(found_host)
+ return found_hosts
+
+ def __sighandler(self, signal, frame):
+ """
+ If we get SIGINT on the CLI, we need to quit all the threads
+ in our process group
+ """
+ import os
+ import signal
+
+ os.killpg(os.getpgid(0), signal.SIGQUIT)
+
+
+[docs]class TaskRunner(threading.Thread):
+ """
+ TaskRunner is responsible for executing a set of tasks for a
+ single host in it's own thread.
+ """
+
+ from taboot.tasks import TaskResult as _TaskResult
+
+ def __init__(self, host, tasks, semaphore, output, fail_event):
+ """
+ :Parameters:
+ - `host`: The host to operate on.
+ - `tasks`: A list of tasks to perform (see :class:`Runner`)
+ - `semaphore`: The :class:`Runner` semaphore to acquire before
+ executing
+ - `output`: A list of outputters to use. (see :class:`Runner`)
+ - `fail_event`: The :class:`Runner` failure event to check before
+ executing. If this event is set when the TaskRunner acquires the
+ semaphore, then the TaskRunner is effectively a no-op.
+ """
+
+ threading.Thread.__init__(self)
+ self._host = host
+ self._tasks = tasks
+ self._semaphore = semaphore
+ self._output = output
+ self._fail_event = fail_event
+ self._state = {}
+
+ def __getitem__(self, key):
+ return self._state[key]
+
+ def __setitem__(self, key, value):
+ self._state[key] = value
+
+[docs] def run(self):
+ """
+ Run the task(s) for the given host. If the fail_event passed
+ from the invoking :class:`Runner` is set, do nothing.
+ """
+
+ self._semaphore.acquire()
+
+ if self._fail_event.isSet():
+ # some other host has bombed
+ self._semaphore.release()
+ return
+
+ try:
+ host_success = True
+ for task in self._tasks:
+ result = self.run_task(task)
+ if not result.success and not result.ignore_errors:
+ host_success = False
+ break
+ except:
+ self._bail_failure()
+ raise
+
+ if not host_success:
+ self._bail_failure()
+ else:
+ self._semaphore.release()
+ return host_success
+
+ def _bail_failure(self):
+ """
+ Die nicely :)
+ """
+
+ self._fail_event.set()
+ self._semaphore.release()
+
+[docs] def run_task(self, task):
+ """
+ Run a single task. Sets task.host and then invokes the run
+ method for the task.
+
+ :Parameters:
+ - `task`: The task to run
+ """
+
+ ignore_errors = False
+ if 'ignore_errors' in task:
+ if task['ignore_errors'] in ('True', 'true', 1):
+ ignore_errors = True
+
+ task = instantiator(task, 'taboot.tasks', host=self._host)
+
+ outputters = []
+ for o in self._output:
+ instance = instantiator(o, 'taboot.output',
+ host=self._host,
+ task=task)
+ outputters.append(instance)
+
+ try:
+ result = task.run(self)
+ except Exception, e:
+ result = self._TaskResult(task, output=repr(e))
+
+ for o in outputters:
+ o.write(result)
+
+ result.ignore_errors = ignore_errors
+ return result
+
+# -*- coding: utf-8 -*-
+# Taboot - Client utility for performing deployments with Func.
+# Copyright © 2009-2011, Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+[docs]class BaseTask(object):
+ """
+ Base Task. All tasks should inherit from this. It does the
+ pretty string representation of a task and allows setting of the
+ host attribute.
+ """
+
+ def __init__(self, *args, **kwargs):
+ self._name = str(type(self))
+ next = self._name.index("'") + 1
+ self._name = self._name[next:self._name.rindex("'")]
+ self._args = args
+ self.host = kwargs['host']
+ self.concurrentFriendly = True
+
+ def __repr__(self):
+ return "%s%s" % (self._name, self._args)
+
+ def _sethost(self, host):
+ self._host = host
+
+ def _gethost(self):
+ if hasattr(self, '_host'):
+ return self._host
+ else:
+ return None
+
+ host = property(_gethost, _sethost)
+
+
+[docs]class FuncTask(BaseTask):
+ """
+ A Func-based task. All tasks that utilize Func should inherit
+ from this.
+ """
+
+ import func.jobthing
+ from taboot.errors import FuncException as _FuncException
+
+ def _func_run(self, func_command, *args):
+ """
+ Execute a command via Func.
+
+ :Paramaters:
+ - `func_command` String representing func command to run
+ (e.g. 'command.run')
+ - `*args` Argument(s) to be used when invoking the func command
+ """
+
+ import time
+ import func.overlord.client
+ try:
+ client = func.overlord.client.Client(self._host, async=True)
+ job_id = reduce(lambda x, y: getattr(x, y),
+ func_command.split('.'),
+ client)(*args)
+ # poll until the job completes
+ (status, result) = (None, None)
+ while status != self.func.jobthing.JOB_ID_FINISHED:
+ (status, result) = client.job_status(job_id)
+ time.sleep(1)
+ result = result[self._host]
+ if result[0] == 'REMOTE_ERROR':
+ raise self._FuncException(result[1:])
+ return (True, result)
+ except Exception, ex:
+ return (False, repr(ex))
+
+[docs] def run(self, runner):
+ """
+ Run the FuncTask.
+
+ :Parameters:
+ - `runner` A :class:`taboot.runner.TaskRunner` instance
+ """
+
+ if not hasattr(self, '_command'):
+ raise Exception("You MUST set self._command when instantiating " +
+ "a subclass of FuncTask!")
+
+ result = self._func_run(self._command, *(self._args))
+
+ if result[0]:
+ # command executed successfully as far as "func success"
+ return self._process_result(result[1])
+ else:
+ return TaskResult(self, success=False, output=result[1])
+
+
+[docs]class FuncErrorTask(FuncTask):
+ """
+ Explicitly cause a func remote error by calling a bad command.
+ Used to verify func exception handling works as expected
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(FuncErrorTask, self).__init__(*args, **kwargs)
+ self._command = 'thiscommand.DoesntExist'
+
+
+[docs]class TaskResult(object):
+ """
+ An encapsulation of the results of a task. This is passed to one
+ or more instances of output classes (derived from BaseOutput) in
+ order to display to the user.
+ """
+
+ def __init__(self, task, success=False, output='', ignore_errors=False):
+ """
+ :Parameters:
+ - `task`: The task object represented by this result
+ - `success`: Whether the task completed successfully or not
+ - `output`: Any text output produced by the task
+ """
+
+ if hasattr(task, 'host'):
+ self._host = task.host
+ self._task = repr(task)
+ self._taskObj = task
+ self._success = success
+ self._output = output
+ self._ignore_errors = ignore_errors
+
+ def _gettask(self):
+ return self._task
+
+ def _gettaskObj(self):
+ return self._taskObj
+
+ def _settask(self, t):
+ self._task = repr(t)
+
+ def _getsuccess(self):
+ return self._success
+
+ def _setsuccess(self, success):
+ self._success = success
+
+ def _getoutput(self):
+ return self._output
+
+ def _setoutput(self, output):
+ self._output = output
+
+ def _getignore_errors(self):
+ return self._ignore_errors
+
+ def _setignore_errors(self, ignore_errors):
+ self._ignore_errors = ignore_errors
+
+ def _gethost(self):
+ return self._host
+
+ task = property(_gettask, _settask)
+ success = property(_getsuccess, _setsuccess)
+ output = property(_getoutput, _setoutput)
+ ignore_errors = property(_getignore_errors, _setignore_errors)
+ host = property(_gethost)
+ taskObj = property(_gettaskObj)
+
+# -*- coding: utf-8 -*-
+# Taboot - Client utility for performing deployments with Func.
+# Copyright © 2009,2010, Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from taboot.tasks import FuncTask, TaskResult
+
+
+[docs]class Run(FuncTask):
+ """
+ Run arbitrary commands via Func. The arguments passed to __init__
+ are used to execute func.overlord.Client.command.run(args).
+
+ :Parameters:
+ - `command`: Command to run on the remote host
+ """
+
+ def __init__(self, command, **kwargs):
+ super(Run, self).__init__(command, **kwargs)
+ self._command = 'command.run'
+
+ def _process_result(self, result):
+ t = TaskResult(self)
+ if result[0] == 0:
+ t.success = True
+ else:
+ t.success = False
+ t.output = result[1]
+ return t
+
+# -*- coding: utf-8 -*-
+# Taboot - Client utility for performing deployments with Func.
+# Copyright © 2009-2011, Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from taboot.tasks import BaseTask, FuncTask, TaskResult
+
+JK_ENABLE = 0
+JK_DISABLE = 1
+JK_STOP = 2
+
+
+class ToggleHost(FuncTask):
+ def __init__(self, action, proxyhost, **kwargs):
+ super(ToggleHost, self).__init__(proxyhost, **kwargs)
+ self._action = action
+ if action == JK_ENABLE:
+ self._command = 'taboot.modjk.enable_host'
+ elif action == JK_DISABLE:
+ self._command = 'taboot.modjk.disable_host'
+ elif action == JK_STOP:
+ self._command = 'taboot.modjk.stop_host'
+ else:
+ raise Exception("Undefined toggle action")
+
+ def _process_result(self, result):
+ t = TaskResult(self)
+ if len(result) > 0:
+ t.success = True
+ if self._action == JK_ENABLE:
+ verb = 'Enabled'
+ elif self._action == JK_DISABLE:
+ verb = 'Disabled'
+ elif self._action == JK_STOP:
+ verb = 'Stopped'
+
+ t.output = "%s AJP on the following balancer/worker " \
+ "pairs:\n" % verb
+ for balancer, worker in result:
+ t.output += "%s: %s\n" % (balancer, worker)
+ else:
+ t.success = False
+ t.output = "Failed to find worker host"
+ return t
+
+
+class JKBaseTask(BaseTask):
+ def __init__(self, proxies, action, **kwargs):
+ super(JKBaseTask, self).__init__(**kwargs)
+ from sys import modules
+ self.proxies = proxies
+ self.jkaction = getattr(modules[self.__module__], "JK_%s" %
+ action.upper())
+
+ def run(self, runner):
+ output = ""
+ success = True
+ for proxy in self.proxies:
+ toggler = ToggleHost(self.jkaction, self._host, host=proxy)
+ result = toggler.run(runner)
+ output += "%s:\n" % proxy
+ output += "%s\n" % result.output
+ if result.success == False:
+ success = False
+ break
+ return TaskResult(self, success=success, output=output)
+
+
+[docs]class OutOfRotation(JKBaseTask):
+ """
+ Remove an AJP node from rotation on a proxy via modjkapi access on
+ the proxy with func.
+
+ :Parameters:
+ - `proxies`: A list of URLs to AJP jkmanage interfaces
+ """
+ def __init__(self, proxies, action="stop", **kwargs):
+ super(OutOfRotation, self).__init__(proxies, action, **kwargs)
+
+
+[docs]class InRotation(JKBaseTask):
+ """
+ Put an AJP node in rotation on a proxy via modjkapi access on
+ the proxy with func.
+
+ :Parameters:
+ - `proxies`: A list of URLs to AJP jkmanage interfaces
+ """
+ def __init__(self, proxies, action="enable", **kwargs):
+ super(InRotation, self).__init__(proxies, action, **kwargs)
+
+# -*- coding: utf-8 -*-
+# Taboot - Client utility for performing deployments with Func.
+# Copyright © 2009-2011, Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from taboot.tasks import BaseTask, TaskResult, FuncTask
+import taboot.errors
+import sys
+
+
+[docs]class NagiosBase(FuncTask):
+ """
+ All subsequent Nagios tasks are subclasses of this.
+
+ Code note: Because a `FuncTask` expects to make connections to
+ `self.host` we need to switch `nagios_url` with `self.host` and
+ pass the original `self.host` as an argument.
+
+ .. versionchanged:: 0.2.14
+
+ The previous version specified the `nagios_url` parameter as a
+ URL. To facilitate transitions we automatically correct URLs
+ into hostnames.
+
+ Previously the `service` key was defined as a scalar, like
+ "HTTP" or "JBOSS". This version accepts that key as a scalar OR
+ as a list and "does the right thing" in each case.
+ """
+
+ def _fix_nagios_url(self, nagios_url):
+ """
+ For backwards compatability we accept a Nagios URL that
+ identifies an HTTP resource.
+
+ This method will take a string like http://foo.com/nagios/cmd.cgi
+ and return just the hostname component ("foo.com").
+ """
+ import re
+ return re.sub(r'(https?://)([^/]+)(.*)', r'\2', nagios_url)
+
+ def _process_result(self, result):
+ t = TaskResult(self)
+
+ if result.startswith("Fail: "):
+ t.success = False
+ else:
+ t.sucess = True
+ t.success = True
+ t.output = result
+ return t
+
+
+[docs]class DisableAlerts(NagiosBase):
+ """
+ Disable alerts for a host on a nagios instance
+ """
+
+ def __init__(self, nagios_url, **kwargs):
+ """
+ :Parameters:
+ - `nagios_url`: Hostname of the Nagios server.
+ """
+ target_host = kwargs['host']
+ kwargs['host'] = self._fix_nagios_url(nagios_url)
+ super(DisableAlerts, self).__init__(target_host, **kwargs)
+ self._command = 'nagios.disable_host_notifications'
+
+
+[docs]class EnableAlerts(NagiosBase):
+ """
+ Enable alerts for a host on a nagios instance
+ """
+
+ def __init__(self, nagios_url, **kwargs):
+ """
+ :Parameters:
+ - `nagios_url`: Hostname of the Nagios server.
+ """
+ target_host = kwargs['host']
+ kwargs['host'] = self._fix_nagios_url(nagios_url)
+ super(EnableAlerts, self).__init__(target_host, **kwargs)
+ self._command = 'nagios.enable_host_notifications'
+
+
+[docs]class ScheduleDowntime(NagiosBase):
+ """
+ Schedule downtime for services on a host in Nagios
+ """
+
+ def __init__(self, nagios_url, service='HOST', minutes=30, **kwargs):
+ """
+ :Parameters:
+ - `nagios_url`: Hostname of the Nagios server.
+ - `service`: Service or list of services to schedule down for.
+ - `minutes`: The number of minutes to schedule downtime
+ for. Default is 30.
+ """
+ import types
+ target_host = kwargs['host']
+ kwargs['host'] = self._fix_nagios_url(nagios_url)
+
+ if isinstance(service, types.StringTypes):
+ service = [service]
+
+ if not isinstance(minutes, types.IntType):
+ if isinstance(minutes, types.FloatType):
+ minutes = int(minutes)
+ else:
+ raise TypeError("Invalid data given for minutes.",
+ "Expecting int type.",
+ "Got '%s'." % minutes)
+
+ super(ScheduleDowntime, self).__init__(target_host, service,
+ minutes, **kwargs)
+
+ if service == 'HOST':
+ self._command = "nagios.schedule_host_downtime"
+ else:
+ self._command = 'nagios.schedule_svc_downtime'
+
+ def _process_result(self, result):
+ t = TaskResult(self)
+ t.success = True
+ for r in result:
+ if r.startswith("Fail: "):
+ t.success = t.success & False
+ else:
+ t.sucess = t.success & True
+ t.output = result
+ return t
+
+
+[docs]class SilenceHost(NagiosBase):
+ """
+ Silence all notifications for a given host
+ """
+
+ def __init__(self, nagios_url, **kwargs):
+ """
+ :Parameters:
+ - `nagios_url`: Hostname of the Nagios server.
+ """
+ target_host = kwargs['host']
+ kwargs['host'] = self._fix_nagios_url(nagios_url)
+ super(SilenceHost, self).__init__(target_host, **kwargs)
+ self._command = 'nagios.silence_host'
+
+ def _process_result(self, result):
+ t = TaskResult(self)
+ t.success = True
+ for r in result:
+ if r.startswith("Fail: "):
+ t.success = t.success & False
+ else:
+ t.sucess = t.success & True
+ t.output = result
+ return t
+
+
+[docs]class UnsilenceHost(NagiosBase):
+ """
+ Unsilence all notifications for a given host
+ """
+
+ def __init__(self, nagios_url, **kwargs):
+ """
+ :Parameters:
+ - `nagios_url`: Hostname of the Nagios server.
+ """
+ target_host = kwargs['host']
+ kwargs['host'] = self._fix_nagios_url(nagios_url)
+ super(UnsilenceHost, self).__init__(target_host, **kwargs)
+ self._command = 'nagios.unsilence_host'
+
+ def _process_result(self, result):
+ t = TaskResult(self)
+ t.success = True
+ for r in result:
+ if r.startswith("Fail: "):
+ t.success = t.success & False
+ else:
+ t.sucess = t.success & True
+ t.output = result
+ return t
+
+# -*- coding: utf-8 -*-
+# Taboot - Client utility for performing deployments with Func.
+# Copyright © 2009, Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from taboot.tasks import BaseTask
+from taboot.tasks import TaskResult
+import time
+
+
+[docs]class PollTask(BaseTask):
+ """
+ PollTask. A task that will poll a particular task until the task
+ succeeds or until max_attempts is reached.
+
+ :Parameters:
+ - `task` The task to poll.
+ - `sleep_interval` The number of seconds to wait before trying
+ the task again.
+ - `max_attempts` The maximum number of attempts that the task
+ should be run.
+ - `fail_task` The task to run when max_attempts has been exhausted.
+ If no fail_task is provided, then a simple TaskResult
+ indicating failure is returned.
+ """
+
+ def __init__(self, task, sleep_interval=5, max_attempts=6,
+ fail_task=None, **kwargs):
+ super(PollTask, self).__init__(**kwargs)
+ self._task = task
+ self._sleep_interval = sleep_interval
+ self._max_attempts = max_attempts
+ self._fail_task = fail_task
+
+ def run(self, runner):
+ for x in range(self._max_attempts):
+ result = runner.run_task(self._task)
+ if result.success:
+ return result
+ time.sleep(self._sleep_interval)
+
+ # exhausted max_attempts
+ if self._fail_task != None:
+ return runner.run_task(self._fail_task)
+ else:
+ # return a "failed" TaskResult, stop executing further tasks
+ return TaskResult(self, success=False,
+ output="Max attempts of %s reached running %s" %
+ (self._max_attempts, repr(self._task)))
+
+# -*- coding: utf-8 -*-
+# Taboot - Client utility for performing deployments with Func.
+# Copyright © 2009-2011, Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from taboot.tasks import command, TaskResult
+import puppet
+
+
+[docs]class PuppetBase(command.Run):
+ """
+ Base class for puppet commands
+ """
+
+ def __init__(self, pcmd, **kwargs):
+ super(PuppetBase, self).__init__(pcmd, **kwargs)
+
+
+[docs]class Run(PuppetBase):
+ """
+ Run 'puppetd --test || true'
+
+ :Optional Parameters:
+ - `server`: Puppetmaster to run against
+ - `noop`: If this should be a noop run (Boolean)
+ - `safe`: Abort if puppet errors (Boolean)
+
+ See also: :py:meth:`taboot.tasks.Puppet.SafeRun`
+ """
+
+ def __init__(self, server="", noop=False, safe=False, **kwargs):
+ pcmd = "puppetd --test --color=false"
+ if server != "":
+ pcmd += " --server=%s" % server
+ if noop == True:
+ pcmd += " --noop"
+ if safe == False:
+ # If safe is False, ignore the return code of the puppet run
+ pcmd += " || true"
+ super(Run, self).__init__(pcmd, **kwargs)
+
+ def run(self, runner):
+ result = super(Run, self).run(runner)
+ return PuppetTaskResult(result.taskObj, result.success,
+ result.output, result.ignore_errors)
+
+
+[docs]class SafeRun(puppet.Run):
+ """
+ Run 'puppetd --test'.
+
+ How is this different from Run? Simple, it will abort everything
+ if puppet returns with a non-zero exit status.
+ """
+
+ def __init__(self, server="", **kwargs):
+ super(SafeRun, self).__init__(server, safe=True, **kwargs)
+
+
+[docs]class Enable(PuppetBase):
+ """
+ Run 'puppetd --enable'.
+ """
+
+ def __init__(self, **kwargs):
+ super(Enable, self).__init__('puppetd --enable', **kwargs)
+
+
+[docs]class Disable(PuppetBase):
+ """
+ Run 'puppetd --disable'.
+ """
+
+ def __init__(self, **kwargs):
+ super(Disable, self).__init__('puppetd --disable', **kwargs)
+
+
+[docs]class DeleteLockfile(PuppetBase):
+ """
+ Remove the puppet lock file.
+ """
+
+ def __init__(self, **kwargs):
+ PUPPET_LOCKFILE = "/var/lib/puppet/state/puppetdlock"
+ super(DeleteLockfile, self).__init__("rm -f %s" % PUPPET_LOCKFILE,
+ **kwargs)
+
+
+[docs]class PuppetTaskResult(TaskResult):
+ """
+ Wrapper around TaskResult to be able to differentiate in output class
+ """
+
+ def __init__(self, task, success=False, output='', ignore_errors=False):
+ super(PuppetTaskResult, self).__init__(task, success, output,
+ ignore_errors)
+
+# -*- coding: utf-8 -*-
+# Taboot - Client utility for performing deployments with Func.
+# Copyright © 2009,2010, Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from taboot.tasks import command, TaskResult
+
+
+[docs]class RPMBase(command.Run):
+ """
+ Base class for rpm commands
+ """
+
+ def __init__(self, pcmd, **kwargs):
+ super(RPMBase, self).__init__(pcmd, **kwargs)
+
+
+[docs]class PreManifest(command.Run):
+ """
+ Gather list of installed RPMs. A later invocation of :class:`PostManifest`
+ is then used to output the RPMs changed during intermediate tasks.
+ """
+
+ def __init__(self, **kwargs):
+ super(PreManifest, self).__init__('rpm -qa | sort', **kwargs)
+
+[docs] def run(self, runner):
+ """
+ Override the default :class:`command.Run` to strip the output
+ from the result because we're really not interested in the
+ contents of the pre-manifest; we just want to collect it to
+ compare later on with PostManifest.
+ """
+
+ result = super(PreManifest, self).run(runner)
+ runner['rpm.PreManifest'] = result.output
+ result.output = ''
+ return result
+
+
+[docs]class PostManifest(command.Run):
+ """
+ Gather list of installed RPMs and compare against a previously
+ taken :class:`PreManifest`
+ """
+
+ from difflib import Differ as _Differ
+
+ def __init__(self, **kwargs):
+ super(PostManifest, self).__init__('rpm -qa | sort', **kwargs)
+
+[docs] def run(self, runner):
+ """
+ The runner that gets passed in contains state that can be
+ access via dict-like access. PreManifest uses this to write
+ to the rpm.Premanifest field. So we'll check to make sure the
+ pre-manifest is there by looking for that state.
+ """
+ try:
+ pre_manifest = runner['rpm.PreManifest']
+ except:
+ return TaskResult(self, success=False,
+ output="You must use PreManifest before PostManifest")
+
+ # ok, so now we have something to compare against so we get
+ # new state...
+ result = super(command.Run, self).run(runner)
+
+ old_list = pre_manifest.splitlines(1)
+ new_list = result.output.splitlines(1)
+
+ differ = self._Differ()
+ diff_output = list(differ.compare(old_list, new_list))
+ diff_output = [line for line in diff_output if line[0] in ('+', '-')]
+
+ result.output = ''.join(diff_output)
+
+ return RPMTaskResult(result.taskObj, result.success,
+ result.output, result.ignore_errors)
+
+
+[docs]class RPMTaskResult(TaskResult):
+ """
+ Wrapper around TaskResult to be able to differentiate in output class
+ """
+
+ def __init__(self, task, success=False, output='', ignore_errors=False):
+ super(RPMTaskResult, self).__init__(task, success, output,
+ ignore_errors)
+
+# -*- coding: utf-8 -*-
+# Taboot - Client utility for performing deployments with Func.
+# Copyright © 2009,2011, Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from taboot.tasks import command
+
+
+
+[docs]class Start(ServiceBase):
+ """
+ Start a service.
+
+ Arguments:
+ - ``service`` - The service to start.
+ """
+
+ def __init__(self, service, **kwargs):
+ super(Start, self).__init__('service %s start' % service, **kwargs)
+
+
+[docs]class Stop(ServiceBase):
+ """
+ Stop a service.
+
+ Arguments:
+ - ``service`` - The service to stop.
+ """
+
+ def __init__(self, service, **kwargs):
+ super(Stop, self).__init__('service %s stop' % service, **kwargs)
+
+
+[docs]class Restart(ServiceBase):
+ """
+ Restart a service.
+
+ Arguments:
+ - ``service`` - The service to restart.
+ """
+
+ def __init__(self, service, **kwargs):
+ super(Restart, self).__init__('service %s restart' % service, **kwargs)
+
+# -*- coding: utf-8 -*-
+# Taboot - Client utility for performing deployments with Func.
+# Copyright © 2011, Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from taboot.tasks import BaseTask, TaskResult
+from termios import tcflush, TCIFLUSH
+import sys
+
+
+[docs]class SleepBase(BaseTask):
+ """
+ Base class for task-queue pausing classes.
+ """
+
+ def __init__(self, **kwargs):
+ super(SleepBase, self).__init__(**kwargs)
+
+
+[docs]class Seconds(SleepBase):
+ """
+ Halt task processing on a node for a certain number of seconds.
+
+ :Parameters:
+ - `seconds`: Number of seconds to halt execution for.
+ """
+
+ def __init__(self, seconds=60, **kwargs):
+ super(Seconds, self).__init__(**kwargs)
+ self._seconds = seconds
+
+ def run(self, runner):
+ import time
+ time.sleep(self._seconds)
+ return TaskResult(self, success=True,
+ output="Paused for %s seconds" %
+ self._seconds)
+
+
+[docs]class Minutes(SleepBase):
+ """
+ Halt task processing on a node for a certain number of minutes.
+
+ :Parameters:
+ - `minutes`: Number of minutes to halt execution for.
+ """
+
+ def __init__(self, minutes=1, **kwargs):
+ super(Minutes, self).__init__(**kwargs)
+ self._minutes = minutes
+ self._seconds = minutes * 60
+
+ def run(self, runner):
+ import time
+ time.sleep(self._seconds)
+ return TaskResult(self, success=True,
+ output="Paused for %s minutes" %
+ self._minutes)
+
+
+[docs]class WaitOnInput(SleepBase):
+ """
+ Halt task processing on a node until the user presses enter.
+
+ :Parameters:
+ - `message`: The message to prompt on the CLI.
+ """
+
+ def __init__(self, message="Press enter to continue\n", **kwargs):
+ super(WaitOnInput, self).__init__(**kwargs)
+ self._message = message
+ self.concurrentFriendly = False
+
+ def run(self, runner):
+ import time
+ start = time.time()
+ tcflush(sys.stdin, TCIFLUSH)
+ raw_input(self._message)
+ return TaskResult(self, success=True,
+ output="Paused for %s seconds" %
+ (time.time() - start))
+
+# -*- coding: utf-8 -*-
+# Taboot - Client utility for performing deployments with Func.
+# Copyright © 2009, Red Hat, Inc.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from taboot.tasks import command, BaseTask
+
+
+[docs]class YumBase(BaseTask):
+ """
+ Base class for all Yum-related tasks.
+ """
+
+ def __init__(self, packages):
+ if isinstance(packages, list):
+ self._packages = packages
+ self._packages_str = ' '.join(packages)
+ else:
+ self._packages = [packages]
+ self._packages_str = packages
+
+
+[docs]class Install(YumBase, command.Run):
+ """
+ Install one or more packages.
+ """
+
+ def __init__(self, packages, **kwargs):
+ """
+ :Parameters:
+ - `packages`: A list of packages to install
+ """
+ YumBase.__init__(self, packages)
+ command.Run.__init__(self, 'yum install -y %s' % self._packages_str,
+ **kwargs)
+
+
+[docs]class Update(YumBase, command.Run):
+ """
+ Update one or more packages.
+ """
+
+ def __init__(self, packages=[], **kwargs):
+ """
+ :Parameters:
+ - `packages`: A list of packages to update. If `packages` is empty,
+ update all packages on the system.
+ """
+ YumBase.__init__(self, packages)
+ command.Run.__init__(self, 'yum update -y %s' % self._packages_str,
+ **kwargs)
+
+
+[docs]class Remove(YumBase, command.Run):
+ """
+ Remove one or more packages.
+ """
+
+ def __init__(self, packages, **kwargs):
+ """
+ :Parameters:
+ - `packages`: A list of packages to remove.
+ """
+ YumBase.__init__(self, packages)
+ command.Run.__init__(self, 'yum remove -y %s' % self._packages_str,
+ **kwargs)
+
=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= +h[3];l=0;for(m=h.length;l =0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l ";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& +q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: +function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q =0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f
0)for(var j=d;j 0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= +{},i;if(f&&a.length){e=0;for(var o=a.length;e -1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"+d+">"},F={option:[1,""],legend:[1,""],thead:[1," ","
"],tr:[2,"","
"],td:[3,""],col:[2,"
"," "],area:[1,""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
"," ",""];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= +c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, +prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, +this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); +return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, +""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); +return this}else{e=0;for(var j=d.length;e 0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", +""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===" "&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= +c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? +c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= +function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= +Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, +"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= +a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= +a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/ + + + + + + + + +
+ +++ + + +++ + ++++ ++++ + +API¶
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. In dignissim +placerat nibh, non feugiat risus varius vitae. Donec eu libero +lectus. Ut non orci felis, eget mattis mauris. Etiam ut tellus in +magna porta venenatis. Quisque scelerisque, sem non ultrices bibendum, +dolor diam rutrum lectus, sed luctus neque neque vitae eros. Vivamus +mattis, ipsum ut bibendum gravida, lectus arcu venenatis elit, vitae +luctus diam leo sit amet ligula. Nunc egestas justo in nulla sagittis +ut suscipit sapien gravida. Morbi id dui nibh. Nullam diam massa, +rhoncus a dignissim non, adipiscing vel arcu. Quisque ultricies +tincidunt purus ut sodales. Quisque scelerisque dapibus purus quis +egestas. Maecenas sagittis porttitor adipiscing. Duis eu magna +sem. Donec arcu felis, faucibus et malesuada non, blandit vitae +metus. Fusce nec sapien dolor.
+Aenean ac fermentum nisl. Integer leo sem, rutrum nec dictum at, +pretium quis sapien. Duis felis metus, sodales sit amet gravida in, +pretium ut arcu. Nulla ligula quam, aliquam sit amet sollicitudin +eget, molestie tincidunt ipsum. Nulla leo nunc, mattis sed auctor at, +suscipit ut metus. Suspendisse hendrerit, justo sagittis malesuada +molestie, nisi nunc placerat libero, vel vulputate elit tellus et +augue. Phasellus tempor lectus ac nisi aliquam faucibus. Donec feugiat +egestas nibh id mattis. In hac habitasse platea dictumst. Ut accumsan +lorem eget leo dictum viverra.
+Quisque egestas lorem sit amet felis tincidunt adipiscing. Aenean +ornare fermentum accumsan. Aenean eu mauris arcu, id pulvinar +quam. Suspendisse nec massa vel augue laoreet ultricies in convallis +dolor. Mauris sodales porta enim, non ultricies dolor luctus +in. Phasellus eu tortor lectus, vel porttitor nulla. Mauris vulputate, +erat id scelerisque lobortis, nibh ipsum tristique elit, ac viverra +arcu sem a ante. Praesent nec metus vestibulum augue eleifend +suscipit. In feugiat, sem nec dignissim consequat, velit tortor +scelerisque metus, sit amet mollis nisl sem eu nibh. Quisque in nibh +turpis. Proin ac nisi ligula, a pretium augue.
+In nibh eros, laoreet id interdum vel, sodales sed tortor. Sed +ullamcorper, sem vel mattis consectetur, nibh turpis molestie nisl, +eget lobortis mi magna sed metus. Cras justo est, tempus quis +adipiscing ut, hendrerit convallis sem. Mauris ullamcorper, sapien et +luctus iaculis, urna elit egestas ipsum, et tristique enim risus vitae +nunc. Vivamus aliquet lorem eu urna pulvinar hendrerit malesuada nunc +sollicitudin. Cras in mi rhoncus quam egestas dignissim vel sit amet +lacus. Maecenas interdum viverra laoreet. Quisque elementum +sollicitudin ullamcorper.
+Pellentesque mauris sem, malesuada at lobortis in, porta eget +urna. Duis aliquet quam eget risus elementum quis auctor ligula +gravida. Phasellus et ullamcorper libero. Nam elementum ultricies +tellus, in sagittis magna aliquet quis. Ut sit amet tellus id erat +tristique lobortis. Suspendisse est enim, tristique eu convallis id, +rutrum nec lacus. Fusce iaculis diam non felis rutrum lobortis. Proin +hendrerit mi tincidunt dui fermentum placerat.
+