#!/usr/bin/python
#    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/>.

""" cas-admin - fingerprinting utility for cas
"""
import os
import ConfigParser
import optparse
import sys
import urlparse
import datetime
import calendar

from cas.core import CoreBase
from cas.util import UtilBase, Logging
from cas.rpmutils import RPMBase
from cas.db import CasStorage, CasStorageException
from cas.cas_subprocess import Popen, PIPE
from cas.cas_shutil import rmtree

if sys.version_info[:2] < (2,3):
    raise SystemExit("Python >= 2.3 required")

# Read in configuration
config = ConfigParser.ConfigParser()
config.read("/etc/cas.conf")
KERNELS = config.get("settings","kernels")
RPMFILTER = config.get("settings","rpmFilter")
DEBUGS = config.get("settings","debugs")
DEBUGLEVEL = config.get("settings","debugLevel")
DATABASE = config.get("settings","database")
WORKDIRECTORY = config.get("settings","workDirectory")

# read maintenance options
PURGELIMIT = config.get("maintenance","purgeLimit")
AUTOPURGE = config.get("maintenance","autoPurge")

class CasDatabaseHandler(object):
    def __init__(self, logger):
        self.casLog = logger
        self.util = UtilBase()
        # setup database connection
        self.first_run = False
        if not os.path.isfile(DATABASE):
            self.first_run = True
        self.db = CasStorage(DATABASE)
        self.db.connect()
        if self.first_run:
            self.db.buildTable()

    def run(self):
        # Uses emacs regex -- see `man find`
        cmd = ["find", "-L", KERNELS, "-iregex", RPMFILTER]
        pipe = Popen(cmd, stdout=PIPE, stderr=PIPE)
        # setup count for kernels found, mainly for console output
        count = 0
        # create list of rpms from `cmd`
        for line in pipe.stdout:
            self.db.addDebuginfoRPM(line.strip())
            self.casLog.status("(found) %-5d kernel(s)" % (count,))
            count = count + 1
        # query database for debuginfo rpms
        rpms = self.db.getAllDebuginfoRPM()
        for id, rpm in rpms:
            # temporary storage path in form of DEBUGS/COUNT
            dst = os.path.join(DEBUGS, str(count))
            if not os.path.isdir(dst):
                os.makedirs(dst)
            rpmTool = RPMBase()
            self.casLog.status("(extracting) %-50s" % (os.path.basename(rpm),))
            results = rpmTool.extract(rpm, dst)
            # Sort through extracted debug for each type
            # e.g. hugemem, PAE, smp, largesmp
            for item in results:
                vmlinux = item.strip()
                stamper = CoreBase()
                debugKernel = os.path.normpath(vmlinux)
                timestamp = stamper.timestamp(debugKernel)
                # add rpm id, debug, timestamp to database
                self.db.addTimestamp(id, debugKernel, timestamp)
            # Cleanup extracted debugs
            rmtree(dst)
        return

class CasServerHandler(object):
    def __init__(self, logger):
        self.casLog = logger
        self.util = UtilBase()
        # setup database connection
        self.first_run = False
        if not os.path.isfile(DATABASE):
            self.first_run = True
        self.db = CasStorage(DATABASE)
        self.db.connect()
        if self.first_run:
            self.db.buildTable()

    def run(self):
        try:
            serverList = {}
            hostname_count = 0
            # Attempt to load func to automate detection of machine arch
            import func.overlord.client as fc
            parent_func = fc.Overlord("*")
            minions = parent_func.minions
            # DONE: add only servers that respond, purge the rest
            for i in minions:
                scheme, netloc, path, query, frag = urlparse.urlsplit(i)
                hostname, port = netloc.split(":")
                client = fc.Overlord(hostname)
                isClientReachable = client.run("test","ping", [])
                if isClientReachable == 1:
                    # Returns as {"hostname":['sts','stdout','stderr']}
                    # what a pain
                    client_dict = client.command.run("/bin/uname -m")
                    # silly magic to get output from command module
                    # since we are doing one host at a time we just pull the first
                    # key from the dict and parse its return
                    sts, arch, err = client_dict[client_dict.keys()[0]]
                    if sts:
                        self.casLog.debug(err)
                    # clean up arch string
                    arch = arch.strip()
                    self.db.addServer(hostname, arch)
                    hostname_count = hostname_count + 1
            self.casLog.info("Server database built with %d server(s) added." % (hostname_count,))
        except ImportError:
            raise SystemExit(self.casLog.debug("Please install func (http://fedorahosted.org/func) for " \
                   "an automated machine arch population.\n"))
        return
        
