def run(self):
        own_lock = False
        build_system_name = self.identifyBuildSystemName()
        self.proclaim_existence(build_system_name)
        own_lock = self.acquireLock()

        try:
            for config_file in self.config_file_names:
                config_file_path = self.find_config_file(config_file)
                if not config_file_path:
                    raise ConfigurationError(
                        "No config file for '%s' found in the config subdir" %
                        config_file)
                lf_name = "log/%s.log" % config_file.replace(
                    '.yml', '').replace('_config', '')
                self.log = ActivityLogger(lf_name)
                logAllExceptions(True, self.log)
                self._operateService(config_file_path)
        except Exception as msg:
            self.log.error(msg)
        finally:
            try:
                if own_lock: self.releaseLock()
            except Exception as msg:
                raise OperationalError(
                    "ERROR: unable to remove lock file '%s', %s" %
                    (LOCK_FILE, msg))
        self.log.info('run completed')
    def __init__(self, args):
        """
            An instance of this class is responsible for basic command line parsing,
            pulling off the name of the target config file.
            Once that item is taken care of, the instance obtains an instance of a
            Konfabulator, a AuxiliaryClassLoader instance and and instance of an BLDConnector.
            The Konfabulator is delegated the task of reading the config file, the AuxiliaryClassLoader
            is delegated the task of pulling in any Extension classes.
            These instances are then provided to the BuildConnector instance.
            This instance then directs the BuildConnector instance to obtain unrecorded builds 
            from the target Build/CI system and reflect them in the Agile Central server.
        """
        if len(args) < 1:
            problem = (
                "Insufficient command line args, must be at least a config file name."
            )
            raise ConfigurationError(problem)

        self.default_log_file_name = True

        # this only allows a logfile spec of --logfile=<some_file_name>
        # TODO: consider allowing additional syntax  logfile=foo.log or --logfile foo.log
        log_spec = [arg for arg in args if arg.startswith('--logfile=')]
        if log_spec:
            self.logfile_name = log_spec[0].replace('--logfile=', '')
            self.default_log_file_name = False
            for spec in log_spec:
                args = [arg for arg in args if arg != spec]

        self.config_file_names = args
        if self.default_log_file_name:  # set it to first config file minus any '_config.yml' portion
            self.first_config = self.config_file_names[0]
            self.logfile_name = "log/%s.log" % self.first_config.replace(
                '.yml', '').replace('_config', '')
            try:
                if not os.path.exists('log'):
                    os.makedirs('log')
            except Exception as msg:
                sys.stderr.write(
                    "Unable to locate or create the log sub-directory, %s\n" %
                    msg)
                raise Exception

        self.log = ActivityLogger(self.logfile_name)
        logAllExceptions(True, self.log)
        self.preview = False
        self.connector = None
        self.extension = {}
Esempio n. 3
0
def test_tab_yml():
    logger = ActivityLogger('log/bad_tab.log')
    expectedErrPattern = "Your config file contains tab characters which are not allowed in a YML file."
    with pytest.raises(Exception) as excinfo:
        konf = Konfabulator('config/bad_tab.yml', logger)
    actualErrVerbiage = excinfo.value.args[0]
    assert re.search(expectedErrPattern, actualErrVerbiage)
Esempio n. 4
0
def test_config_defaults():
    #config_file = ('defaults.yml')
    logger = ActivityLogger('log/defaults.log')
    konf = Konfabulator('config/defaults.yml', logger)
    jenk_conf = konf.topLevel('Jenkins')
    ac_conf = konf.topLevel('AgileCentral')
    srv_config = konf.topLevel('Service')
    assert not ac_conf.get('Server', None)
    assert not jenk_conf.get('Server', None)

    # runner = BuildConnectorRunner([config_file])
    # runner.run()

    agicen = bsh.AgileCentralConnection(konf.topLevel('AgileCentral'), logger)
    agicen.other_name = 'Jenkins'
    agicen.project_name = jenk_conf['AgileCentral_DefaultBuildProject']
    agicen.connect()
    assert agicen.server == 'rally1.rallydev.com'
    assert not agicen.proxy

    jc = bsh.JenkinsConnection(jenk_conf, logger)
    jc.connect()
    assert jc.server == 'coyotepair.ca.com'
    assert jc.port == 8080
    assert jc.protocol == 'http'
