Ejemplo n.º 1
0
 def __init__(self, debug=False):
     self.debug = debug
     config = configuration.GetConfig()
     username, password = rest.GetUsernameAndPassword()
     self.rest_client = rest.RestClient(
         pkgdb_url=config.get('rest', 'pkgdb'),
         releases_url=config.get('rest', 'releases'),
         username=username,
         password=password,
         debug=debug)
Ejemplo n.º 2
0
 def __init__(self, binary_path, debug=False):
     self.debug = debug
     self._binary_path = binary_path
     self.config = configuration.GetConfig()
     username, password = rest.GetUsernameAndPassword()
     self.rest_client = rest.RestClient(
         pkgdb_url=self.config.get('rest', 'pkgdb'),
         releases_url=self.config.get('rest', 'releases'),
         username=username,
         password=password)
     fd = open(self._binary_path, 'rb')
     self._mmap = mmap.mmap(fd.fileno(), 0, access=mmap.PROT_READ)
     self._elffile = ELFFile(self._mmap)
Ejemplo n.º 3
0
def main():
    oldcatrel, newcatrel, arch, osrel = GetCLIOptions()

    config = configuration.GetConfig()
    username, password = rest.GetUsernameAndPassword()
    rest_client = rest.RestClient(pkgdb_url=config.get('rest', 'pkgdb'),
                                  releases_url=config.get('rest', 'releases'),
                                  username=username,
                                  password=password)

    removelst, rebuildlst, obsolete = (ComputeRemoveAndRebuild(
        oldcatrel, newcatrel, arch, osrel, rest_client))
    WriteToTextFiles(removelst, rebuildlst, newcatrel, arch, osrel, obsolete)
Ejemplo n.º 4
0
 def __init__(self, osrel, arch, debug=False):
   super(InstallContentsImporter, self).__init__()
   self.osrel = osrel
   self.arch = arch
   self.pkginst_cache = {}
   config = configuration.GetConfig()
   username, password = rest.GetUsernameAndPassword()
   self.rest_client = rest.RestClient(
       pkgdb_url=config.get('rest', 'pkgdb'),
       releases_url=config.get('rest', 'releases'),
       username=username,
       password=password,
       debug=debug)
Ejemplo n.º 5
0
 def RemovePackage(self, catalogname, execute=False, os_releases=None):
     if not os_releases:
         os_releases = common_constants.OS_RELS
     username, password = rest.GetUsernameAndPassword()
     rd = RevDeps(self.rest_client)
     rev_deps = {}
     # md5 sums to remove
     to_remove = []
     found_anywhere = False
     for osrel in os_releases:
         if osrel not in common_constants.OS_RELS:
             logging.warning(
                 "%s not found in common_constants.OS_RELS (%s). Skipping.",
                 osrel, common_constants.OS_RELS)
             continue
         if osrel in common_constants.OBSOLETE_OS_RELS:
             logging.info("%s is an obsolete OS release. Skipping.", osrel)
             continue
         for arch in common_constants.PHYSICAL_ARCHITECTURES:
             try:
                 pkg_simple = self.rest_client.Srv4ByCatalogAndCatalogname(
                     UNSTABLE, arch, osrel, catalogname)
             except urllib2.HTTPError, e:
                 logging.warning("could not fetch %r from %s/%s: %s",
                                 catalogname, arch, osrel, e)
                 pkg_simple = None
             if not pkg_simple:
                 # Maybe we were given a pkgname instead of a catalogname? We can try
                 # that before failing.
                 pkg_simple = self.rest_client.Srv4ByCatalogAndPkgname(
                     UNSTABLE, arch, osrel, catalogname)
                 if not pkg_simple:
                     msg = "{0} was not in the unstable {1} {2} catalog."
                     logging.debug(
                         msg.format(repr(catalogname), arch, osrel))
                     continue
             if pkg_simple:
                 found_anywhere = True
             md5 = pkg_simple["md5_sum"]
             key = UNSTABLE, arch, osrel
             cat_rev_deps = rd.RevDepsByMD5(UNSTABLE, arch, osrel, md5)
             if cat_rev_deps:
                 rev_deps[key] = cat_rev_deps
             to_remove.append((UNSTABLE, arch, osrel, md5))
