Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retrigger bodhi update via dist-git PR comment #1654

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 138 additions & 56 deletions packit_service/worker/handlers/bodhi.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@
"""
import logging
from typing import Optional
from dataclasses import dataclass

from celery import Task
from fedora.client import AuthError
from koji import ClientSession

from packit.constants import DEFAULT_BODHI_NOTE
from packit.constants import DEFAULT_BODHI_NOTE, KOJI_BASEURL

from packit.exceptions import PackitException

from packit.api import PackitAPI
from packit.config.aliases import get_branches
from packit.distgit import DistGit

from packit.config import JobConfig, JobType, PackageConfig
from packit.local_project import LocalProject
Expand All @@ -26,19 +29,31 @@
KojiBuildState,
RETRY_INTERVAL_IN_MINUTES_WHEN_USER_ACTION_IS_NEEDED,
)
from packit_service.worker.events import PullRequestCommentPagureEvent
from packit_service.worker.events.koji import KojiBuildEvent
from packit_service.worker.handlers.abstract import (
TaskName,
configured_as,
reacts_to,
RetriableJobHandler,
run_for_comment,
)
from packit_service.worker.result import TaskResults

logger = logging.getLogger(__name__)


@dataclass
class KojiBuildEventWrapper:
build_id: int
state: KojiBuildState
dist_git_branch: str
nvr: str


@configured_as(job_type=JobType.bodhi_update)
@run_for_comment(command="create-update")
@reacts_to(event=PullRequestCommentPagureEvent)
@reacts_to(event=KojiBuildEvent)
class CreateBodhiUpdateHandler(RetriableJobHandler):
"""
Expand All @@ -63,15 +78,92 @@ def __init__(
)

# lazy properties
self._koji_build_event: Optional[KojiBuildEvent] = None
self._koji_build_event_data: Optional[KojiBuildEventWrapper] = None
self._packit_api: Optional[PackitAPI] = None
self._local_project: Optional[LocalProject] = None

def _build_from_event_dict(self) -> KojiBuildEventWrapper:
koji_build_event = KojiBuildEvent.from_event_dict(self.data.event_dict)
return KojiBuildEventWrapper(
build_id=koji_build_event.build_id,
state=koji_build_event.state,
dist_git_branch=koji_build_event.git_ref,
nvr=koji_build_event.nvr,
)

def _build_from_koji_api(self) -> KojiBuildEventWrapper:
dist_git_branch = self.project.get_pr(self.data.pr_id).target_branch
nvr = DistGit.get_latest_build_in_tag(
downstream_package_name=self.project.repo, dist_git_branch=dist_git_branch
)

session = ClientSession(baseurl=KOJI_BASEURL)
build = session.getBuild(buildInfo=nvr)
Comment on lines +96 to +101
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we could refactor the packit get_latest_build_in_tag method to return the whole build dictionary and not only nvr here and then we would not need to call the Koji API here the second time

return KojiBuildEventWrapper(
build_id=build["build_id"],
state=KojiBuildState.from_number(build["state"]),
dist_git_branch=dist_git_branch,
nvr=nvr,
)

def _build_koji_event_data(self) -> KojiBuildEventWrapper:
if self.data.event_type == PullRequestCommentPagureEvent.__name__:
return self._build_from_koji_api()

return self._build_from_event_dict()

