Difference between revisions of "Pidora-Update-Source"

From CDOT Wiki
Jump to: navigation, search
(Created page with '{{Admon/important| Warning! | This script has barely been tested as of this posting. May be riddled with bugs or may not function as expected.}} = Pidora Update Source = * This…')
 
(Pidora Update Source)
Line 39: Line 39:
 
import random
 
import random
 
import re
 
import re
 +
import string
 +
import subprocess
 +
import os
  
 
class tools:
 
class tools:
 
     def __init__(self):
 
     def __init__(self):
 
         # Default configuration values
 
         # Default configuration values
         sigulhost = "england"
+
         self.sigulhost = "england.proximity.on.ca"
         mashhost = "japan"
+
         self.mashhost = "japan.proximity.on.ca"
         rsynchost = "pidora.ca"
+
         self.rsynchost = "pidora.proximity.on.ca"
         siguluser = "agreene"
+
         self.siguluser = "agreene"
         mashuser = "root"
+
         self.mashuser = "root"
         rsyncuser = "pidorapr"
+
         self.rsyncuser = "pidorapr"
         mashdir = "/usr/local/bin/mash-pidora"
+
         self.mashdir = "/usr/local/bin/mash-pidora"
         kojitags = ['f18-updates', 'f18-rpfr-updates', 'f18-updates-testing', 'f18-rpfr-updates-testing']
+
         self.kojitags = ['f18-updates', 'f18-rpfr-updates', 'f18-updates-testing', 'f18-rpfr-updates-testing']
 +
        self.email = "andrew.oatley-willis@senecacollege.ca"
 +
        self.auto = False
 +
        self.logdir = "/var/log/pidora-smr/"
 +
        self.logfile = "output"
  
 
         # Create command line options
 
         # Create command line options
Line 56: Line 63:
 
         parser = optparse.OptionParser(usage='Usage: %prog [options]')
 
         parser = optparse.OptionParser(usage='Usage: %prog [options]')
 
         parser.add_option('-i', '--info',  help='check machine status and configuration', dest='status', default=False, action='store_true')
 
         parser.add_option('-i', '--info',  help='check machine status and configuration', dest='status', default=False, action='store_true')
         parser.add_option('-a', '--all',  help='sign, mash, rsync', dest='all', default=False, action='store_true')
+
         parser.add_option('-a', '--all',  help='sign, mash, rsync', dest='everything', default=False, action='store_true')
 
         parser.add_option('-s', '--sign',  help='sign all packages in listed tag', dest='sign', default=False, action='store_true')
 
         parser.add_option('-s', '--sign',  help='sign all packages in listed tag', dest='sign', default=False, action='store_true')
 
         parser.add_option('-m', '--mash',  help='start a mash run', dest='mash', default=False, action='store_true')
 
         parser.add_option('-m', '--mash',  help='start a mash run', dest='mash', default=False, action='store_true')
         parser.add_option('-r', '--rync',  help='perform a rsync of the mash repos', dest='rsync', default=False, action='store_true')
+
         parser.add_option('-r', '--rsync',  help='perform a rsync of the mash repos', dest='rsync', default=False, action='store_true')
 
         parser.add_option('-l', '--list-unsigned',  help='list unsigned rpms', dest='listunsigned', default=False, action='store_true')
 
         parser.add_option('-l', '--list-unsigned',  help='list unsigned rpms', dest='listunsigned', default=False, action='store_true')
 +
        parser.add_option('--auto',  help='enables logging and emails logs', dest='auto', default=self.auto, action='store_true')
 
         parser.add_option('--koji-tag',  help='specify the koji tag to sign', dest='kojitag', default=False, action='store')
 
         parser.add_option('--koji-tag',  help='specify the koji tag to sign', dest='kojitag', default=False, action='store')
         parser.add_option('--sigul-user',  help='specify the user for sigul', dest='siguluser', default=siguluser, action='store', metavar=siguluser)
+
        parser.add_option('--email',  help='specify the email to send logs to', dest='email', default=False, action='store', metavar=self.email)
         parser.add_option('--sigul-host',  help='specify the host for sigul', dest='sigulhost', default=sigulhost, action='store', metavar=sigulhost)
+
         parser.add_option('--sigul-user',  help='specify the user for sigul', dest='siguluser', default=self.siguluser, action='store', metavar=self.siguluser)
         parser.add_option('--mash-user',  help='specify the user for mash', dest='mashuser', default=mashuser, action='store', metavar=mashuser)
+
         parser.add_option('--sigul-host',  help='specify the host for sigul', dest='sigulhost', default=self.sigulhost, action='store', metavar=self.sigulhost)
         parser.add_option('--mash-host',  help='specify the host for mash', dest='mashhost', default=mashhost, action='store', metavar=mashhost)
+
         parser.add_option('--mash-user',  help='specify the user for mash', dest='mashuser', default=self.mashuser, action='store', metavar=self.mashuser)
         parser.add_option('--rsync-user',  help='specify the user for rsync', dest='rsyncuser', default=rsyncuser, action='store', metavar=rsyncuser)
