Skip to content

Commit

Permalink
Implement non-PR retriggering (#2589)
Browse files Browse the repository at this point in the history
Implement non-PR retriggering

TODO:

 implement reacting to commit comments (eyes emoji) in ogr

Fixes #1062
RELEASE NOTES BEGIN
We have added support for retriggering jobs that are configured for commit and release trigger. Retriggering can be done via commit comments on the relevant commit, using the usual comments, such as /packit build or /packit test, but the branch or tag need to be specified like this (without the arguments, we will default to commit trigger for the default branch of the repository):
/packit build --commit <branch-name>

or
/packit build --release <tag-name>

RELEASE NOTES END

Reviewed-by: Maja Massarini
Reviewed-by: Laura Barcziová
  • Loading branch information
softwarefactory-project-zuul[bot] authored Oct 23, 2024
2 parents c609251 + 7f79d81 commit 8aa9807
Show file tree
Hide file tree
Showing 17 changed files with 969 additions and 11 deletions.
1 change: 1 addition & 0 deletions packit_service/service/api/webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def interested():
"push": not deleted,
"release": action == "published",
"installation": action == "created",
"commit_comment": action == "created",
}
_interested = interests.get(event, False)

Expand Down
41 changes: 37 additions & 4 deletions packit_service/worker/allowlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
ReleaseEvent,
TestingFarmResultsEvent,
)
from packit_service.worker.events.comment import CommitCommentEvent
from packit_service.worker.events.gitlab import ReleaseGitlabEvent
from packit_service.worker.events.koji import KojiBuildEvent, KojiBuildTagEvent
from packit_service.worker.events.new_hotness import NewHotnessUpdateEvent
Expand Down Expand Up @@ -312,13 +313,18 @@ def _check_release_push_event(
if not project_url:
raise KeyError(f"Failed to get namespace from {type(event)!r}")
if self.is_namespace_or_parent_denied(project_url):
logger.info("Refusing release event on denied repo namespace.")
msg = f"{project_url} or parent namespaces denied!"
project.commit_comment(event.commit_sha, msg)
return False

if self.is_namespace_or_parent_approved(project_url):
return True

logger.info("Refusing release event on not allowlisted repo namespace.")
msg = (
f"Project {project_url} is not on our allowlist! "
"See https://packit.dev/docs/guide/#2-approval"
)
project.commit_comment(event.commit_sha, msg)
return False

def _check_pr_event(
Expand Down Expand Up @@ -438,6 +444,33 @@ def _check_issue_comment_event(
event: Union[IssueCommentEvent, IssueCommentGitlabEvent],
project: GitProject,
job_configs: Iterable[JobConfig],
) -> bool:
return self._check_issue_and_commit_comment_event(
event=event,
project=project,
comment_fn=lambda msg: project.get_issue(event.issue_id).comment(msg),
)

def _check_commit_comment_event(
self,
event: CommitCommentEvent,
project: GitProject,
job_configs: Iterable[JobConfig],
) -> bool:
return self._check_issue_and_commit_comment_event(
event=event,
project=project,
comment_fn=lambda msg: project.commit_comment(
commit=event.commit_sha,
body=msg,
),
)

def _check_issue_and_commit_comment_event(
self,
event: Union[CommitCommentEvent, IssueCommentEvent, IssueCommentGitlabEvent],
project: GitProject,
comment_fn: Callable[[str], Any],
) -> bool:
actor_name = event.actor
if not actor_name:
Expand All @@ -452,7 +485,6 @@ def _check_issue_comment_event(
else:
namespace_approved = self.is_namespace_or_parent_approved(project_url)
user_approved = project.can_merge_pr(actor_name)
# TODO: clear failing check when present
if namespace_approved and user_approved:
return True
msg = (
Expand All @@ -465,7 +497,7 @@ def _check_issue_comment_event(
)

logger.debug(msg)
project.get_issue(event.issue_id).comment(msg)
comment_fn(msg)
return False

def check_and_report(
Expand Down Expand Up @@ -516,6 +548,7 @@ def check_and_report(
IssueCommentEvent,
IssueCommentGitlabEvent,
): self._check_issue_comment_event,
(CommitCommentEvent): self._check_commit_comment_event,
}

# Administrators
Expand Down
143 changes: 141 additions & 2 deletions packit_service/worker/events/comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@
"""
abstract-comment event classes.
"""
import re
from logging import getLogger
from typing import Optional
from typing import Optional, Union

from ogr.abstract import Comment, Issue

from packit_service.models import BuildStatus, TestingFarmResult
from packit_service.models import (
BuildStatus,
GitBranchModel,
ProjectEventModel,
ProjectReleaseModel,
TestingFarmResult,
)
from packit_service.service.db_project_events import (
AddIssueEventToDb,
AddPullRequestEventToDb,
Expand Down Expand Up @@ -183,3 +190,135 @@ def get_dict(self, default_dict: Optional[dict] = None) -> dict:
result["issue_id"] = self.issue_id
result.pop("_issue_object")
return result


class CommitCommentEvent(AbstractCommentEvent):
_trigger: Union[GitBranchModel, ProjectReleaseModel] = None
_event: ProjectEventModel = None

def __init__(
self,
project_url: str,
comment: str,
comment_id: int,
commit_sha: str,
actor: str,
repo_name: str,
repo_namespace: str,
) -> None:
super().__init__(
project_url=project_url,
comment=comment,
comment_id=comment_id,
)
self.repo_name = repo_name
self.repo_namespace = repo_namespace
self.actor = actor
self.commit_sha = commit_sha
self._tag_name: Optional[str] = None
self._branch: Optional[str] = None

@property
def identifier(self) -> Optional[str]:
return self.tag_name or self.branch

@property
def git_ref(self) -> Optional[str]:
return self.tag_name or self.branch

@property
def tag_name(self) -> Optional[str]:
if not self._tag_name and "--release" in self.comment:
release_match = re.search(r"--release[\s=](?P<release>\S+)", self.comment)
if release_match:
self._tag_name = release_match.group("release")
return self._tag_name

@property
def branch(self) -> Optional[str]:
if not self._branch and "--release" not in self.comment:
if "--commit" in self.comment:
commit_match = re.search(r"--commit[\s=](?P<commit>\S+)", self.comment)
if commit_match:
self._branch = commit_match.group("commit")
else:
self._branch = self.project.default_branch
return self._branch

@property
def comment_object(self) -> Optional[Comment]:
if not self._comment_object:
self._comment_object = self.project.get_commit_comment(
self.commit_sha,
self.comment_id,
)
return self._comment_object

def _add_release_trigger(self):
try:
release = self.project.get_release(tag_name=self.tag_name)
except Exception:
logger.debug(f"Release with tag name {self.tag_name} not found.")
return

if not release or release.git_tag.commit_sha != self.commit_sha:
logger.debug(
"Release with tag name from comment doesn't exist or doesn't match "
"the commit SHA.",
)
return

self._trigger, self._event = ProjectEventModel.add_release_event(
tag_name=self.tag_name,
namespace=self.repo_namespace,
repo_name=self.repo_name,
project_url=self.project_url,
commit_hash=self.commit_sha,
)

def _add_commit_trigger(self):
try:
commits = self.project.get_commits(self.branch)
except Exception:
commits = []
if self.commit_sha not in commits:
logger.debug(
f"Branch {self.branch} doesn't exist or doesn't "
f"contain the commit where the comment was triggered on.",
)
return

self._trigger, self._event = ProjectEventModel.add_branch_push_event(
branch_name=self.branch,
namespace=self.repo_namespace,
repo_name=self.repo_name,
project_url=self.project_url,
commit_sha=self.commit_sha,
)

def _add_trigger_and_event(self):
if not self._event or not self._trigger:
if self.tag_name:
self._add_release_trigger()
elif self.branch:
self._add_commit_trigger()
return self._trigger, self._event

@property
def db_project_object(self) -> Union[GitBranchModel, ProjectReleaseModel]:
(trigger, _) = self._add_trigger_and_event()
return trigger

@property
def db_project_event(self) -> ProjectEventModel:
(_, event) = self._add_trigger_and_event()
return event

def get_dict(self, default_dict: Optional[dict] = None) -> dict:
result = super().get_dict() # type: ignore
result.pop("_trigger", None)
result.pop("_event", None)
result["git_ref"] = self.git_ref
result["identifier"] = self.identifier
result["tag_name"] = self.tag_name
return result
27 changes: 27 additions & 0 deletions packit_service/worker/events/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,33 @@ def _add_project_object_and_event(self):
commit_sha=self.event_dict.get("commit_sha"),
)
)
elif self.event_type in {
"CommitCommentGithubEvent",
"CommitCommentGitlabEvent",
}:
if self.tag_name:
(
self._db_project_object,
self._db_project_event,
) = ProjectEventModel.add_release_event(
tag_name=self.tag_name,
namespace=self.project.namespace,
repo_name=self.project.repo,
project_url=self.project_url,
commit_hash=self.commit_sha,
)
else:
(
self._db_project_object,
self._db_project_event,
) = ProjectEventModel.add_branch_push_event(
branch_name=self.git_ref,
namespace=self.project.namespace,
repo_name=self.project.repo,
project_url=self.project_url,
commit_sha=self.commit_sha,
)

else:
logger.warning(
"We don't know, what to search in the database for this event data.",
Expand Down
5 changes: 5 additions & 0 deletions packit_service/worker/events/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from packit_service.worker.events.comment import (
AbstractIssueCommentEvent,
AbstractPRCommentEvent,
CommitCommentEvent,
)
from packit_service.worker.events.enums import (
IssueCommentAction,
Expand Down Expand Up @@ -409,3 +410,7 @@ def project(self):

def get_project(self):
return None


class CommitCommentGithubEvent(CommitCommentEvent):
pass
5 changes: 5 additions & 0 deletions packit_service/worker/events/gitlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from packit_service.worker.events.comment import (
AbstractIssueCommentEvent,
AbstractPRCommentEvent,
CommitCommentEvent,
)
from packit_service.worker.events.enums import GitlabEventAction
from packit_service.worker.events.event import AbstractForgeIndependentEvent
Expand Down Expand Up @@ -251,3 +252,7 @@ def __init__(
self.commit_sha = commit_sha
self.title = title
self.message = message


class CommitCommentGitlabEvent(CommitCommentEvent):
pass
2 changes: 2 additions & 0 deletions packit_service/worker/handlers/copr.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
ReleaseEvent,
ReleaseGitlabEvent,
)
from packit_service.worker.events.comment import CommitCommentEvent
from packit_service.worker.handlers.abstract import (
JobHandler,
RetriableJobHandler,
Expand Down Expand Up @@ -94,6 +95,7 @@
@reacts_to(CheckRerunPullRequestEvent)
@reacts_to(CheckRerunCommitEvent)
@reacts_to(CheckRerunReleaseEvent)
@reacts_to(CommitCommentEvent)
class CoprBuildHandler(
RetriableJobHandler,
ConfigFromEventMixin,
Expand Down
2 changes: 2 additions & 0 deletions packit_service/worker/handlers/testing_farm.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
ReleaseGitlabEvent,
TestingFarmResultsEvent,
)
from packit_service.worker.events.comment import CommitCommentEvent
from packit_service.worker.handlers import JobHandler
from packit_service.worker.handlers.abstract import (
RetriableJobHandler,
Expand Down Expand Up @@ -84,6 +85,7 @@
@reacts_to(AbstractPRCommentEvent)
@reacts_to(CheckRerunPullRequestEvent)
@reacts_to(CheckRerunCommitEvent)
@reacts_to(CommitCommentEvent)
@configured_as(job_type=JobType.tests)
class TestingFarmHandler(
RetriableJobHandler,
Expand Down
4 changes: 4 additions & 0 deletions packit_service/worker/helpers/testing_farm.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
PushGitHubEvent,
PushGitlabEvent,
)
from packit_service.worker.events.github import CommitCommentGithubEvent
from packit_service.worker.events.gitlab import CommitCommentGitlabEvent
from packit_service.worker.helpers.build import CoprBuildJobHelper
from packit_service.worker.reporting import BaseCommitStatus
from packit_service.worker.result import TaskResults
Expand Down Expand Up @@ -357,6 +359,8 @@ def build_required(self) -> bool:
PushGitlabEvent.__name__,
PullRequestGithubEvent.__name__,
MergeRequestGitlabEvent.__name__,
CommitCommentGithubEvent.__name__,
CommitCommentGitlabEvent.__name__,
)
or self.is_copr_build_comment_event()
)
Expand Down
3 changes: 2 additions & 1 deletion packit_service/worker/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
AbstractForgeIndependentEvent,
AbstractResultEvent,
)
from packit_service.worker.events.gitlab import CommitCommentGitlabEvent
from packit_service.worker.events.koji import KojiBuildTagEvent
from packit_service.worker.handlers import (
CoprBuildHandler,
Expand Down Expand Up @@ -798,7 +799,7 @@ def get_handlers_for_comment_and_rerun_event(self) -> set[type[JobHandler]]:

if handlers_triggered_by_job and not isinstance(
self.event,
PullRequestCommentPagureEvent,
(PullRequestCommentPagureEvent, CommitCommentGitlabEvent),
):
self.event.comment_object.add_reaction(COMMENT_REACTION)

Expand Down
Loading

0 comments on commit 8aa9807

Please sign in to comment.