@property
def koji_build_event(self):
if not self._koji_build_event:
self._koji_build_event = KojiBuildEvent.from_event_dict(
self.data.event_dict
def koji_build_event_data(self) -> Optional[KojiBuildEventWrapper]:
if self._koji_build_event_data is None:
self._koji_build_event_data = self._build_koji_event_data()

return self._koji_build_event_data

@property
def packit_api(self) -> Optional[PackitAPI]:
if self._packit_api is None:
self._packit_api = PackitAPI(
self.service_config,
self.job_config,
downstream_local_project=self.local_project,
)

return self._packit_api

@property
def local_project(self) -> LocalProject:
if self._local_project is None:
self._local_project = LocalProject(
git_project=self.project,
working_dir=self.service_config.command_handler_work_dir,
cache=RepositoryCache(
cache_path=self.service_config.repository_cache,
add_new=self.service_config.add_repositories_to_repository_cache,
)
if self.service_config.repository_cache
else None,
)

return self._local_project

def _write_access_to_dist_git_repo_is_not_needed_or_satisfied(self) -> bool:
if self.data.event_type != PullRequestCommentPagureEvent.__name__:
# not needed
return True

user = self.data.actor
logger.debug(
f"Bodhi update will be re-triggered via dist-git PR comment by user {user}."
)

if not self.project.has_write_access(user=user):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we just discussed with Franta and Tomas that here we should check both whether the user is a packager and has write access to the repo, so here we need to call the is_packager(

def is_packager(self, user):
)

logger.info(
f"Re-triggering Bodhi update via dist-git comment in PR#{self.data.pr_id}"
f" and project {self.project.repo} is not allowed for the user: {user}."
)
return self._koji_build_event
return False

return True

def pre_check(self) -> bool:
"""
Expand All @@ -80,15 +172,15 @@ def pre_check(self) -> bool:
By default, we use `fedora-stable` alias.
(Rawhide updates are already created automatically.)
"""
if self.koji_build_event.state != KojiBuildState.complete:
if self.koji_build_event_data.state != KojiBuildState.complete:
logger.debug(
f"Skipping build '{self.koji_build_event.build_id}' "
f"on '{self.koji_build_event.git_ref}'. "
f"Skipping build '{self.koji_build_event_data.build_id}' "
f"on '{self.koji_build_event_data.dist_git_branch}'. "
f"Build not finished yet."
)
return False

if self.koji_build_event.git_ref not in (
if self.koji_build_event_data.dist_git_branch not in (
configured_branches := get_branches(
*(self.job_config.dist_git_branches or {"fedora-stable"}),
default_dg_branch="rawhide", # Koji calls it rawhide, not main
Expand All @@ -99,68 +191,58 @@ def pre_check(self) -> bool:
f"Bodhi update configured only for '{configured_branches}'."
)
return False
return True

def run(self) -> TaskResults:
self.local_project = LocalProject(
git_project=self.project,
working_dir=self.service_config.command_handler_work_dir,
cache=RepositoryCache(
cache_path=self.service_config.repository_cache,
add_new=self.service_config.add_repositories_to_repository_cache,
)
if self.service_config.repository_cache
else None,
return self._write_access_to_dist_git_repo_is_not_needed_or_satisfied()

def _error_message_for_auth_error(self, packit_exception: PackitException) -> str:
body = (
f"Bodhi update creation failed for `{self.koji_build_event_data.nvr}` "
f"because of the missing permissions.\n\n"
f"Please, give {self.service_config.fas_user} user `commit` rights in the "
f"[dist-git settings]({self.data.project_url}/adduser).\n\n"
)
packit_api = PackitAPI(
self.service_config,
self.job_config,
downstream_local_project=self.local_project,

body += (
f"*Try {self.celery_task.retries + 1}/"
f"{self.celery_task.get_retry_limit() + 1}"
)

# Notify user on each task run and set a more generous retry interval
# to let the user fix this issue in the meantime.
if not self.celery_task.is_last_try():
body += (
f": Task will be retried in "
f"{RETRY_INTERVAL_IN_MINUTES_WHEN_USER_ACTION_IS_NEEDED} minutes.*"
)
self.celery_task.retry(
delay=RETRY_INTERVAL_IN_MINUTES_WHEN_USER_ACTION_IS_NEEDED * 60,
ex=packit_exception,
)
else:
body += "*"

return body

def run(self) -> TaskResults:
try:
packit_api.create_update(
dist_git_branch=self.koji_build_event.git_ref,
self.packit_api.create_update(
dist_git_branch=self.koji_build_event_data.dist_git_branch,
update_type="enhancement",
update_notes=DEFAULT_BODHI_NOTE,
koji_builds=[
self.koji_build_event.nvr # it accepts NVRs, not build IDs
self.koji_build_event_data.nvr # it accepts NVRs, not build IDs
],
)
except PackitException as ex:
logger.debug(f"Bodhi update failed to be created: {ex}")

if isinstance(ex.__cause__, AuthError):
body = (
f"Bodhi update creation failed for `{self.koji_build_event.nvr}` "
f"because of the missing permissions.\n\n"
f"Please, give {self.service_config.fas_user} user `commit` rights in the "
f"[dist-git settings]({self.data.project_url}/adduser).\n\n"
)

body += (
f"*Try {self.celery_task.retries + 1}/"
f"{self.celery_task.get_retry_limit() + 1}"
)

# Notify user on each task run and set a more generous retry interval
# to let the user fix this issue in the meantime.
if not self.celery_task.is_last_try():
body += (
f": Task will be retried in "
f"{RETRY_INTERVAL_IN_MINUTES_WHEN_USER_ACTION_IS_NEEDED} minutes.*"
)
self.celery_task.retry(
delay=RETRY_INTERVAL_IN_MINUTES_WHEN_USER_ACTION_IS_NEEDED * 60,
ex=ex,
)
else:
body += "*"

body = self._error_message_for_auth_error(packit_exception=ex)
notify = True
known_error = True
else:
body = (
f"Bodhi update creation failed for `{self.koji_build_event.nvr}`:\n"
f"Bodhi update creation failed for `{self.koji_build_event_data.nvr}`:\n"
"```\n"
f"{ex}\n"
"```"
Expand Down
4 changes: 2 additions & 2 deletions packit_service/worker/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ def is_project_public_or_enabled_private(self) -> bool:

return True

def check_explicit_matching(self):
def check_explicit_matching(self) -> List[JobConfig]:
"""Force explicit event/jobs matching for triggers

Returns:
Expand All @@ -477,7 +477,7 @@ def check_explicit_matching(self):
if isinstance(self.event, PullRequestCommentPagureEvent):
for job in self.event.package_config.jobs:
if (
job.type == JobType.koji_build
job.type in [JobType.koji_build, JobType.bodhi_update]
and job.trigger == JobConfigTriggerType.commit
and self.event.job_config_trigger_type
== JobConfigTriggerType.pull_request
Expand Down
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,9 @@ def koji_build_completed_f35():
def koji_build_completed_epel8():
with open(DATA_DIR / "fedmsg" / "koji_build_completed_epel8.json", "r") as outfile:
return load_the_message_from_file(outfile)


@pytest.fixture()
def pagure_pr_comment_added():
with open(DATA_DIR / "fedmsg" / "pagure_pr_comment.json") as outfile:
return json.load(outfile)
Loading