+
         parser.add_option('--mash-host',  help='specify the host for mash', dest='mashhost', default=self.mashhost, action='store', metavar=self.mashhost)
         parser.add_option('--rsync-host',  help='specify the host for rsync', dest='rsynchost', default=rsynchost, action='store', metavar=rsynchost)
+
         parser.add_option('--rsync-user',  help='specify the user for rsync', dest='rsyncuser', default=self.rsyncuser, action='store', metavar=self.rsyncuser)
 +
         parser.add_option('--rsync-host',  help='specify the host for rsync', dest='rsynchost', default=self.rsynchost, action='store', metavar=self.rsynchost)
 +
        parser.add_option('--log-dir',  help='specify a logging directory', dest='logdir', default=self.logdir, action='store', metavar=self.logdir)
 +
        parser.add_option('--log-file',  help='specify a log file name', dest='logfile', default=self.logfile, action='store', metavar=self.logfile)
 
         (opts, args) = parser.parse_args()
 
         (opts, args) = parser.parse_args()
  
Line 75: Line 86:
 
             exit(-1)
 
             exit(-1)
 
         if opts.kojitag:
 
         if opts.kojitag:
             kojitags = [opts.kojitag]
+
             self.kojitags = [opts.kojitag]
 
         if opts.sigulhost:
 
         if opts.sigulhost:
             sigulhost = opts.sigulhost
+
             self.sigulhost = opts.sigulhost
 
         if opts.mashhost:
 
         if opts.mashhost:
             mashhost = opts.mashhost
+
             self.mashhost = opts.mashhost
 
         if opts.rsynchost:
 
         if opts.rsynchost:
             rsynchost = opts.rsynchost
+
             self.rsynchost = opts.rsynchost
 
         if opts.siguluser:
 
         if opts.siguluser:
             siguluser = opts.siguluser
+
             self.siguluser = opts.siguluser
 
         if opts.mashuser:
 
         if opts.mashuser:
             mashuser = opts.mashuser
+
             self.mashuser = opts.mashuser
 
         if opts.rsyncuser:
 
         if opts.rsyncuser:
             rsyncuser = opts.rsyncuser
+
             self.rsyncuser = opts.rsyncuser
 +
        if opts.email:
 +
            self.email = opts.email
 +
        if opts.auto:
 +
            self.auto = opts.auto
 +
        if opts.logdir:
 +
            self.logdir = opts.logdir
 +
        if opts.logfile:
 +
            self.logfile = self.logdir + opts.logfile
 +
       
 +
        # Check for a few strange situations with options
 +
        self.signmash = False
 +
        self.signrsync = False
 +
        self.mashrsync = False
 +
        if opts.sign and opts.mash and opts.rsync:
 +
            opts.sign = False
 +
            opts.mash = False
 +
            opts.rsync = False
 +
            opts.everything = True
 +
        elif opts.sign and opts.mash:
 +
            opts.sign = False
 +
            opts.mash = False
 +
            self.signmash = True
 +
        elif opts.sign and opts.rsync:
 +
            opts.sign = False
 +
            opts.rsync = False
 +
            self.signrsync = True
 +
        elif opts.mash and opts.rsync:
 +
            opts.mash = False
 +
            opts.rsync = False
 +
            self.mashrsync = True
  
 
         # Create lists of successful and failed hosts
 
         # Create lists of successful and failed hosts
         mhosts, mfail = self.get_status(mashhost, mashuser)
+
         mhosts, mfail = self.get_status(self.mashhost, self.mashuser)
         shosts, sfail = self.get_status(sigulhost, siguluser)
+
         shosts, sfail = self.get_status(self.sigulhost, self.siguluser)
         rhosts, rfail = self.get_status(rsynchost, rsyncuser)
+
         rhosts, rfail = self.get_status(self.rsynchost, self.rsyncuser)
         hosts = mhosts + shosts + rhosts
+
         self.hosts = mhosts + shosts + rhosts
         fhosts = mfail + sfail + rfail
+
         self.fhosts = mfail + sfail + rfail
 
        
 
        
 
         # Start the main tasks
 
         # Start the main tasks
 
         if opts.status:
 
         if opts.status:
             print '\nsigulhost = ' + sigulhost
+
             print self.info()
            print 'mashhost = ' + mashhost
+
         elif self.sigulhost not in self.hosts: # Check connection with sigul host
            print 'rsynchost = ' + rsynchost
+
             self.email_exit('[Error]\nCannot connect to sigul: failed hosts: \n' + self.info(), subject='pidora-smr - failed', errors=1)
            print 'siguluser = ' + siguluser
 
            print 'mashuser = ' + mashuser
 
            print 'rsyncuser = ' + rsyncuser
 
            print 'mashdir = ' + mashdir
 
            print 'kojitags = ', kojitags
 
            print '\nworking hosts: ', hosts
 
            print 'failed hosts: ', fhosts
 
            print ""
 
            exit(0)
 
         elif sigulhost not in hosts: # Check connection with sigul host
 
             print 'Cannot connect to sigul: failed hosts: ', fhosts
 
            exit(1)
 
 
         elif opts.listunsigned:
 
         elif opts.listunsigned:
             for tag in kojitags:
+
             print 'Unsigned packages: ', self.kojitags
                print 'Unsigned packages: ' + tag
+
            self.checksign()
                self.checksign(sigulhost, siguluser, tag)
 
 
             exit(0)
 
             exit(0)
 +
        elif not opts.sign and not opts.mash and not opts.rsync and not opts.everything:
 +
            parser.print_help()
 +
            exit(-1)
 
         elif opts.sign:
 
         elif opts.sign:
             self.run_sign(sigulhost, siguluser, kojitags)
+
             self.sign()
 +
            self.email_exit('[Success]\nSign for pidora complete', subject='pidora-smr - success')
 +
        elif self.mashhost not in self.hosts: # Check connection with mash host
 +
            self.email_exit('[Error]\nCannot connect to mash: failed hosts: \n' + self.info(), subject='pidora-smr - failed', errors=1)
 +
        elif self.checksign():
 +
            print 'Unsigned packages: ', self.kojitags
 +
            self.checksign()
 +
            print 'Cannot mash or rsync if packages are not signed'
 
             exit(0)
 
             exit(0)
        elif mashhost not in hosts: # Check connection with mash host
 
            print 'Cannot connect to mash hosts: failed hosts: ', fhosts
 
            exit(1)
 
 
         elif opts.mash:
 
         elif opts.mash:
             self.run_mash(mashhost, mashuser, kojitags, sigulhost, siguluser)
+
             self.mash()
             exit(0)
+
             self.email_exit('[Success]\nMash for pidora complete', subject='pidora-smr - success')
         elif rsynchost not in hosts: # Check connection with rsync host
+
         elif self.rsynchost not in self.hosts: # Check connection with rsync host
             print 'Cannot connect to rsync hosts: failed hosts: ', fhosts
+
             self.email_exit('[Error]\nCannot connect to rsync: failed hosts: \n' + self.info(), subject='pidora-smr - failed', errors=1)
            exit(1)
 
 
         elif opts.rsync:
 
         elif opts.rsync:
             for tag in kojitags:
+
             self.rsync()
                if self.checksign(sigulhost, siguluser, tag):
+
            self.email_exit('[Success]\nRsync for pidora complete', subject='pidora-smr - success')
                    print 'Unsigned packages in: ' + tag
 
                    print 'Run script with options: --list-unsigned to see unsigned packages'
 
                    print '== Exiting due to unsigned packages =='
 
                    exit(1)
 
            self.checkmash(mashhost, mashuser)
 
            self.rsync(rsynchost, rsyncuser)
 
            exit(0)
 
 
         elif opts.everything:
 
         elif opts.everything:
             self.run_sign(sigulhost, siguluser, kojitags)
+
             self.sign()
             self.run_mash(mashhost, mashuser, kojitags, sigulhost, siguluser)
+
            self.mash()
             self.rsync(rsynchost, rsyncuser)
+
            self.rsync()
            exit(0)
+
            self.email_exit('[Success]\nSign, mash, rsync for pidora complete', subject='pidora-smr - success')
   
+
 
     # Call sign over multiple koji tags and ask only once for the password
+
    # Email text and subject, written a little bit crazy...
     def run_sign(self, sigulhost, siguluser, kojitags):
+
    def sendemail(self, subject, text):
         print '\n== Start: Sign run ==\n'
+
        arg = '-s "' + subject + '" "' + self.email + '"'
         print 'Koji tags marked for signing:'
+
        output = subprocess.check_output(['echo "' + str(text) + '" |mail ' + str(arg)], shell=True)
         for tag in kojitags:
+
 
             print tag.strip()
+
    def logging(self, logme):
         print '\nEnter sigul key passphrase:'
+
        try:
         password = getpass.getpass()
+
            os.mkdirs(directory)
         for tag in kojitags:
+
        except OSError: pass
             self.sign(sigulhost, siguluser, tag, password)
+
 
            print ""