Ejemplo n.º 6
0
 def __init__(self, name, sqo_pkgs_list, osrel, arch, catrel, debug=False,
     show_progress=False):
   super(CheckpkgManagerBase, self).__init__()
   self.debug = debug
   self.name = name
   self.sqo_pkgs_list = sqo_pkgs_list
   self.osrel = osrel
   self.arch = arch
   self.catrel = catrel
   self.show_progress = show_progress
   self._ResetState()
   self.individual_checks = []
   self.set_checks = []
   config = configuration.GetConfig()
   username, password = rest.GetUsernameAndPassword()
   self.rest_client = rest.RestClient(
       pkgdb_url=config.get('rest', 'pkgdb'),
       releases_url=config.get('rest', 'releases'),
       username=username,
       password=password)
Ejemplo n.º 7
0
def main():
    parser = optparse.OptionParser(USAGE)
    parser.add_option("-c",
                      "--catalogname",
                      dest="catalogname",
                      help='the name of the package in catalog')
    parser.add_option("--os-releases",
                      dest="os_releases",
                      help=("Comma separated OS releases, e.g. "
                            "SunOS5.9,SunOS5.10"))
    parser.add_option("--debug", dest="debug", action="store_true")
    parser.add_option("--dry-run",
                      dest="dry_run",
                      default=False,
                      action="store_true",
                      help=("Don't apply changes (no REST calls)."))
    options, args = parser.parse_args()
    logging_level = logging.INFO
    if options.debug:
        logging_level = logging.DEBUG
    fmt = '%(levelname)s %(asctime)s %(filename)s:%(lineno)d %(message)s'
    logging.basicConfig(format=fmt, level=logging_level)
    if not options.catalogname:
        logging.error('option catalogname required \n%s', USAGE)
        sys.exit(1)
    os_releases = common_constants.OS_RELS
    if options.os_releases:
        os_releases = options.os_releases.split(",")

    config = configuration.GetConfig()
    username, password = rest.GetUsernameAndPassword()
    rest_client = rest.RestClient(pkgdb_url=config.get('rest', 'pkgdb'),
                                  releases_url=config.get('rest', 'releases'),
                                  username=username,
                                  password=password)

    pr = PackageRemover(rest_client)
    pr.RemovePackage(options.catalogname, not options.dry_run, os_releases)
Ejemplo n.º 8
0
 def __init__(self, pkg_path, debug):
     self.debug = debug
     self.pkg_path = pkg_path
     self._work_dir = None
     self._admin_file = None
     self._gunzipped_path = None
     self._md5_sum = None
     self._stat = None
     self._mtime = None
     self._transformed = False
     self._pkgname = None
     self._pkginfo_dict = None
     self._dir_format_base_dir = None
     self._files_metadata = None
     self._binaries = None
     self._file_paths = None
     self.config = configuration.GetConfig()
     username, password = rest.GetUsernameAndPassword()
     self.rest_client = rest.RestClient(
         pkgdb_url=self.config.get('rest', 'pkgdb'),
         releases_url=self.config.get('rest', 'releases'),
         username=username,
         password=password)
