# -*- coding: utf-8 -*- # # Copyright (C) 2014-2019 Bitergia # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # # Authors: # Santiago DueƱas <sduenas@bitergia.com> # import argparse import datetime import json import logging import sys from .. import api from ..command import Command, CMD_SUCCESS, HELP_LIST logger = logging.getLogger(__name__) class Export(Command): """Export data from the registry. This command exports data about identities. Data are exported, by default, to the standard output. A file path can also be given to store the data. Identities are exported using the option '--identities'. Using the option '--source' will only export those identities which have one or more identities associated to that source. To export organizations and domains information use the option '--orgs'. """ def __init__(self, **kwargs): super(Export, self).__init__(**kwargs) self.parser = argparse.ArgumentParser(description=self.description, usage=self.usage) # Actions group = self.parser.add_mutually_exclusive_group(required=True) group.add_argument('--identities', action='store_true', help="export identities") group.add_argument('--orgs', action='store_true', help="export organizations") # General options self.parser.add_argument('--source', dest='source', default=None, help="source of the identities to export") # Positional arguments self.parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'), default=sys.stdout, help="output file") # Exit early if help is requested if 'cmd_args' in kwargs and [i for i in kwargs['cmd_args'] if i in HELP_LIST]: return self._set_database(**kwargs) @property def description(self): return """Export data from the registry.""" @property def usage(self): return "%(prog)s export --identities [--source <source>] [file]\n or: %(prog)s export --orgs [file]" def run(self, *args): """Export data from the registry. By default, it writes the data to the standard output. If a positional argument is given, it will write the data on that file. """ params = self.parser.parse_args(args) with params.outfile as outfile: if params.identities: code = self.export_identities(outfile, params.source) elif params.orgs: code = self.export_organizations(outfile) else: # The running proccess never should reach this section raise RuntimeError("Unexpected export option") return code def export_identities(self, outfile, source=None): """Export identities information to a file. The method exports information related to unique identities, to the given 'outfile' output file. When 'source' parameter is given, only those unique identities which have one or more identities from the given source will be exported. :param outfile: destination file object :param source: source of the identities to export """ exporter = SortingHatIdentitiesExporter(self.db) dump = exporter.export(source) try: outfile.write(dump) outfile.write('\n') except IOError as e: raise RuntimeError(str(e)) return CMD_SUCCESS def export_organizations(self, outfile): """Export organizations information to a file. The method exports information related to organizations, to the given 'outfile' output file. :param outfile: destination file object """ exporter = SortingHatOrganizationsExporter(self.db) dump = exporter.export() try: outfile.write(dump) outfile.write('\n') except IOError as e: raise RuntimeError(str(e)) return CMD_SUCCESS class IdentitiesExporter(object): """Abstract class for exporting identities""" def __init__(self, db): self.db = db def export(self, source=None): raise NotImplementedError class SortingHatIdentitiesExporter(IdentitiesExporter): """Export identities to Sorting Hat identities format. This class exports the identities stored in the registry following the Sorting Hat identities JSON format. :param db: database manager """ def __init__(self, db): super(SortingHatIdentitiesExporter, self).__init__(db) def export(self, source=None): """Export a set of unique identities. Method to export unique identities from the registry. Identities schema will follow Sorting Hat JSON format. When source parameter is given, only those unique identities which have one or more identities from the given source will be exported. :param source: source of the identities to export :returns: a JSON formatted str """ uidentities = {} uids = api.unique_identities(self.db, source=source) for uid in uids: enrollments = [rol.to_dict() for rol in api.enrollments(self.db, uuid=uid.uuid)] u = uid.to_dict() u['identities'].sort(key=lambda x: x['id']) uidentities[uid.uuid] = u uidentities[uid.uuid]['enrollments'] = enrollments blacklist = [mb.excluded for mb in api.blacklist(self.db)] obj = {'time': str(datetime.datetime.now()), 'source': source, 'blacklist': blacklist, 'organizations': {}, 'uidentities': uidentities} return json.dumps(obj, default=self._json_encoder, indent=4, separators=(',', ': '), sort_keys=True) def _json_encoder(self, obj): """Default JSON encoder""" if isinstance(obj, datetime.datetime): return obj.isoformat() else: return json.JSONEncoder.default(obj) class OrganizationsExporter(object): """Abstract class for exporting organizations""" def __init__(self, db): self.db = db def export(self, source=None): raise NotImplementedError class SortingHatOrganizationsExporter(OrganizationsExporter): """Export organizations to Sorting Hat organizations format. This class exports the organizations stored in the registry following the Sorting Hat organizations JSON format. :param db: database manager """ def __init__(self, db): super(SortingHatOrganizationsExporter, self).__init__(db) def export(self): """Export a set of organizations. Method to export organizations from the registry. Organizations schema will follow Sorting Hat JSON format. :returns: a JSON formatted str """ organizations = {} orgs = api.registry(self.db) for org in orgs: domains = [{'domain': dom.domain, 'is_top': dom.is_top_domain} for dom in org.domains] domains.sort(key=lambda x: x['domain']) organizations[org.name] = domains obj = {'time': str(datetime.datetime.now()), 'blacklist': [], 'organizations': organizations, 'uidentities': {}} return json.dumps(obj, default=self._json_encoder, indent=4, separators=(',', ': '), sort_keys=True) def _json_encoder(self, obj): """Default JSON encoder""" if isinstance(obj, datetime.datetime): return obj.isoformat() else: return json.JSONEncoder.default(obj)