+
    # Display all configuration data + hosts status
 +
    def info(self, infotype='all'):
 +
        if infotype == 'all':
 +
            info = ['\n[Connection]\nsigulhost = ' + self.sigulhost,
 +
                    'siguluser = ' + self.siguluser,
 +
                    'mashhost = ' + self.mashhost,
 +
                    'mashuser = ' + self.mashuser,
 +
                    'rsynchost = ' + self.rsynchost,
 +
                    'rsyncuser = ' + self.rsyncuser,
 +
                    '\n[General]\nauto = ' + str(self.auto),
 +
                    'mashdir = ' + self.mashdir,
 +
                    'kojitags = ' + str(self.kojitags),
 +
                    'email = ' + self.email,
 +
                    '\nlogdir = ' + self.logdir,
 +
                    'logfile = ' + self.logfile,
 +
                    '\n[Hosts]\nworking hosts: ' + str(self.hosts),
 +
                    'failed hosts: ' + str(self.fhosts) + '\n']
 +
        elif infotype == 'sign':
 +
             info = ['\n[Connection]\nsigulhost = ' + self.sigulhost,
 +
                    'siguluser = ' + self.siguluser,
 +
                    '\n[General]\nauto = ' + str(self.auto),
 +
                    'kojitags = ' + str(self.kojitags),
 +
                    'logdir = ' + self.logdir,
 +
                    'logfile = ' + self.logfile,
 +
                    '\n[Hosts]\nworking hosts: ' + str(self.hosts),
 +
                    'failed hosts: ' + str(self.fhosts) + '\n']
 +
        elif infotype == 'mash':
 +
            info = ['\n[Connection]\nmashhost = ' + self.mashhost,
 +
                    'mashuser = ' + self.mashuser,
 +
                    '\n[General]\nauto = ' + str(self.auto),
 +
                    'mashdir = ' + self.mashdir,
 +
                    'kojitags = ' + str(self.kojitags),
 +
                    '\nlogdir = ' + self.logdir,
 +
                    'logfile = ' + self.logfile,  
 +
                    '\n[Hosts]\nworking hosts: ' + str(self.hosts),
 +
                    'failed hosts: ' + str(self.fhosts) + '\n']
 +
        elif infotype == 'rsync':
 +
             info = ['\n[Connection]\nrsynchost = ' + self.rsynchost,
 +
                    'rsyncuser = ' + self.rsyncuser,
 +
                    'mashdir = ' + self.mashdir,
 +
                    '\n[General]\nauto = ' + str(self.auto),
 +
                    'kojitags = ' + str(self.kojitags),
 +
                    '\nlogdir = ' + self.logdir,
 +
                    'logfile = ' + self.logfile,
 +
                    '\n[Hosts]\nworking hosts: ' + str(self.hosts),
 +
                    'failed hosts: ' + str(self.fhosts) + '\n']
 +
        return '\n'.join(info)
 +
 
 +
     # Display text and exit or send an email and exit
 +
     def email_exit(self, text, subject=False, errors=0):
 +
        if self.auto and subject:
 +
            self.sendemail(subject, text)
 +
            exit(errors)
 +
        else:
 +
            print text
 +
            exit(errors)
 +
 
 +
    # Rsync to the repo directory
 +
    def rsync(self):
 +
         print '\n== Start: Rsync ==\n'
 +
         self.checkmash()
 +
        srv = pysftp.Connection(host=self.rsynchost, username=self.rsyncuser, log=True)
 +
        output = srv.execute('/home/pidorapr/bin/rsync-japan; echo $? > /home/pidorapr/.rsync-japan-exit-status')
 +
         for line in output:
 +
             print line.strip()
 +
         output = srv.execute('cat /home/pidorapr/.rsync-japan-exit-status')
 +
         srv.close()
 +
         if str(output.strip()) != '0':
 +
             self.email_exit('[Error]\nRsync failed stopping program\nExit status = ' + str(output.strip()) + self.info(), subject='pidora-smr - failed', errors=1)
 +
 
  
    # Call mash and check that all packages are signed
 
    def run_mash(self, mashhost, mashuser, kojitags, sigulhost, siguluser):
 
        print '\n== Start: Mash run ==\n'
 
        for tag in kojitags: # Check for unsigned packages before mashing
 
            if self.checksign(sigulhost, siguluser, tag):
 
                print 'Cannot mash while packages are unsigned: '
 
                self.checksign(sigulhost, siguluser, tag)
 
                exit(1)
 
        self.mash(mashhost, mashuser)
 
  
 
     # Check if hosts are online and can establish connection, return lists of failed and succesful hosts
 
     # Check if hosts are online and can establish connection, return lists of failed and succesful hosts
Line 191: Line 278:
  
 
     # Start a signing run across a designated tag
 
     # Start a signing run across a designated tag
     def sign(self, host, username, tag, password):
+
     def sign(self):
        print "Signing packages in tag: " + tag
+
        print '\n== Start: Sign run ==\n'
        print "Packages found: "
+
        print 'Koji tags marked for signing:'
        print self.checksign(host, username, tag)
+
        for tag in self.kojitags:
        tempfile1 = crypt.crypt(str(random.random()), "pidora" ) + '.log'
+
            print tag.strip()
        tempfile = tempfile1.replace("/", "")
+
        print '\nEnter sigul key passphrase:'
        tempdir = '~/.pidora/'
+
        password = getpass.getpass()
        srv = pysftp.Connection(host=host, username=username, log=True)
+
        for tag in self.kojitags:
        errors = srv.execute('mkdir ' + tempdir + ' 2>/dev/null')
+
            print "Signing packages in tag: " + tag
        errors = srv.execute('touch ' + tempdir + tempfile + '2>/dev/null')
+
            print "Packages found: "
        output = srv.execute('~/.sigul/sigulsign_unsigned.py -v --password=' + password + ' --write-all --tag=' + tag + " pidora-18 2>" + tempdir + tempfile)