Ejemplo n.º 9
0
def main():
    parser = optparse.OptionParser(USAGE)
    parser.add_option("-d",
                      "--debug",
                      dest="debug",
                      action="store_true",
                      default=False,
                      help="Switch on debugging messages")
    parser.add_option("-q",
                      "--quiet",
                      dest="quiet",
                      action="store_true",
                      default=False,
                      help="Display less messages")
    parser.add_option(
        "--catalog-release",
        dest="catrel",
        default="current",
        help="A catalog release: current, unstable, testing, stable.")
    parser.add_option(
        "-r",
        "--os-releases",
        dest="osrel_commas",
        help=("Comma separated list of ['SunOS5.9', 'SunOS5.10'], "
              "e.g. 'SunOS5.9,SunOS5.10'."))
    parser.add_option("-a",
                      "--catalog-architecture",
                      dest="arch",
                      help="Architecture: i386, sparc.")
    parser.add_option("--profile",
                      dest="profile",
                      default=False,
                      action="store_true",
                      help="Enable profiling (a developer option).")
    options, args = parser.parse_args()
    assert len(args), "The list of files or md5 sums must be not empty."

    logging_level = logging.INFO
    if options.quiet:
        logging_level = logging.WARNING
    elif options.debug:
        # If both flags are set, debug wins.
        logging_level = logging.DEBUG
    fmt = '%(levelname)s %(asctime)s %(filename)s:%(lineno)d %(message)s'
    logging.basicConfig(format=fmt, level=logging_level)
    logging.debug("Starting.")

    configuration.SetUpSqlobjectConnection()

    err_msg_list = []
    if not options.osrel_commas:
        err_msg_list.append("Please specify --os-releases.")
    if not options.arch:
        err_msg_list.append("Please specify --catalog-architecture.")
    if options.arch not in cc.PHYSICAL_ARCHITECTURES:
        err_msg_list.append(
            "Valid --catalog-architecture values are: %s, you passed: %r" %
            (cc.PHYSICAL_ARCHITECTURES, options.arch))
    if err_msg_list:
        raise UsageError(" ".join(err_msg_list))

    md5_sums_from_files = []
    collector = package_stats.StatsCollector(logger=logging,
                                             debug=options.debug)
    # We need to separate files and md5 sums.
    md5_sums, file_list = [], []
    for arg in args:
        if struct_util.IsMd5(arg):
            md5_sums.append(arg)
        else:
            file_list.append(arg)

    config = configuration.GetConfig()
    username, password = rest.GetUsernameAndPassword()
    rest_client = rest.RestClient(pkgdb_url=config.get('rest', 'pkgdb'),
                                  releases_url=config.get('rest', 'releases'),
                                  username=username,
                                  password=password)

    if file_list:

        def MakeEntry(file_name):
            file_hash = hashlib.md5()
            with open(file_name, "r") as fd:
                chunk_size = 2 * 1024 * 1024
                data = fd.read(chunk_size)
                while data:
                    file_hash.update(data)
                    data = fd.read(chunk_size)
                md5_sum = file_hash.hexdigest()
                del file_hash
            _, file_basename = os.path.split(file_name)
            return {
                'pkg_path': file_name,
                'md5sum': md5_sum,
                'file_basename': file_basename,
            }

        entries = [MakeEntry(x) for x in file_list]
        md5_sums_from_files = collector.CollectStatsFromCatalogEntries(
            entries, False)
        for md5_sum in md5_sums_from_files:
            if not rest_client.IsRegisteredLevelOne(md5_sum):
                rest_client.RegisterLevelOne(md5_sum)
    # We need the md5 sums of these files
    md5_sums.extend(md5_sums_from_files)
    assert md5_sums, "The list of md5 sums must not be empty."
    logging.debug("md5_sums: %s", md5_sums)
    osrel_list = options.osrel_commas.split(",")
    logging.debug("Reading packages data from the database.")
    # This part might need improvements in order to handle a whole
    # catalog.  On the other hand, if we already have the whole catalog in
    # the database, we can do it altogether differently.
    # Transforming the result to a list in order to force object
    # retrieval.
    sqo_pkgs = list(
        models.Srv4FileStats.select(
            sqlobject.IN(models.Srv4FileStats.q.md5_sum, md5_sums)))
    tags_for_all_osrels = []
    try:
        sqo_catrel = models.CatalogRelease.selectBy(
            name=options.catrel).getOne()
    except sqlobject.main.SQLObjectNotFound as e:
        logging.fatal("Fetching from the db has failed: catrel=%s",
                      repr(str(options.catrel)))
        logging.fatal("Available catalog releases:")
        sqo_catrels = models.CatalogRelease.select()
        for sqo_catrel in sqo_catrels:
            logging.fatal(" - %s", sqo_catrel.name)
        raise
    sqo_arch = models.Architecture.selectBy(name=options.arch).getOne()
    for osrel in osrel_list:
        sqo_osrel = models.OsRelease.selectBy(short_name=osrel).getOne()
        VerifyContents(sqo_osrel, sqo_arch)
        check_manager = checkpkg_lib.CheckpkgManager2(
            CHECKPKG_MODULE_NAME,
            sqo_pkgs,
            osrel,
            options.arch,
            options.catrel,
            debug=options.debug,
            show_progress=(os.isatty(1) and not options.quiet))
        # Running the checks, reporting and exiting.
        exit_code, screen_report, tags_report = check_manager.Run()
        screen_report = unicode(screen_report)
        if not options.quiet and screen_report:
            # TODO: Write this to screen only after overrides are applied.
            sys.stdout.write(screen_report)
        else:
            logging.debug("No screen report.")

        overrides_list = [list(pkg.GetOverridesResult()) for pkg in sqo_pkgs]
        override_list = reduce(operator.add, overrides_list)
        args = (sqo_osrel, sqo_arch, sqo_catrel)
        tag_lists = [list(pkg.GetErrorTagsResult(*args)) for pkg in sqo_pkgs]
        error_tags = reduce(operator.add, tag_lists)
        (tags_after_overrides, unapplied_overrides) = overrides.ApplyOverrides(
            error_tags, override_list)
        tags_for_all_osrels.extend(tags_after_overrides)
        if not options.quiet:
            if tags_after_overrides:
                print(textwrap.fill(BEFORE_OVERRIDES, 80))
                for checkpkg_tag in tags_after_overrides:
                    print checkpkg_tag.ToGarSyntax()
                print
                for paragraph in AFTER_OVERRIDES:
                    print(textwrap.fill(paragraph, 80))
                    print
            elif error_tags:
                msg = (
                    'Fair enough, there were %d error tags, '
                    'but they were all overridden. '
                    "Just make sure you didn't override anything silly, like "
                    'sparc binaries in a i386 package.' % len(error_tags))
                print
            else:
                print('Jolly good! All checks passed, no error tags reported.')

            if unapplied_overrides:
                print textwrap.fill(UNAPPLIED_OVERRIDES, 80)
                for override in unapplied_overrides:
                    print u"* Unused %s" % override
    exit_code = bool(tags_for_all_osrels)
    sys.exit(exit_code)