Esempio n. 5
0
def setup_test_config(filename,
                      jenkins_structure='DEFAULT_JENKINS_STRUCTURE',
                      services='DEFAULT_SERVICES'):
    filename = inflate_config_file(filename, jenkins_structure, services)
    logger = ActivityLogger('test.log')
    tkonf = TestKonfabulator(filename, logger)
    return logger, tkonf
Esempio n. 6
0
def test_without_project():
    config_file = ('missing-project.yml')
    ymlfile = open("config/{}".format(config_file), 'r')
    y = yaml.load(ymlfile)
    logger = ActivityLogger('log/missing-project.log')
    logger.setLevel('DEBUG')
    logAllExceptions(True, logger)
    konf = Konfabulator('config/missing-project.yml', logger)
    jenk_conf = konf.topLevel('Jenkins')
    ac_conf = konf.topLevel('AgileCentral')
    #expectedErrPattern = 'The Jenkins section of the config is missing AgileCentral_DefaultBuildProject property'
    expectedErrPattern = 'The Jenkins section of the config is missing a value for AgileCentral_DefaultBuildProject property'
    with pytest.raises(Exception) as excinfo:
        bc = bsh.BLDConnector(konf, logger)
    actualErrVerbiage = excinfo.value.args[0]
    assert re.search(expectedErrPattern, actualErrVerbiage) is not None
    assert excinfo.typename == 'ConfigurationError'
Esempio n. 7
0
def test_without_project():
    config_file = ('missing-project.yml')
    ymlfile = open("config/{}".format(config_file), 'r')
    y = yaml.load(ymlfile)
    logger = ActivityLogger('log/missing-project.log')
    logger.setLevel('DEBUG')
    logAllExceptions(True, logger)
    konf = Konfabulator('config/missing-project.yml', logger)
    jenk_conf = konf.topLevel('Jenkins')
    ac_conf = konf.topLevel('AgileCentral')
    #expectedErrPattern = 'The Jenkins section of the config is missing AgileCentral_DefaultBuildProject property'
    expectedErrPattern = 'The Jenkins section of the config is missing a value for AgileCentral_DefaultBuildProject property'
    with pytest.raises(Exception) as excinfo:
        bc = bsh.BLDConnector(konf, logger)
    actualErrVerbiage = excinfo.value.args[0]
    assert re.search(expectedErrPattern, actualErrVerbiage) is not None
    assert excinfo.typename == 'ConfigurationError'
Esempio n. 8
0
def test_bad_yml():
    logger = ActivityLogger('log/bad_yml.log')
    expectedErrPattern = "The file does not contain consistent indentation for the sections and section contents"
    unexpectedErrPattern = "Oh noes!"
    with pytest.raises(Exception) as excinfo:
        konf = Konfabulator('config/bad_yml.yml', logger)
    actualErrVerbiage = excinfo.value.args[0]
    assert re.search(expectedErrPattern, actualErrVerbiage)
    assert not re.search(unexpectedErrPattern, actualErrVerbiage)
Esempio n. 9
0
def connect_to_jenkins(config_file):
    config_file = "config/{}".format(config_file)
    jenk_conf = {}
    with open(config_file, 'r') as cf:
        content = cf.read()
        all_conf = yaml.load(content)
        jenk_conf = all_conf['JenkinsBuildConnector']['Jenkins']

    jc = bsh.JenkinsConnection(jenk_conf, ActivityLogger('inventory.log'))
    return jc
Esempio n. 10
0
def connect_to_ac(config_file):
    logger = ActivityLogger('kublakhan.log')
    konf = Konfabulator('config/buildorama.yml', logger)
    jenk_conf = konf.topLevel('Jenkins')
    ac_conf = konf.topLevel('AgileCentral')
    ac_conf['Project'] = jenk_conf[
        'AgileCentral_DefaultBuildProject']  # leak proj from jenkins section to ac section
    agicen = AgileCentralConnection(ac_conf, logger)
    agicen.other_name = 'Jenkins'
    agicen.connect()
    return agicen