+
            print self.checksign()
        errors = srv.execute('cat ' + tempdir + tempfile)
+
            tempfile1 = crypt.crypt(str(random.random()), "pidora" ) + '.log'
        srv.close()
+
            tempfile = tempfile1.replace("/", "")
        # Scan through output and find errors! If errors are found, stop program and spit out error warnings
+
            tempdir = '~/.pidora/'
        outputs = output + errors
+
            srv = pysftp.Connection(host=self.sigulhost, username=self.siguluser, log=True)
        errors = False
+
            errors = srv.execute('mkdir ' + tempdir + ' 2>/dev/null')
        for output in outputs:
+
            errors = srv.execute('touch ' + tempdir + tempfile + '2>/dev/null')
            print output.strip()
+
            output = srv.execute('~/.sigul/sigulsign_unsigned.py -v --password=' + password + ' --write-all --tag=' + tag + " pidora-18 2>" + tempdir + tempfile)
            if re.search('^ERROR:.*$', output):
+
            errors = srv.execute('cat ' + tempdir + tempfile)
                errors = True
+
            srv.close()
        if errors:
+
            # Scan through output and find errors! If errors are found, stop program and spit out error warnings
            print "\n== Error signing stopping program =="
+
            outputs = output + errors
            exit(1)
+
            errors = []
 +
            for output in outputs:
 +
                print output.strip()
 +
                if re.search('^ERROR:.*$', output):
 +
                    errors.append(output)
 +
            if errors:
 +
                self.email_exit('[Error]\nError signing stopping program\n' + str(errors) + self.info(), subject='pidora-smr - failed', errors=1)
  
 
     # Check koji for unsigned packages, returns True if unsigned rpms are found
 
     # Check koji for unsigned packages, returns True if unsigned rpms are found
     def checksign(self, host, username, tag):
+
     def checksign(self):
 
         check = False
 
         check = False
         srv = pysftp.Connection(host=host, username=username, log=True)
+
         for tag in self.kojitags:
        output = srv.execute("~/.sigul/sigulsign_unsigned.py --just-list --tag=" + tag + " pidora-18")
+
            srv = pysftp.Connection(host=self.sigulhost, username=self.siguluser, log=True)
        srv.close()
+
            output = srv.execute("~/.sigul/sigulsign_unsigned.py --just-list --tag=" + tag + " pidora-18")
        for rpm in output:
+
            srv.close()
            print rpm.strip()
+
            for rpm in output:
            if rpm.strip() != "":
+
                print rpm.strip()
                check = "unsigned rpms found"
+
                if rpm.strip() != "":
 +
                    check = "unsigned rpms found"
 
         if check:
 
         if check:
 
             return True
 
             return True
  
 
     # Run mash and search through the log file for failed mash errors
 
     # Run mash and search through the log file for failed mash errors
     def mash(self, host, username):
+
     def mash(self):
         srv = pysftp.Connection(host=host, username=username, log=True)
+
        print '\n== Start: Mash run ==\n'
 +
         srv = pysftp.Connection(host=self.mashhost, username=self.mashuser, log=True)
 
         output = srv.execute('/usr/local/bin/mashrun-pidora-18')
 
         output = srv.execute('/usr/local/bin/mashrun-pidora-18')
 
         srv.close()
 
         srv.close()
         self.checkmash(host, username)
+
         self.checkmash()
  
     def checkmash(self, host, username):
+
     def checkmash(self):
         errors = False
+
         errors = []
        errorline = []
+
         srv = pysftp.Connection(host=self.mashhost, username=self.mashuser, log=True)
         srv = pysftp.Connection(host=host, username=username, log=True)
+
         output = srv.execute('cat /mnt/koji/mash/pidora-mash-latest/mash.log')
         output = srv.execute('cat /mnt/koji/mash/pidora-mash-latest')
 
 
         srv.close()
 
         srv.close()
 
         for line in output:
 
         for line in output:
 
             if re.search('^mash failed .*$', line):
 
             if re.search('^mash failed .*$', line):
                 errorline.append(line.strip())
+
                 errors.append(line.strip())
                errors = True
 
 
         if errors:
 
         if errors:
             print "\n== mash failed on repo stopping program ==\n"
+
             self.email_exit('[Error]\nmash failed on repo stopping program\n' + str(errors) + self.info(), subject='pidora-smr - failed', errors=1)
            for line in errorline:
+
           
                print line
 
            exit(1)
 
 
          
 
          
    def rsync(self, host, username):
 
        print '\n== Start: Rsync ==\n'
 
        srv = pysftp.Connection(host=host, username=username, log=True)
 
        output = srv.execute('/home/pidorapr/bin/rsync-japan')
 
        srv.close()
 
        for line in output:
 
            print line.strip()
 
  
 
if __name__ == '__main__':
 
if __name__ == '__main__':

Revision as of 08:55, 9 July 2013

Important.png
Warning!
This script has barely been tested as of this posting. May be riddled with bugs or may not function as expected.