Ejemplo n.º 10
0
def main():
    parser = optparse.OptionParser(USAGE)
    parser.add_option("-d",
                      "--debug",
                      dest="debug",
                      default=False,
                      action="store_true",
                      help="Turn on debugging messages")
    parser.add_option(
        "-t",
        "--pkg-review-template",
        dest="pkg_review_template",
        help="A Cheetah template used for package review reports.")
    parser.add_option("-r",
                      "--os-release",
                      dest="osrel",
                      default="SunOS5.10",
                      help="E.g. SunOS5.10")
    parser.add_option("-a",
                      "--arch",
                      dest="arch",
                      default="sparc",
                      help="'i386' or 'sparc'")
    parser.add_option("-c",
                      "--catalog-release",
                      dest="catrel",
                      default="unstable",
                      help="E.g. unstable, dublin")
    parser.add_option("--replace",
                      dest="replace",
                      default=False,
                      action="store_true",
                      help="Replace packages when importing (importpkg)")
    parser.add_option("--profile",
                      dest="profile",
                      default=False,
                      action="store_true",
                      help="Turn on profiling")
    parser.add_option("--force-unpack",
                      dest="force_unpack",
                      default=False,
                      action="store_true",
                      help="Force unpacking of packages")
    options, args = parser.parse_args()

    logging_level = logging.INFO
    if options.debug:
        logging_level = logging.DEBUG
    fmt = '%(levelname)s %(asctime)s %(filename)s:%(lineno)d %(message)s'
    logging.basicConfig(format=fmt, level=logging_level)

    if not args:
        raise UsageError("Please specify a command.  See --help.")
    # SetUpSqlobjectConnection needs to be called after
    # logging.basicConfig
    configuration.SetUpSqlobjectConnection()
    command = args[0]
    args = args[1:]
    if command == 'show':
        subcommand = args[0]
        args = args[1:]
    elif command == 'pkg':
        subcommand = args[0]
        args = args[1:]
    else:
        subcommand = None

    md5_sums = args

    if (command, subcommand) == ('show', 'errors'):
        for md5_sum in md5_sums:
            srv4 = GetPkg(md5_sum)
            res = m.CheckpkgErrorTag.select(
                m.CheckpkgErrorTag.q.srv4_file == srv4)
            for row in res:
                print 'overridden' if row.overridden else 'active',
                print row.pkgname, row.tag_name, row.tag_info, row.catrel.name, row.arch.name,
                print row.os_rel.short_name
    elif (command, subcommand) == ('show', 'overrides'):
        for md5_sum in md5_sums:
            srv4 = GetPkg(md5_sum)
            res = m.CheckpkgOverride.select(
                m.CheckpkgOverride.q.srv4_file == srv4)
            for row in res:
                print row.pkgname, row.tag_name, row.tag_info
    elif (command, subcommand) == ('show', 'pkg'):
        for md5_sum in md5_sums:
            srv4 = GetPkg(md5_sum)
            t = Template(SHOW_PKG_TMPL, searchList=[srv4])
            sys.stdout.write(unicode(t))
    elif command == 'gen-html':
        config = configuration.GetConfig()
        username, password = rest.GetUsernameAndPassword()
        rest_client = rest.RestClient(pkgdb_url=config.get('rest', 'pkgdb'),
                                      releases_url=config.get(
                                          'rest', 'releases'),
                                      username=username,
                                      password=password,
                                      debug=options.debug)

        g = HtmlGenerator(md5_sums, options.pkg_review_template, rest_client,
                          options.debug)
        sys.stdout.write(g.GenerateHtml())
    elif command == 'initdb':
        config = configuration.GetConfig()
        database.InitDB(config)
    elif command == 'importpkg':
        collector = package_stats.StatsCollector(logger=logging,
                                                 debug=options.debug)
        file_list = args
        catalog_entries = []
        for file_name in file_list:
            file_hash = hashlib.md5()
            chunk_size = 2 * 1024 * 1024
            with open(file_name, 'rb') as fd:
                data = fd.read(chunk_size)
                while data:
                    file_hash.update(data)
                    data = fd.read(chunk_size)
            data_md5_sum = file_hash.hexdigest()
            catalog_entry = {
                'md5sum': data_md5_sum,
                'file_basename': os.path.basename(file_name),
                'pkg_path': file_name,
            }
            catalog_entries.append(catalog_entry)
        md5_list = collector.CollectStatsFromCatalogEntries(
            catalog_entries, force_unpack=options.force_unpack)
        config = configuration.GetConfig()
        rest_client = rest.RestClient(pkgdb_url=config.get('rest', 'pkgdb'),
                                      releases_url=config.get(
                                          'rest', 'releases'),
                                      debug=options.debug)

        for md5_sum in md5_list:
            logging.debug("Importing %s", md5_sum)
            rest_client.RegisterLevelTwo(md5_sum)

    elif command == 'removepkg':
        for md5_sum in md5_sums:
            srv4 = GetPkg(md5_sum)
            in_catalogs = list(srv4.in_catalogs)
            if in_catalogs:
                for in_catalog in in_catalogs:
                    logging.warning("%s", in_catalog)
                logging.warning(
                    "Not removing from the database, because the package "
                    "in question is part of at least one catalog.")
            else:
                logging.info("Removing %s", srv4)
                srv4.DeleteAllDependentObjects()
                srv4.destroySelf()
    elif command == 'add-to-cat':
        if len(args) < 4:
            raise UsageError("Not enough arguments, see usage.")
        user = getpass.getuser()
        osrel, arch, catrel = args[:3]
        username, password = rest.GetUsernameAndPassword()
        rest_client = rest.RestClient(username=username, password=password)
        md5_sums = args[3:]
        for md5_sum in md5_sums:
            rest_client.AddSvr4ToCatalog(catrel, arch, osrel, md5_sum)
    elif command == 'del-from-cat':
        if len(args) < 4:
            raise UsageError("Not enough arguments, see usage.")
        osrel, arch, catrel = args[:3]
        md5_sums = args[3:]
        username, password = rest.GetUsernameAndPassword()
        rest_client = rest.RestClient(username=username, password=password)
        for md5_sum in md5_sums:
            rest_client.RemoveSvr4FromCatalog(catrel, arch, osrel, md5_sum)
    elif command == 'system-metadata-to-disk':
        logging.debug("Args: %s", args)
        outfile = None
        infile_contents = common_constants.DEFAULT_INSTALL_CONTENTS_FILE
        infile_pkginfo = None
        osrel, arch = (None, None)
        if len(args) >= 2:
            infile_contents = args[0]
            infile_pkginfo = args[1]
        if len(args) >= 3:
            outfile = args[2]
        if len(args) >= 4:
            if len(args) == 5:
                osrel, arch = args[3:5]
            else:
                raise UsageError("Wrong number of arguments (%s), see usage." %
                                 len(args))
        spi = system_pkgmap.Indexer(outfile, infile_contents, infile_pkginfo,
                                    osrel, arch)
        spi.IndexAndSave()
    elif command == 'import-system-metadata':
        if len(args) < 2:
            raise UsageError(
                "Usage: ... import-system-metadata <osrel> <arch>")
        osrel = args[0]
        arch = args[1]
        importer = system_pkgmap.InstallContentsImporter(osrel,
                                                         arch,
                                                         debug=options.debug)
        importer.Import(show_progress=(not options.debug))
    elif (command, subcommand) == ('pkg', 'search'):
        logging.debug("Searching for %s", args)
        sqo_osrel = m.OsRelease.selectBy(short_name=options.osrel).getOne()
        sqo_arch = m.Architecture.selectBy(name=options.arch).getOne()
        sqo_catrel = m.CatalogRelease.selectBy(name=options.catrel).getOne()
        if len(args) < 1:
            logging.fatal("Wrong number of arguments: %s", len(args))
            raise SystemExit
        for catalogname in args:
            join = [
                sqlbuilder.INNERJOINOn(
                    None, m.Srv4FileInCatalog,
                    m.Srv4FileInCatalog.q.srv4file == m.Srv4FileStats.q.id),
            ]
            res = m.Srv4FileStats.select(
                sqlobject.AND(
                    m.Srv4FileInCatalog.q.osrel == sqo_osrel,
                    m.Srv4FileInCatalog.q.arch == sqo_arch,
                    m.Srv4FileInCatalog.q.catrel == sqo_catrel,
                    m.Srv4FileStats.q.catalogname.contains(catalogname),
                    m.Srv4FileStats.q.use_to_generate_catalogs == True),
                join=join,
            ).orderBy("catalogname")
            for sqo_srv4 in res:
                print "%s %s" % (sqo_srv4.basename, sqo_srv4.md5_sum)
    elif command == 'sync-cat-from-file':
        if len(args) != 4:
            raise UsageError("Wrong number of arguments, see usage.")
        osrel, arch, catrel, catalog_file = args
        ci = CatalogImporter(debug=options.debug)
        ci.SyncFromCatalogFile(osrel, arch, catrel, catalog_file)
    elif command == 'sync-catalogs-from-tree':
        if len(args) != 2:
            raise UsageError("Wrong number of arguments, see usage.")
        ci = CatalogImporter(debug=options.debug)
        catrel, base_dir = args
        ci.SyncFromCatalogTree(catrel, base_dir, options.force_unpack)
    elif (command, subcommand) == ('show', 'cat'):
        sqo_osrel, sqo_arch, sqo_catrel = m.GetSqoTriad(
            options.osrel, options.arch, options.catrel)
        res = m.GetCatPackagesResult(sqo_osrel, sqo_arch, sqo_catrel)
        for obj in res:
            print obj.catalogname, obj.basename, obj.md5_sum
    elif (command, subcommand) == ('show', 'files'):
        md5_sum = args[0]
        join = [
            sqlbuilder.INNERJOINOn(
                None, m.Srv4FileStats,
                m.CswFile.q.srv4_file == m.Srv4FileStats.q.id),
        ]
        res = m.CswFile.select(
            m.Srv4FileStats.q.md5_sum == md5_sum,
            join=join,
        )
        for obj in res:
            print os.path.join(obj.path, obj.basename)
    elif (command, subcommand) == ('show', 'basename'):
        db_catalog = checkpkg_lib.Catalog()
        for arg in args:
            pkgs_by_path = db_catalog.GetPathsAndPkgnamesByBasename(
                arg, options.osrel, options.arch, options.catrel)
            for file_path in pkgs_by_path:
                print os.path.join(file_path,
                                   arg), ", ".join(pkgs_by_path[file_path])
    elif (command, subcommand) == ('show', 'filename'):
        db_catalog = checkpkg_lib.Catalog()
        for arg in args:
            pkgs = db_catalog.GetPkgByPath(arg, options.osrel, options.arch,
                                           options.catrel)
            print " ".join(pkgs)
    else:
        raise UsageError("Command unrecognized: %s" % command)
Ejemplo n.º 11
0
     previous_catalogs_by_triad = cPickle.load(fd)
 except (IOError, EOFError), e:
   logging.warning(e)
   previous_catalogs_by_triad = {}
 # Merge the two data structures here
 catalogs = []
 for key in catalogs_by_triad:
   if key in previous_catalogs_by_triad:
     catalogs.append(
       # ("fossil", "amd65", "SolarOS5.12", cat_a, cat_b),
       key + (previous_catalogs_by_triad[key], catalogs_by_triad[key])
     )
   else:
     logging.debug("%s not found in previous_catalogs_by_triad", key)
 formatter = NotificationFormatter()
 username, password = rest.GetUsernameAndPassword()
 config = configuration.GetConfig()
 rest_client = rest.RestClient(
     pkgdb_url=config.get('rest', 'pkgdb'),
     releases_url=config.get('rest', 'releases'),
     username=username,
     password=password)
 notifications = formatter.FormatNotifications(
     cat_tree_url, catalogs, rest_client)
 whitelist = frozenset()
 if options.whitelist:
   whitelist = frozenset(options.whitelist.split(","))
   logging.debug("Email whitelist: %s", whitelist)
 for email in notifications:
   if options.send_notifications:
     logging.debug("email: %s", repr(email))