Skip to content

Commit

Permalink
Add the import_repo script and update create_issues to work with the …
Browse files Browse the repository at this point in the history
…github2fedmsg DB

Signed-off-by: Aurélien Bompard <[email protected]>
  • Loading branch information
abompard committed Sep 9, 2024
1 parent f07eac3 commit 54bd81d
Show file tree
Hide file tree
Showing 3 changed files with 221 additions and 31 deletions.
92 changes: 61 additions & 31 deletions webhook_to_fedora_messaging/migration_from_gh2fm/create_issues.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import asyncio
import logging
from contextlib import asynccontextmanager

import click
import gidgethub
import gidgethub.abc
import gidgethub.httpx
import httpx
from sqlalchemy import select
from sqlalchemy.orm import selectinload

from webhook_to_fedora_messaging.config import set_config_file
from webhook_to_fedora_messaging.database import get_session
from webhook_to_fedora_messaging.models import Service
from webhook_to_fedora_messaging.migration_from_gh2fm import gh2fm


DESC_PREFIX = "Migrated from GitHub2FedMsg."
Expand All @@ -23,10 +20,11 @@
migrating its applications away from Fedmsg to Fedora Messaging, we encourage you to migrate your
repository to the successor of the Github2Fedmsg project, Webhook To Fedora Messaging.
Please follow this $LINK to the official announcement of the project's release and use the
instructions there to migrate to the new service. If this notification was a mistake, please close
this ticket. We will not act on the repositories for which no migration has been requested
and any related Github2Fedmsg operations will stop working once the service is decommissioned.
Please read [this official announcement](https://communityblog.fedoraproject.org/?p=14044) of the
project's release and use the instructions there to migrate to the new service.
If this notification was a mistake, please close this ticket. We will not act on the repositories
for which no migration has been requested and any related Github2Fedmsg operations will stop
working once the service is decommissioned.
"""

log = logging.getLogger(__name__)
Expand All @@ -44,49 +42,81 @@
"-u",
"--github-username",
envvar="GITHUB_USERNAME",
required=True,
help="The Github username that will be opening the issues",
)
@click.option("-t", "--github-token", envvar="GITHUB_TOKEN", help="The Github API token")
@click.option(
"-t",
"--github-token",
envvar="GITHUB_TOKEN",
required=True,
help="The Github API token",
)
@click.option(
"-g",
"--github2fedmsg-db-url",
envvar="GITHUB2FEDMSG_DB_URL",
required=True,
help="The URL to Github2Fedmsg's DB",
)
@click.option("-d", "--debug", is_flag=True, help="Show more information")
def main(config_path, github_username, github_token, debug):
def main(config_path, github_username, github_token, github2fedmsg_db_url, debug):
if config_path:
set_config_file(config_path)
logging.basicConfig(
format="%(asctime)s %(message)s",
level=logging.DEBUG if debug else logging.INFO,
)
logging.getLogger("httpx").setLevel(logging.INFO if debug else logging.WARNING)
asyncio.run(_main(github_username, github_token))

asyncio.run(_main(github_username, github_token, github2fedmsg_db_url))

with_db_session = asynccontextmanager(get_session)


async def _main(github_username, github_token):
async def _main(github_username, github_token, github2fedmsg_db_url):
async with httpx.AsyncClient() as http_client:
gh = gidgethub.httpx.GitHubAPI(http_client, github_username, oauth_token=github_token)
query = (
select(Service)
.where(Service.disabled.is_(False), Service.desc.like(f"{DESC_PREFIX}%"))
.options(selectinload(Service.users))
async with gh2fm.get_session(github2fedmsg_db_url) as gh2fm_session:
query = (
select(gh2fm.users.c.github_username, gh2fm.repos.c.name)
.join_from(gh2fm.repos, gh2fm.users)
.where(gh2fm.repos.c.enabled.is_(True))
)
result = await gh2fm_session.execute(query)
for user, repo in result.fetchall():
await process_repo(f"{user}/{repo}", gh)


async def process_repo(repo, gh: gidgethub.abc.GitHubAPI):
log.debug("Processing %s", repo)
# Make sure the repo exists:
try:
await gh.getitem(f"/repos/{repo}")
except gidgethub.BadRequest as e:
if e.status_code == 404:
log.info("Repo %s not found, skipping", repo)
return
print(e, e.__dict__)
raise
except gidgethub.RedirectionException:
# Get the new name
request_headers = gidgethub.sansio.create_headers(
gh.requester, accept=gidgethub.sansio.accept_format(), oauth_token=gh.oauth_token
)
async with with_db_session() as db_session:
for service in await db_session.scalars(query):
await process_service(service, gh)


async def process_service(service, gh: gidgethub.abc.GitHubAPI):
log.debug("Processing %s", service.name)
issues_url = f"/repos/{service.name}/issues"
status_code, headers, content = await gh._request(
"GET",
gidgethub.sansio.format_url(f"/repos/{repo}", None, base_url=gh.base_url),
request_headers,
)
new_repo = await gh.getitem(headers["location"])
repo = new_repo["full_name"]
log.info("The repo has been moved to %s", repo)
issues_url = f"/repos/{repo}/issues"
issues = gh.getiter(issues_url, dict(state="all", creator=gh.requester))
async for issue in issues:
if issue["title"] == ISSUE_TITLE:
return # The issue already exists in this repo
log.info("Creating issue in %s", service.name)
log.info("Creating issue in %s", repo)
new_issue = await gh.post(issues_url, data={"title": ISSUE_TITLE, "body": ISSUE_BODY})
log.debug(
"Created issue %s in %s (%s)", new_issue["number"], service.name, new_issue["html_url"]
)
log.debug("Created issue %s in %s (%s)", new_issue["number"], repo, new_issue["html_url"])


if __name__ == "__main__":
Expand Down
45 changes: 45 additions & 0 deletions webhook_to_fedora_messaging/migration_from_gh2fm/gh2fm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from contextlib import asynccontextmanager

from sqlalchemy import Boolean, Column, ForeignKey, Integer, MetaData, String, Table, Unicode
from sqlalchemy.ext.asyncio import create_async_engine


metadata = MetaData()

users = Table(
"users",
metadata,
Column("username", Unicode, primary_key=True),
Column("github_username", Unicode),
Column("emails", Unicode),
Column("full_name", String),
Column("oauth_access_token", Unicode),
)

repos = Table(
"repos",
metadata,
Column("id", Integer, primary_key=True),
Column("name", Unicode),
Column("description", Unicode),
Column("language", Unicode),
Column("enabled", Boolean),
Column("username", Unicode, ForeignKey("users.github_username")),
)

org_to_user = Table(
"org_to_user_mapping",
metadata,
Column("org_id", Unicode, ForeignKey("users.github_username"), primary_key=True),
Column("usr_id", Unicode, ForeignKey("users.github_username"), primary_key=True),
)


@asynccontextmanager
async def get_session(url):
engine = create_async_engine(url)

async with engine.begin() as conn:
yield conn

await engine.dispose()
115 changes: 115 additions & 0 deletions webhook_to_fedora_messaging/migration_from_gh2fm/import_repo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import asyncio
import logging
from contextlib import asynccontextmanager
from uuid import uuid4

import click
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError, NoResultFound

from webhook_to_fedora_messaging.config import set_config_file
from webhook_to_fedora_messaging.database import get_session
from webhook_to_fedora_messaging.migration_from_gh2fm import gh2fm
from webhook_to_fedora_messaging.models import Service, User
from webhook_to_fedora_messaging.models.owners import owners_table


log = logging.getLogger(__name__)


@click.command()
@click.option(
"-c",
"--config",
"config_path",
type=click.Path(exists=True, readable=True),
help="Path to the configuration file",
)
@click.option(
"-g",
"--github2fedmsg-db-url",
envvar="GITHUB2FEDMSG_DB_URL",
required=True,
help="The URL to Github2Fedmsg's DB",
)
@click.option("-d", "--debug", is_flag=True, help="Show more information")
@click.argument("fas_username", required=True)
@click.argument("repo", required=True)
def main(config_path, github2fedmsg_db_url, debug, fas_username, repo):
if config_path:
set_config_file(config_path)
logging.basicConfig(
format="%(asctime)s %(name)s %(levelname)s: %(message)s", level=logging.INFO
)
if debug:
log.setLevel(logging.DEBUG)
log.addHandler(log.root.handlers[0])
log.propagate = False

if repo.count("/") != 1:
raise click.BadArgumentUsage(
"The repo argument must be in the 'user_or_org/repo_name' format."
)
asyncio.run(_main(repo, fas_username, github2fedmsg_db_url))


with_db_session = asynccontextmanager(get_session)


async def _main(repo_full_name, fas_username, github2fedmsg_db_url):
user_or_org, repo_name = repo_full_name.split("/")
async with gh2fm.get_session(github2fedmsg_db_url) as gh2fm_session:
query = (
select(gh2fm.repos, gh2fm.users.c.username.label("fas_username"))
.join_from(gh2fm.repos, gh2fm.users)
.where(gh2fm.repos.c.username == user_or_org, gh2fm.repos.c.name == repo_name)
)
result = await gh2fm_session.execute(query)
try:
repo = result.one()
except NoResultFound:
log.error("Could not find repo %r in Github2Fedmsg's database", repo_full_name)
return
if not repo.enabled:
log.info("The repo is disabled, skipping.")
return
if not repo.fas_username.startswith("github_org_") and repo.fas_username != fas_username:
log.warning(
"The repo %s used to be linked to FAS user %s, it will now be linked "
"to FAS user %s",
repo_full_name,
repo.fas_username,
fas_username,
)
async with with_db_session() as db_session:
await import_repo(db_session, repo, fas_username)


async def import_repo(db, repo, username):
db_user = await db.scalar(select(User).where(User.name == username))
if not db_user:
db_user = User(name=username)
db.add(db_user)
log.debug("Adding user %s", username)
await db.flush()
db_service = Service(
name=f"{repo.username}/{repo.name}",
uuid=uuid4().hex[0:8],
type="github",
desc=f"Migrated from GitHub2FedMsg. {repo.description}",
disabled=False,
)
db.add(db_service)
try:
await db.flush()
except IntegrityError:
log.warning("Service already exists.")
await db.rollback()
return
stmt = owners_table.insert().values({"service_id": db_service.id, "user_id": db_user.id})
await db.execute(stmt)
log.info("Service %s created", db_service.name)


if __name__ == "__main__":
main()

0 comments on commit 54bd81d

Please sign in to comment.