class PurgeHandler(object):
    def __init__(self, purgeDays, logger):
        self.purgeDataDays = purgeDays
        self.casLog = logger
        self.util = UtilBase()
        self.todaysDate = datetime.date.today()
        
    def run(self):
        # create date of timedelta
        cutOffDate = self.todaysDate - datetime.timedelta(days=self.purgeDataDays)
        self.casLog.debug(cutOffDate)
        # Start of purging data
        for root, dirs, files in os.walk(WORKDIRECTORY):
            for d in dirs:
                dirpath = os.path.join(root,d)
                # pull out date from directory structure and trim it to
                # (year, month, day)
                searchDate = self.util.regexSearch('(\d{4})\.(\d+)\.(\d+)', dirpath)
                if searchDate:
                    self.casLog.debug('found %s' % (searchDate,))
                    (year, month, day) = searchDate.split('.')
                    # create our datetime object so we can do some arithmetic
                    dirDate = datetime.date(int(year), int(month), int(day))
                    self.casLog.debug(dirDate)
                    if dirDate < cutOffDate:
                        self.casLog.debug('Should purge old directories, %s' % (dirpath,))
                        rmtree(dirpath)

class CasAdminApplication(object):
    def __init__(self, args):
        self.parse_options(args)
        self.casLog = Logging("/var/log","cas-admin", DEBUGLEVEL)

    def parse_options(self, args):
        parser = optparse.OptionParser(usage="cas-admin [opts] args")
        parser.add_option("-b","--build", dest="buildDB",
                          help="Build CAS DB", action="store_true",
                          default=False)
        parser.add_option("-s","--server", dest="server_init",
                          help="Build SERVER DB", action="store_true",
                          default=False)
        parser.add_option("-p","--purge", dest="purgeData",
                          help="Purge files default 90 days, customize with -d",
                          action="store_true", default=False)
        parser.add_option("-d","--days", dest="purgeDataDays",
                          help="Set how many days back to purge data")
        (self.opts, args) = parser.parse_args()
        self.buildDB = self.opts.buildDB
        self.server_init = self.opts.server_init
        self.purgeData = self.opts.purgeData
        self.purgeDataDays = self.opts.purgeDataDays

    def run(self):
        """ Make sure necessary directories and configuration is setup
        prior to running the fingerprint
        """
        if os.getuid() is not 0:
            raise RuntimeError, "You must be root(0), instead you are id(%d)" % (os.getuid())
        if not os.path.isdir(os.path.dirname(DATABASE)):
            os.makedirs(os.path.dirname(DATABASE))
        if not os.path.isdir(DEBUGS):
            os.makedirs(DEBUGS)

        # if autopurge is enabled lets clean up some stale data
        if AUTOPURGE == 'yes' or AUTOPURGE == 'y':
            self.casLog.debug('Autopurge enabled, begin cleanup')
            purgeHandler = PurgeHandler(int(PURGELIMIT), self.casLog).run()

        if self.purgeData:
            ans = raw_input("You are about to purge data, is this what you " \
                                "really want to do? [Y/y/N/n]: ")
            if ans=='Y' or ans=='y':
                if not self.purgeDataDays:
                    self.purgeDataDays = PURGELIMIT
                self.casLog.info("Beginning Purge going back %s day(s)" % (self.purgeDataDays,))
                purgeHandler = PurgeHandler(int(self.purgeDataDays), self.casLog).run()
                raise SystemExit(self.casLog.info("Purge finished"))
            else:
                raise SystemExit(self.casLog.info("Purge cancelled"))

        if self.buildDB:
            self.casLog.info("Starting CAS DB instance.")
            dbHandler = CasDatabaseHandler(self.casLog).run()
        elif self.server_init:
            self.casLog.info("Building CAS Server DB instance.")
            serverHandler = CasServerHandler(self.casLog).run()
        else:
            raise SystemExit(self.casLog.info("Missing options, please run with --help."))

if __name__=="__main__":
    app = CasAdminApplication(sys.argv[1:])
    sys.exit(app.run())

