Source code for taboot.output

# -*- 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()