Esempio n. 11
0
def main(args):

    conf = yaml.load(CONFIG_CHUNK)
    jenkconf = conf['Jenkins']

    #pprint(jenkconf)
    #print("=" * 80)
    #print("")

    logger = ActivityLogger("jenk.log", policy='calls', limit=1000)
    jenkins = JenkinsConnection(jenkconf, logger)
    started = time.time()
    try:
        jenkins.connect()
    except Exception as ex:
        # does ex include the following text?
        #  Max retries exceeded with url: /manage .* nodename nor servname provided, or not known'
        print(sys.exc_info()[0])  # 0 is the Exception instance
        print(sys.exc_info()[1])  # 1 is the Exception text

    ref_timestamp = (2016, 6, 1, 0, 0, 0, 5, 0, -1)

    #top_level_folders = sorted(jenkins.view_folders['All'].keys())
    #for folder_name in top_level_folders:
    #    folder = jenkins.view_folders['All'][folder_name]
    #    print(folder.info())

    builds = jenkins.getRecentBuilds(ref_timestamp)
    finished = time.time()
    for tank_and_project in sorted(builds):
        container, project = tank_and_project.split('::', 1)
        print("Jenkins Container: %s" % container)
        print("AgileCentral Project: %s" % project)
        for job in builds[tank_and_project]:
            print("     Job: %s" % job)
            build_results = builds[tank_and_project][job]
            for build in build_results:
                print("%s%s" % (" " * 8, build))
            print("")

    #for view_and_project in builds:
    #    view, project = view_and_project.split('::', 1)
    #    print("Jenkins View: %s" % view)
    #    print("AgileCentral Project: %s" % project)
    #    for job in builds[view_and_project]:
    #        print("     Job: %s" % job)
    #        build_results = builds[view_and_project][job]
    #        for build in builds[view_and_project][job]:
    #            print("%s%s" % (" " * 8, build))
    #        print("")
    #
    print("")
    print("Elapsed processing time: %6.3f seconds" % (finished - started))
Esempio n. 12
0
def test_test_konfabulator():
    logger = ActivityLogger('test.log')
    filename = "config/buildorama.yml"
    test_konfabulator = TestKonfabulator(filename, logger)
    job1 = 'slippery slope'
    item1 = {'Job': job1}
    test_konfabulator.add_to_container(item1)
    assert test_konfabulator.has_item('Job', job1) == True
    job2 = 'uphill both ways'
    item2 = {'Job': job2}
    test_konfabulator.replace_in_container(item1, item2)
    assert test_konfabulator.has_item('Job', job1) == False
    assert test_konfabulator.has_item('Job', job2) == True
Esempio n. 13
0
def main(args):

    if args:
        config_file_name = args.pop(0)
        with open("config/%s" % config_file_name, 'r') as f:
             uber_conf = yaml.load(f)
        conf = uber_conf['JenkinsBuildConnector']
    else:
        conf = yaml.load(CONFIG_CHUNK)
    ac_conf = conf['AgileCentral']

    #pprint(ac_conf)
    #print("=" * 80)
    #print("")

    logger  = ActivityLogger("agicen.builds.log", policy='calls', limit=1000)
    agicen = AgileCentralConnection(ac_conf, logger)
    agicen_headers = {'name'    : 'AgileCentral Build Lister', 'version' : __version__,
                      'vendor'  : 'Open Source Galaxy', 'other_version' : 'placeholder' }
    agicen.set_integration_header(agicen_headers)
    agicen.setSourceIdentification('placeholder', '0.0.0')
    agicen.connect()

    ref_timestamp = (2016, 3, 1, 0, 0, 0, 5, 0, -1)

    started = time.time()
    builds = agicen.getRecentBuilds(ref_timestamp)
    print("%d Projects had Build items within the defined scope" % len(builds)) 
    finished = time.time()
    for project in builds:
    ##
      # if project != ac_conf['DefaultProject']:
      #     break
    ##
        print("AgileCentral Project: %s" % project)
        job_names = builds[project].keys()
        for job_name in job_names:
            for build in builds[project][job_name]:
                build_date = build.Start[:-5].replace('T', ' ')
                duration = pretty_duration(build.Duration)

                print("    %-24.24s   %5d  %-10.10s  %s  %15.15s" % \
                      (job_name, int(build.Number), build.Status, build_date, duration))
        print("")

    print("Elapsed retrieval time: %6.3f seconds" % (finished - started))
