server/plugins/projects.py (144 lines of code) (raw):

import plugins.ldap import plugins.repositories import asfpy.sqlite import typing import datetime class Committer: def save(self, dbhandle: asfpy.sqlite.DB): document = { "asfid": self.asf_id, "githubid": self.github_login, "mfa": 1 if self.github_mfa else 0, "updated": datetime.datetime.now(), } dbhandle.upsert("ids", document, asfid=self.asf_id) def remove(self, dbhandle: asfpy.sqlite.DB): """Removes a user from the db, used for lockouts""" if self.asf_id: dbhandle.delete("ids", asfid=self.asf_id) def __init__( self, asf_id: str, linkdb: asfpy.sqlite.DB, ): self.asf_id = asf_id self.repositories: typing.Set[plugins.repositories.Repository] = set() self.projects: typing.Set[Project] = set() if linkdb: row = linkdb.fetchone("ids", limit=1, asfid=asf_id) else: row = None if row: self.github_login = row["githubid"] self.github_mfa = bool(row["mfa"]) self.real_name = "" else: self.github_login = None self.github_mfa = False self.real_name = "" def __repr__(self): return self.asf_id def __eq__(self, other): if isinstance(other, str): return other == self.asf_id if not isinstance(other, Committer): return False return self.asf_id == other.asf_id def __hash__(self): return hash((self.asf_id,)) class Project: def __init__(self, org: "Organization", name: str, committers: list, pmc: list): self.name: str = name self.committers: typing.List[Committer] = [] for committer in committers: account = org.add_committer(committer) self.committers.append(account) account.projects.add(self) self.pmc: typing.List[Committer] = [] for owner in pmc or []: account = org.add_committer(owner) self.pmc.append(account) account.projects.add(self) self.public_repos: typing.List[plugins.repositories.Repository] = [] self.private_repos: typing.List[plugins.repositories.Repository] = [] def __repr__(self): return f"Project<{self.name}>" def add_repository(self, repo: plugins.repositories.Repository, private: bool): """Adds a repository to a project and assigns the repo to the commmitter/PMC group as applicable""" if private: self.private_repos.append(repo) for account in self.pmc: account.repositories.add(repo) else: self.public_repos.append(repo) for account in self.committers: account.repositories.add(repo) def public_github_team(self, mfa = None): """Returns the GitHub IDs of everyone that should be on the GitHub team for this project""" team_ids = set() for committer in self.committers: if mfa: if mfa.get(committer.github_login): team_ids.add(committer.github_login) elif committer.github_mfa: team_ids.add(committer.github_login) return list(team_ids) def private_github_team(self, mfa = None): """Returns the GitHub IDs of everyone that should be on the GitHub private team for this project""" team_ids = set() for committer in self.pmc: if mfa: if mfa.get(committer.github_login): team_ids.add(committer.github_login) elif committer.github_mfa: team_ids.add(committer.github_login) return list(team_ids) class Organization: def __init__(self, linkdb: asfpy.sqlite.DB = None): self.committers: typing.List[Committer] = list() self.projects: typing.Dict[str, Project] = dict() self.linkdb: typing.Optional[asfpy.sqlite.DB] = linkdb def add_project(self, name: str, committers: typing.List[str], pmc: typing.List[str]) -> typing.Optional[Project]: if name and name not in self.projects: project = Project(org=self, name=name, committers=committers, pmc=pmc) if project: self.projects[name] = project return project return None def add_committer(self, asf_id: str): """Adds a new committer to the organization. If already found in previous projects, returns the old committer object""" if asf_id not in self.committers: committer = Committer(asf_id, self.linkdb) self.committers.append(committer) return committer else: for committer in self.committers: if committer.asf_id == asf_id: return committer async def compile_data( ldap: plugins.ldap.LDAPConfig, repositories: typing.List[plugins.repositories.Repository], linkdb: typing.Optional[asfpy.sqlite.DB] ) -> Organization: """Compiles a comprehensive list of projects and people associated with them""" org = Organization(linkdb=linkdb) discovered = 0 async with plugins.ldap.LDAPClient(ldap) as lc: # Gather LDAP records of all projects first for repo in repositories: project = repo.project if not project: continue if project not in org.projects: committers, pmc = await lc.get_members(project) if committers and pmc: discovered += 1 # print(f"Discovered project: {project} - {len(committers)} committers, {len(pmc)} in pmc") if discovered % 50 == 0: print("Discovered %d projects so far..." % discovered) org.add_project(name=project, committers=committers, pmc=pmc) # Then look at overrides for repo in repositories: project = repo.project if not project: continue xproject = org.projects[project] xproject.add_repository(repo, repo.private) # LDAP additional owner override if lc.ldap_override: repo_alts = lc.ldap_override.get("repository_alternate_owners") # Dict of repo -> list of alt owners if repo_alts: alts_for_this_repo = repo_alts.get(repo.filename, []) # list of alt owners (projects, e.g. httpd, tomcat, members) for alt_project in alts_for_this_repo: if alt_project in org.projects: # If alt project is defined, add the repo to their list print(f"Adding alternate owner of {repo.filename}: {alt_project}") org.projects[alt_project].add_repository(repo, repo.private) return org