Pidora Update Source

  • This script was kinda rushed, probably need to go back over it and fix some things
    • Complete testing of all functions in this script has not been completed
  • To show/track which version is posted here
commit 7e5e19709b8ffb3f06ea7287da5a058445123fd3
Author: Andrew Oatley-Willis <andrew.oatley-willis@senecacollege.ca>
Date:   Fri Jun 28 11:46:44 2013 -0400

    Bugs fixed
     - Stop rsync if packages are unsigned
    
    Added descriptions
     - Added mash, sign, and rsync descriptions
     - Output to show when a process has started and ended
  • Source code of pidora-update.py
#!/usr/bin/env python
# Andrew Oatley-Willis
# Multi-purpose tool written in python for linux
# This script will allow for a single safeguarded process to run: sigul, mash, and rsync to pidora.ca
# It will do a sanity check on everything that is happening and prevent manual errors that could occur
# Every configuration is customizable on the command line with well named options
import optparse
import pysftp
import sys
import urllib2
import getpass
import crypt
import random
import re
import string
import subprocess
import os

class tools:
    def __init__(self):
        # Default configuration values
        self.sigulhost = "england.proximity.on.ca"
        self.mashhost = "japan.proximity.on.ca"
        self.rsynchost = "pidora.proximity.on.ca"
        self.siguluser = "agreene"
        self.mashuser = "root"
        self.rsyncuser = "pidorapr"
        self.mashdir = "/usr/local/bin/mash-pidora"
        self.kojitags = ['f18-updates', 'f18-rpfr-updates', 'f18-updates-testing', 'f18-rpfr-updates-testing']
        self.email = "andrew.oatley-willis@senecacollege.ca"
        self.auto = False
        self.logdir = "/var/log/pidora-smr/"
        self.logfile = "output"

        # Create command line options
        parser = optparse.OptionParser()
        parser = optparse.OptionParser(usage='Usage: %prog [options]')
        parser.add_option('-i', '--info',  help='check machine status and configuration', dest='status', default=False, action='store_true')
        parser.add_option('-a', '--all',  help='sign, mash, rsync', dest='everything', default=False, action='store_true')
        parser.add_option('-s', '--sign',  help='sign all packages in listed tag', dest='sign', default=False, action='store_true')
        parser.add_option('-m', '--mash',  help='start a mash run', dest='mash', default=False, action='store_true')
        parser.add_option('-r', '--rsync',  help='perform a rsync of the mash repos', dest='rsync', default=False, action='store_true')
        parser.add_option('-l', '--list-unsigned',  help='list unsigned rpms', dest='listunsigned', default=False, action='store_true')
        parser.add_option('--auto',  help='enables logging and emails logs', dest='auto', default=self.auto, action='store_true')
        parser.add_option('--koji-tag',  help='specify the koji tag to sign', dest='kojitag', default=False, action='store')
        parser.add_option('--email',  help='specify the email to send logs to', dest='email', default=False, action='store', metavar=self.email)
        parser.add_option('--sigul-user',  help='specify the user for sigul', dest='siguluser', default=self.siguluser, action='store', metavar=self.siguluser)
        parser.add_option('--sigul-host',  help='specify the host for sigul', dest='sigulhost', default=self.sigulhost, action='store', metavar=self.sigulhost)
        parser.add_option('--mash-user',  help='specify the user for mash', dest='mashuser', default=self.mashuser, action='store', metavar=self.mashuser)
        parser.add_option('--mash-host',  help='specify the host for mash', dest='mashhost', default=self.mashhost, action='store', metavar=self.mashhost)
        parser.add_option('--rsync-user',  help='specify the user for rsync', dest='rsyncuser', default=self.rsyncuser, action='store', metavar=self.rsyncuser)
        parser.add_option('--rsync-host',  help='specify the host for rsync', dest='rsynchost', default=self.rsynchost, action='store', metavar=self.rsynchost)
        parser.add_option('--log-dir',  help='specify a logging directory', dest='logdir', default=self.logdir, action='store', metavar=self.logdir)
        parser.add_option('--log-file',  help='specify a log file name', dest='logfile', default=self.logfile, action='store', metavar=self.logfile)
        (opts, args) = parser.parse_args()

        # Check number of arguments and check for option switches
        if len(sys.argv[1:]) == 0:
            parser.print_help()
            exit(-1)
        if opts.kojitag:
            self.kojitags = [opts.kojitag]
        if opts.sigulhost:
            self.sigulhost = opts.sigulhost
        if opts.mashhost:
            self.mashhost = opts.mashhost
        if opts.rsynchost:
            self.rsynchost = opts.rsynchost
        if opts.siguluser:
            self.siguluser = opts.siguluser
        if opts.mashuser:
            self.mashuser = opts.mashuser
        if opts.rsyncuser:
            self.rsyncuser = opts.rsyncuser
        if opts.email:
            self.email = opts.email
        if opts.auto:
            self.auto = opts.auto
        if opts.logdir:
            self.logdir = opts.logdir
        if opts.logfile:
            self.logfile = self.logdir + opts.logfile
        
        # Check for a few strange situations with options
        self.signmash = False
        self.signrsync = False
        self.mashrsync = False
        if opts.sign and opts.mash and opts.rsync:
            opts.sign = False
            opts.mash = False
            opts.rsync = False
            opts.everything = True
        elif opts.sign and opts.mash:
            opts.sign = False
            opts.mash = False
            self.signmash = True
        elif opts.sign and opts.rsync:
            opts.sign = False
            opts.rsync = False
            self.signrsync = True
        elif opts.mash and opts.rsync:
            opts.mash = False
            opts.rsync = False
            self.mashrsync = True

        # Create lists of successful and failed hosts
        mhosts, mfail = self.get_status(self.mashhost, self.mashuser)
        shosts, sfail = self.get_status(self.sigulhost, self.siguluser)
        rhosts, rfail = self.get_status(self.rsynchost, self.rsyncuser)
        self.hosts = mhosts + shosts + rhosts
        self.fhosts = mfail + sfail + rfail
       
        # Start the main tasks
        if opts.status:
            print self.info()
        elif self.sigulhost not in self.hosts: # Check connection with sigul host
            self.email_exit('[Error]\nCannot connect to sigul: failed hosts: \n' + self.info(), subject='pidora-smr - failed', errors=1)
        elif opts.listunsigned:
            print 'Unsigned packages: ', self.kojitags
            self.checksign()
            exit(0)
        elif not opts.sign and not opts.mash and not opts.rsync and not opts.everything:
            parser.print_help()
            exit(-1)
        elif opts.sign:
            self.sign()
            self.email_exit('[Success]\nSign for pidora complete', subject='pidora-smr - success')
        elif self.mashhost not in self.hosts: # Check connection with mash host
            self.email_exit('[Error]\nCannot connect to mash: failed hosts: \n' + self.info(), subject='pidora-smr - failed', errors=1)
        elif self.checksign():
            print 'Unsigned packages: ', self.kojitags
            self.checksign()
            print 'Cannot mash or rsync if packages are not signed'
            exit(0)
        elif opts.mash:
            self.mash()
            self.email_exit('[Success]\nMash for pidora complete', subject='pidora-smr - success')
        elif self.rsynchost not in self.hosts: # Check connection with rsync host
            self.email_exit('[Error]\nCannot connect to rsync: failed hosts: \n' + self.info(), subject='pidora-smr - failed', errors=1)
        elif opts.rsync:
            self.rsync()
            self.email_exit('[Success]\nRsync for pidora complete', subject='pidora-smr - success')
        elif opts.everything:
            self.sign()
            self.mash()
            self.rsync()
            self.email_exit('[Success]\nSign, mash, rsync for pidora complete', subject='pidora-smr - success')
   
    # Email text and subject, written a little bit crazy...
    def sendemail(self, subject, text):
        arg = '-s "' + subject + '" "' + self.email + '"'
        output = subprocess.check_output(['echo "' + str(text) + '" |mail ' + str(arg)], shell=True)

    def logging(self, logme):
        try:
            os.mkdirs(directory)
        except OSError: pass

    # Display all configuration data + hosts status
    def info(self, infotype='all'):
        if infotype == 'all':
            info = ['\n[Connection]\nsigulhost = ' + self.sigulhost,
                    'siguluser = ' + self.siguluser,
                    'mashhost = ' + self.mashhost,
                    'mashuser = ' + self.mashuser,
                    'rsynchost = ' + self.rsynchost,
                    'rsyncuser = ' + self.rsyncuser,
                    '\n[General]\nauto = ' + str(self.auto),
                    'mashdir = ' + self.mashdir,
                    'kojitags = ' + str(self.kojitags),
                    'email = ' + self.email,
                    '\nlogdir = ' + self.logdir, 
                    'logfile = ' + self.logfile, 
                    '\n[Hosts]\nworking hosts: ' + str(self.hosts),
                    'failed hosts: ' + str(self.fhosts) + '\n']
        elif infotype == 'sign':
            info = ['\n[Connection]\nsigulhost = ' + self.sigulhost,
                    'siguluser = ' + self.siguluser,
                    '\n[General]\nauto = ' + str(self.auto),
                    'kojitags = ' + str(self.kojitags),
                    'logdir = ' + self.logdir, 
                    'logfile = ' + self.logfile, 
                    '\n[Hosts]\nworking hosts: ' + str(self.hosts),
                    'failed hosts: ' + str(self.fhosts) + '\n']
        elif infotype == 'mash':
            info = ['\n[Connection]\nmashhost = ' + self.mashhost,
                    'mashuser = ' + self.mashuser,
                    '\n[General]\nauto = ' + str(self.auto),
                    'mashdir = ' + self.mashdir,
                    'kojitags = ' + str(self.kojitags),
                    '\nlogdir = ' + self.logdir, 
                    'logfile = ' + self.logfile, 
                    '\n[Hosts]\nworking hosts: ' + str(self.hosts),
                    'failed hosts: ' + str(self.fhosts) + '\n']
        elif infotype == 'rsync':
            info = ['\n[Connection]\nrsynchost = ' + self.rsynchost,
                    'rsyncuser = ' + self.rsyncuser,
                    'mashdir = ' + self.mashdir,
                    '\n[General]\nauto = ' + str(self.auto),
                    'kojitags = ' + str(self.kojitags),
                    '\nlogdir = ' + self.logdir, 
                    'logfile = ' + self.logfile, 
                    '\n[Hosts]\nworking hosts: ' + str(self.hosts),
                    'failed hosts: ' + str(self.fhosts) + '\n']
        return '\n'.join(info)

    # Display text and exit or send an email and exit
    def email_exit(self, text, subject=False, errors=0):
        if self.auto and subject:
            self.sendemail(subject, text)
            exit(errors)
        else:
            print text
            exit(errors)

    # Rsync to the repo directory
    def rsync(self):
        print '\n== Start: Rsync ==\n'
        self.checkmash()
        srv = pysftp.Connection(host=self.rsynchost, username=self.rsyncuser, log=True)
        output = srv.execute('/home/pidorapr/bin/rsync-japan; echo $? > /home/pidorapr/.rsync-japan-exit-status')
        for line in output:
            print line.strip()
        output = srv.execute('cat /home/pidorapr/.rsync-japan-exit-status')
        srv.close()
        if str(output.strip()) != '0':
            self.email_exit('[Error]\nRsync failed stopping program\nExit status = ' + str(output.strip()) + self.info(), subject='pidora-smr - failed', errors=1)



    # Check if hosts are online and can establish connection, return lists of failed and succesful hosts
    def get_status(self, host, username):
        hostname = []
        fhost = []
        check = self.connect(host, username)
        if check:
            hostname.append(host)
        else:
            fhost.append(host)
        return (hostname, fhost)
    
    # Connect to the hosts, return True or False
    def connect(self, host, username):
        try:
            response=urllib2.urlopen('http://'+host,timeout=1)
            srv = pysftp.Connection(host=host, username=username, log=True)
            srv.close()
            return True
        except urllib2.URLError as err:pass
        except:pass
        return False

    # Start a signing run across a designated tag
    def sign(self):
        print '\n== Start: Sign run ==\n'
        print 'Koji tags marked for signing:'
        for tag in self.kojitags:
            print tag.strip()
        print '\nEnter sigul key passphrase:'
        password = getpass.getpass()
        for tag in self.kojitags:
            print "Signing packages in tag: " + tag
            print "Packages found: "
            print self.checksign()
            tempfile1 = crypt.crypt(str(random.random()), "pidora" ) + '.log'
            tempfile = tempfile1.replace("/", "")
            tempdir = '~/.pidora/'
            srv = pysftp.Connection(host=self.sigulhost, username=self.siguluser, log=True)
            errors = srv.execute('mkdir ' + tempdir + ' 2>/dev/null')
            errors = srv.execute('touch ' + tempdir + tempfile + '2>/dev/null')
            output = srv.execute('~/.sigul/sigulsign_unsigned.py -v --password=' + password + ' --write-all --tag=' + tag + " pidora-18 2>" + tempdir + tempfile)
            errors = srv.execute('cat ' + tempdir + tempfile)
            srv.close()
            # Scan through output and find errors! If errors are found, stop program and spit out error warnings
            outputs = output + errors
            errors = []
            for output in outputs:
                print output.strip()
                if re.search('^ERROR:.*$', output):
                    errors.append(output)
            if errors:
                self.email_exit('[Error]\nError signing stopping program\n' + str(errors) + self.info(), subject='pidora-smr - failed', errors=1)

    # Check koji for unsigned packages, returns True if unsigned rpms are found
    def checksign(self):
        check = False
        for tag in self.kojitags:
            srv = pysftp.Connection(host=self.sigulhost, username=self.siguluser, log=True)
            output = srv.execute("~/.sigul/sigulsign_unsigned.py --just-list --tag=" + tag + " pidora-18")
            srv.close()
            for rpm in output:
                print rpm.strip()
                if rpm.strip() != "":
                    check = "unsigned rpms found"
        if check:
            return True

    # Run mash and search through the log file for failed mash errors
    def mash(self):
        print '\n== Start: Mash run ==\n'
        srv = pysftp.Connection(host=self.mashhost, username=self.mashuser, log=True)
        output = srv.execute('/usr/local/bin/mashrun-pidora-18')
        srv.close()
        self.checkmash()

    def checkmash(self):
        errors = []
        srv = pysftp.Connection(host=self.mashhost, username=self.mashuser, log=True)
        output = srv.execute('cat /mnt/koji/mash/pidora-mash-latest/mash.log')
        srv.close()
        for line in output:
            if re.search('^mash failed .*$', line):
                errors.append(line.strip())
        if errors:
            self.email_exit('[Error]\nmash failed on repo stopping program\n' + str(errors) + self.info(), subject='pidora-smr - failed', errors=1)
            
        

if __name__ == '__main__':
    tools()