Esempio n. 14
0
        self.name = name
        self.url = url
        self.jobs = jobs

    def __str__(self):
        sub_jobs = len(self.jobs)
        return "name: %-24.24s   sub-jobs: %3d   url: %s " % \
               (self.name, sub_jobs, self.url)

    def info(self):
        return str(self)


############################################################################################

logger = ActivityLogger("jenk.log", policy='calls', limit=1000)
jenk = DepthCharge("tiema03-u183073.ca.com", 8080, 'jenkins', 'rallydev',
                   logger)

started = time.time()
jenk.connect()
retrieved = time.time()

# for job in jenk.jenk['jobs']:
#     if not job['_class'].endswith('.Folder'):
#         inventory['jobs'].append(JenkinsJob(job))

# print('---------------------------')
#
# for view in jenk.jenk['views']:
#     if not job['_class'].endswith('.ListView'):
Esempio n. 15
0
class BuildConnectorRunner(object):
    def __init__(self, args):
        """
            An instance of this class is responsible for basic command line parsing,
            pulling off the name of the target config file.
            Once that item is taken care of, the instance obtains an instance of a
            Konfabulator, a AuxiliaryClassLoader instance and and instance of an BLDConnector.
            The Konfabulator is delegated the task of reading the config file, the AuxiliaryClassLoader
            is delegated the task of pulling in any Extension classes.
            These instances are then provided to the BuildConnector instance.
            This instance then directs the BuildConnector instance to obtain unrecorded builds 
            from the target Build/CI system and reflect them in the Agile Central server.
        """
        if len(args) < 1:
            problem = (
                "Insufficient command line args, must be at least a config file name."
            )
            raise ConfigurationError(problem)

        self.default_log_file_name = True

        # this only allows a logfile spec of --logfile=<some_file_name>
        # TODO: consider allowing additional syntax  logfile=foo.log or --logfile foo.log
        log_spec = [arg for arg in args if arg.startswith('--logfile=')]
        if log_spec:
            self.logfile_name = log_spec[0].replace('--logfile=', '')
            self.default_log_file_name = False
            for spec in log_spec:
                args = [arg for arg in args if arg != spec]

        self.config_file_names = args
        if self.default_log_file_name:  # set it to first config file minus any '_config.yml' portion
            self.first_config = self.config_file_names[0]
            self.logfile_name = "log/%s.log" % self.first_config.replace(
                '.yml', '').replace('_config', '')
            try:
                if not os.path.exists('log'):
                    os.makedirs('log')
            except Exception as msg:
                sys.stderr.write(
                    "Unable to locate or create the log sub-directory, %s\n" %
                    msg)
                raise Exception

        self.log = ActivityLogger(self.logfile_name)
        logAllExceptions(True, self.log)
        self.preview = False
        self.connector = None
        self.extension = {}

    def proclaim_existence(self, build_system_name):
        proc = ProcTable.targetProcess(os.getpid())
        now = time.strftime(STD_TS_FMT, time.gmtime(time.time()))
        cmd_elements = proc.cmdline.split()
        executable = cmd_elements[0]
        cmd_elements[0] = os.path.basename(executable)
        proc.cmdline = " ".join(cmd_elements)
        self.log.write(EXISTENCE_PROCLAMATION %
                       (build_system_name, __version__, now, proc.pid,
                        os.getcwd(), proc.cmdline))

    def acquireLock(self):
        """
            Check for conditions to proceed based on lock file absence/presence status.
            Acquisition of the lock is a green-light to proceed.
            Failure to acquire the lock prevents any further operation.
        """
        if LockFile.exists(LOCK_FILE):
            self.log.warning("A %s file exists" % LOCK_FILE)
            locker = LockFile.currentLockHolder(LOCK_FILE)
            if not LockFile.lockerIsRunning(LOCK_FILE, locker):
                message = (
                    "A prior connector process (%s) did not clean up "
                    "the lock file on termination, proceeding with this run")
                self.log.warning(message % locker)
            else:
                self.log.error(
                    "Another connector process [%s] is still running, unable to proceed"
                    % locker)
                raise ConfigurationError(
                    "Simultaneous processes for this connector are prohibited")

        LockFile.createLock(LOCK_FILE)
        return True

    def releaseLock(self):
        LockFile.destroyLock(LOCK_FILE)

    def run(self):
        own_lock = False
        build_system_name = self.identifyBuildSystemName()
        self.proclaim_existence(build_system_name)
        own_lock = self.acquireLock()

        try:
            for config_file in self.config_file_names:
                config_file_path = self.find_config_file(config_file)
                if not config_file_path:
                    raise ConfigurationError(
                        "No config file for '%s' found in the config subdir" %
                        config_file)
                lf_name = "log/%s.log" % config_file.replace(
                    '.yml', '').replace('_config', '')
                self.log = ActivityLogger(lf_name)
                logAllExceptions(True, self.log)
                self._operateService(config_file_path)
        except Exception as msg:
            self.log.error(msg)
        finally:
            try:
                if own_lock: self.releaseLock()
            except Exception as msg:
                raise OperationalError(
                    "ERROR: unable to remove lock file '%s', %s" %
                    (LOCK_FILE, msg))
        self.log.info('run completed')

    def identifyBuildSystemName(self):
        file_name = self.find_config_file(self.first_config)
        if not file_name:
            problem = "Unable to locate any config file for the name: '%s'" % self.first_config
            sys.stderr.write("ERROR: %s.  Correct this and run again.\n" %
                             problem)
            sys.exit(1)

        bsn = 'UNKNOWN'
        limit = 5
        ix = 0
        with open(file_name, 'r', encoding="utf-8") as fcf:
            while ix < limit:
                text_line = fcf.readline()
                ix += 1
                mo = re.match(r'^(?P<bsn>[A-Z][A-Za-z]+)BuildConnector:$',
                              text_line)
                if mo:
                    bsn = mo.group('bsn')
                    break
        if bsn == 'UNKNOWN':
            problem = "The config file for '%s' does not contain a valid Build Connector identifier." % self.first_config
            sys.stderr.write("ERROR: %s.  Correct this and run again.\n" %
                             problem)
            sys.exit(2)

        return bsn

    def find_config_file(self, config_name):
        relative_path = 'config/%s' % config_name
        if os.path.exists(relative_path):
            return relative_path
        else:
            return None
        # valid_targets = ['%s' % config_name, '%s.yml' % config_name]
        # hits = [entry for entry in glob.glob('config/*') if config_name in entry]
        # if hits:
        #     return hits[0]
        # else:
        #     return None

    def _operateService(self, config_file_path):
        config_name = config_file_path.replace('config/', '')
        started = finished = elapsed = None
        self.connector = None
        self.log.info("processing to commence using content from %s" %
                      config_file_path)

        last_conf_mod = time.strftime(
            STD_TS_FMT, time.gmtime(os.path.getmtime(config_file_path)))
        conf_file_size = os.path.getsize(config_file_path)
        self.log.info("%s last modified %s,  size: %d chars" %
                      (config_file_path, last_conf_mod, conf_file_size))
        config = self.getConfiguration(config_file_path)

        this_run = time.time(
        )  # be optimistic that the reflectBuildsInAgileCentral service will succeed
        now_zulu = time.strftime(STD_TS_FMT, time.gmtime(
            this_run))  # zulu <-- universal coordinated time <-- UTC

        self.time_file = TimeFile(self.buildTimeFileName(config_name),
                                  self.log)
        if self.time_file.exists():
            last_run = self.time_file.read(
            )  # the last_run is in Zulu time (UTC) as an epoch seconds value
        else:
            last_run = time.time() - (THREE_DAYS)
        last_run_zulu = time.strftime(STD_TS_FMT, time.gmtime(last_run))
        #self.log.info("Last Run %s --- Now %s" % (last_run_zulu, now_zulu))
        self.log.info("Time File value %s --- Now %s" %
                      (last_run_zulu, now_zulu))

        self.connector = BLDConnector(config, self.log)
        self.log.debug(
            "Got a BLDConnector instance, calling the BLDConnector.run ...")
        status, builds = self.connector.run(last_run, self.extension)
        # builds is an OrderedDict instance, keyed by job name, value is a list of Build instances

        finished = time.time()
        elapsed = int(round(finished - this_run))
        self.logServiceStatistics(config_name, builds, elapsed)

        if self.preview:
            self.log.info(
                "Preview mode in effect, time.file File not written/updated")
            return
        if not status and builds:
            # Not writing the time.file may cause repetitive detection of Builds,
            # but that is better than missing out on Builds altogether
            self.log.info(
                "There was an error in processing so the time.file was not written"
            )
            return

        if not builds:
            self.log.info(
                'No builds were added during this run, so the time.file NOT updated.'
            )
            return

        # we've added builds successfully, so update the Time File (config/<config>_time.file)
        try:
            earliest_build_start = min(
                build_list[-1].Start
                for job_name, build_list in builds.items())
            time_file_timestamp = earliest_build_start.replace('T', ' ')[:19]
            self.time_file.write(time_file_timestamp)
            self.log.info("time file written with value of %s Z" %
                          time_file_timestamp)
        except Exception as msg:
            raise OperationalError(msg)

    def buildTimeFileName(self, config_file):
        if config_file:
            if config_file.endswith('.yml') or config_file.endswith('.cfg'):
                time_file_name = config_file[0:-4] + '_time.file'
            else:
                time_file_name = "%s_time.file" % config_file
        else:
            time_file_name = 'time.file'
        time_file_path = 'log/%s' % time_file_name
        return time_file_path

    def logServiceStatistics(self, config_name, builds, elapsed):
        """
            what we intend to append to the log...  
               bld_conn_config.yml: 32 additional builds reflected in AgileCentral
            and a line with the elapsed time taken in human readable form  
            (elapsed is in seconds (a float value))
        """
        for build in builds:
            preview_reminder = "(Preview Mode)" if self.preview else ""
            factoid = "%3d builds posted for job %s" % (len(
                builds[build]), build)
            self.log.info("%s: %s %s" %
                          (config_name, factoid, preview_reminder))
        hours, rem = divmod(elapsed, 3600)
        mins, secs = divmod(rem, 60)
        if hours > 0:
            duration = "%s hours %s minutes %s seconds" % (hours, mins, secs)
        else:
            if mins > 0:
                duration = "%s minutes %s seconds" % (mins, secs)
            else:
                duration = "%s seconds" % secs
        self.log.info("%s: service run took %s" % (config_name, duration))

    def getConfiguration(self, config_file):
        try:
            config = Konfabulator(config_file, self.log)
        except NonFatalConfigurationError as msg:
            pass  # info for this will have already been logged or blurted
        except Exception as msg:
            raise ConfigurationError(msg)
        svc_conf = config.topLevel('Service')
        self.preview = False
        if svc_conf and svc_conf.get('Preview', None) == True:
            self.preview = True
        self.log_level = 'Info'
        if svc_conf:
            ll = svc_conf.get('LogLevel', 'Info').title()
            if ll in ['Fatal', 'Error', 'Warn', 'Info', 'Debug']:
                self.log_level = ll
                self.log.setLevel(self.log_level)
            else:
                pass  # bad LogLevel specified
            #if 'PostBatchExtension' in svc_conf:
            #    pba_class_name = svc_conf['PostBatchExtension']
            #    pba_class = ExtensionLoader().getExtension(pba_class_name)
            #    self.extension['PostBatch'] = pba_class()

        return config