Skip to content

Commit 577555c

Browse files
Conditions on PR labels (#2333)
Conditions on PR labels TODO: docs Fixes #2269 #2186 RELEASE NOTES BEGIN We have introduced new configuration options require.label.present and require.label.absent. By configuring these you can specify labels that need to be present or absent on a pull request for Packit to react on such PR. RELEASE NOTES END Reviewed-by: František Lachman <[email protected]>
2 parents 3e6737a + 3c53979 commit 577555c

File tree

9 files changed

+242
-17
lines changed

9 files changed

+242
-17
lines changed

packit_service/utils.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
from io import StringIO
77
from logging import StreamHandler
88
from re import search
9-
from typing import List, Tuple
9+
from typing import List, Tuple, Optional
1010

11+
from ogr.abstract import PullRequest
1112
from packit.config import JobConfig, PackageConfig
1213
from packit.schema import JobConfigSchema, PackageConfigSchema
1314
from packit.utils import PackitFormatter
@@ -222,3 +223,33 @@ def get_koji_task_id_and_url_from_stdout(stdout: str):
222223
task_url = task_url_match.group(0)
223224

224225
return task_id, task_url
226+
227+
228+
def pr_labels_match_configuration(
229+
pull_request: Optional[PullRequest],
230+
configured_labels_present: list[str],
231+
configured_labels_absent: list[str],
232+
) -> bool:
233+
"""
234+
Do the PR labels match the configuration of the labels?
235+
"""
236+
if not pull_request:
237+
logger.debug("No PR to check the labels on.")
238+
return True
239+
240+
logger.info(
241+
f"About to check whether PR labels in PR {pull_request.id} "
242+
f"match to the labels configuration "
243+
f"(label.present: {configured_labels_present}, label.absent: {configured_labels_absent})"
244+
)
245+
246+
pr_labels = pull_request.labels
247+
logger.info(f"Labels on PR: {pr_labels}")
248+
249+
return (
250+
not configured_labels_present
251+
or any(label in pr_labels for label in configured_labels_present)
252+
) and (
253+
not configured_labels_absent
254+
or all(label not in pr_labels for label in configured_labels_absent)
255+
)

packit_service/worker/checker/distgit.py

+16
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from ogr.abstract import AccessLevel
88
from packit.config.aliases import get_branches
99
from packit_service.constants import MSG_GET_IN_TOUCH, KojiAllowedAccountsAlias
10+
from packit_service.utils import pr_labels_match_configuration
1011
from packit_service.worker.checker.abstract import Checker, ActorChecker
1112
from packit_service.worker.events import (
1213
PushPagureEvent,
@@ -24,6 +25,21 @@
2425
logger = logging.getLogger(__name__)
2526

2627

28+
class LabelsOnDistgitPR(Checker, GetPagurePullRequestMixin):
29+
def pre_check(self) -> bool:
30+
if self.data.event_type not in (PushPagureEvent.__name__,) or not (
31+
self.job_config.require.label.present
32+
or self.job_config.require.label.absent
33+
):
34+
return True
35+
36+
return pr_labels_match_configuration(
37+
self.pull_request,
38+
self.job_config.require.label.present,
39+
self.job_config.require.label.absent,
40+
)
41+
42+
2743
class PermissionOnDistgit(Checker, GetPagurePullRequestMixin):
2844
def contains_specfile_change(self):
2945
"""

packit_service/worker/events/comment.py

+1-10
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
from logging import getLogger
88
from typing import Dict, Optional, Set
99

10-
from ogr.abstract import Comment, PullRequest, Issue
11-
10+
from ogr.abstract import Comment, Issue
1211
from packit_service.models import TestingFarmResult, BuildStatus
1312
from packit_service.service.db_project_events import (
1413
AddIssueEventToDb,
@@ -70,7 +69,6 @@ def __init__(
7069
self._comment_object = comment_object
7170
self._build_targets_override = build_targets_override
7271
self._tests_targets_override = tests_targets_override
73-
self._pull_request_object = None
7472

7573
@property
7674
def commit_sha(self) -> str: # type:ignore
@@ -79,12 +77,6 @@ def commit_sha(self) -> str: # type:ignore
7977
self._commit_sha = self.project.get_pr(pr_id=self.pr_id).head_commit
8078
return self._commit_sha
8179

82-
@property
83-
def pull_request_object(self) -> PullRequest:
84-
if not self._pull_request_object:
85-
self._pull_request_object = self.project.get_pr(self.pr_id)
86-
return self._pull_request_object
87-
8880
@property
8981
def comment_object(self) -> Optional[Comment]:
9082
if not self._comment_object:
@@ -121,7 +113,6 @@ def get_dict(self, default_dict: Optional[Dict] = None) -> dict:
121113
result["commit_sha"] = self.commit_sha
122114
result.pop("_build_targets_override")
123115
result.pop("_tests_targets_override")
124-
result.pop("_pull_request_object")
125116
return result
126117

127118

packit_service/worker/events/event.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from logging import getLogger
1010
from typing import Dict, Optional, Type, Union, Set, List
1111

12-
from ogr.abstract import GitProject
12+
from ogr.abstract import GitProject, PullRequest
1313
from ogr.parsing import RepoUrl
1414

1515
from packit.config import JobConfigTriggerType, PackageConfig
@@ -461,6 +461,7 @@ def __init__(
461461
self._pr_id = pr_id
462462
self.fail_when_config_file_missing = False
463463
self.actor = actor
464+
self._pull_request_object = None
464465

465466
@property
466467
def project(self):
@@ -491,6 +492,12 @@ def get_db_project_event(self) -> Optional[ProjectEventModel]:
491492
def pr_id(self) -> Optional[int]:
492493
return self._pr_id
493494

495+
@property
496+
def pull_request_object(self) -> Optional[PullRequest]:
497+
if not self._pull_request_object and self.pr_id:
498+
self._pull_request_object = self.project.get_pr(self.pr_id)
499+
return self._pull_request_object
500+
494501
def get_project(self) -> Optional[GitProject]:
495502
if not (self.project_url or self.db_project_object):
496503
return None
@@ -552,6 +559,11 @@ def get_all_build_targets_by_status(
552559
statuses_to_filter_with=statuses_to_filter_with,
553560
)
554561

562+
def get_dict(self, default_dict: Optional[Dict] = None) -> dict:
563+
result = super().get_dict()
564+
result.pop("_pull_request_object")
565+
return result
566+
555567

556568
class AbstractResultEvent(AbstractForgeIndependentEvent):
557569
"""

packit_service/worker/handlers/distgit.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
ValidInformationForPullFromUpstream,
6161
HasIssueCommenterRetriggeringPermissions,
6262
IsUpstreamTagMatchingConfig,
63+
LabelsOnDistgitPR,
6364
)
6465
from packit_service.worker.events import (
6566
PushPagureEvent,
@@ -678,7 +679,11 @@ def __init__(
678679

679680
@staticmethod
680681
def get_checkers() -> Tuple[Type[Checker], ...]:
681-
return (PermissionOnDistgit, HasIssueCommenterRetriggeringPermissions)
682+
return (
683+
LabelsOnDistgitPR,
684+
PermissionOnDistgit,
685+
HasIssueCommenterRetriggeringPermissions,
686+
)
682687

683688
def _get_or_create_koji_group_model(self) -> KojiBuildGroupModel:
684689
if self._koji_group_model_id is not None:

packit_service/worker/jobs.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
COMMENT_REACTION,
2525
PACKIT_VERIFY_FAS_COMMAND,
2626
)
27-
from packit_service.utils import get_packit_commands_from_comment, elapsed_seconds
27+
from packit_service.utils import (
28+
get_packit_commands_from_comment,
29+
elapsed_seconds,
30+
pr_labels_match_configuration,
31+
)
2832
from packit_service.worker.allowlist import Allowlist
2933
from packit_service.worker.events import (
3034
Event,
@@ -39,7 +43,10 @@
3943
AbstractIssueCommentEvent,
4044
AbstractPRCommentEvent,
4145
)
42-
from packit_service.worker.events.event import AbstractResultEvent
46+
from packit_service.worker.events.event import (
47+
AbstractResultEvent,
48+
AbstractForgeIndependentEvent,
49+
)
4350
from packit_service.worker.handlers import (
4451
CoprBuildHandler,
4552
GithubAppInstallationHandler,
@@ -666,6 +673,16 @@ def get_jobs_matching_event(self) -> List[JobConfig]:
666673
for event_type in MANUAL_OR_RESULT_EVENTS
667674
)
668675
)
676+
and (
677+
job.trigger != JobConfigTriggerType.pull_request
678+
or not (job.require.label.present or job.require.label.absent)
679+
or not isinstance(self.event, AbstractForgeIndependentEvent)
680+
or pr_labels_match_configuration(
681+
pull_request=self.event.pull_request_object,
682+
configured_labels_absent=job.require.label.absent,
683+
configured_labels_present=job.require.label.present,
684+
)
685+
)
669686
):
670687
jobs_matching_trigger.append(job)
671688

tests/unit/test_babysit_vm_image.py

+7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from flexmock import flexmock
88
from flexmock import Mock
99
from packit.config.job_config import JobConfigTriggerType, JobType
10+
from packit.config.requirements import RequirementsConfig, LabelRequirementsConfig
1011
from packit_service.config import ServiceConfig
1112
from packit_service.models import (
1213
VMImageBuildTargetModel,
@@ -117,6 +118,12 @@ def test_update_vm_image_build(stop_babysitting, build_status, vm_image_builder_
117118
trigger=JobConfigTriggerType.pull_request,
118119
type=JobType.vm_image_build,
119120
manual_trigger=False,
121+
require=RequirementsConfig(
122+
LabelRequirementsConfig(
123+
absent=[],
124+
present=[],
125+
)
126+
),
120127
)
121128
],
122129
)

tests/unit/test_checkers.py

+61-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from flexmock import flexmock
77

88
from ogr import PagureService
9-
from ogr.abstract import AccessLevel
9+
from ogr.abstract import AccessLevel, PRStatus
1010
from ogr.services.pagure import PagureProject
1111
from packit.config import (
1212
CommonPackageConfig,
@@ -18,6 +18,7 @@
1818
)
1919

2020
from packit.config.commands import TestCommandConfig
21+
from packit.config.requirements import RequirementsConfig, LabelRequirementsConfig
2122
from packit_service.config import ServiceConfig
2223
from packit_service.models import CoprBuildTargetModel
2324
from packit_service.worker.checker.copr import (
@@ -27,6 +28,7 @@
2728
from packit_service.worker.checker.distgit import (
2829
IsUpstreamTagMatchingConfig,
2930
PermissionOnDistgit,
31+
LabelsOnDistgitPR,
3032
)
3133
from packit_service.worker.checker.koji import (
3234
IsJobConfigTriggerMatching as IsJobConfigTriggerMatchingKoji,
@@ -992,3 +994,61 @@ def test_koji_check_allowed_accounts(
992994
package_config, job_config, distgit_push_event.get_dict()
993995
)
994996
assert checker.check_allowed_accounts(allowed_pr_authors, account) == should_pass
997+
998+
999+
@pytest.mark.parametrize(
1000+
"pr_labels,labels_present,labels_absent,should_pass",
1001+
(
1002+
([], [], [], True),
1003+
(["allowed-1"], [], ["skip-ci"], True),
1004+
(["allowed-1"], ["allowed-1"], ["skip-ci"], True),
1005+
(["allowed-1"], ["allowed-1"], ["skip-ci"], True),
1006+
(["allowed-1", "skip-ci"], ["allowed-1"], ["skip-ci"], False),
1007+
),
1008+
)
1009+
def test_labels_on_distgit_pr(
1010+
distgit_push_event,
1011+
pr_labels,
1012+
labels_present,
1013+
labels_absent,
1014+
should_pass,
1015+
):
1016+
jobs = [
1017+
JobConfig(
1018+
type=JobType.koji_build,
1019+
trigger=JobConfigTriggerType.commit,
1020+
packages={
1021+
"package": CommonPackageConfig(
1022+
dist_git_branches=["f36"],
1023+
require=RequirementsConfig(
1024+
LabelRequirementsConfig(
1025+
absent=labels_absent,
1026+
present=labels_present,
1027+
)
1028+
),
1029+
)
1030+
},
1031+
),
1032+
]
1033+
1034+
package_config = PackageConfig(
1035+
jobs=jobs,
1036+
packages={"package": CommonPackageConfig()},
1037+
)
1038+
job_config = jobs[0]
1039+
1040+
flexmock(PagureProject).should_receive("get_pr_list").and_return(
1041+
[
1042+
flexmock(
1043+
id=5,
1044+
head_commit="ad0c308af91da45cf40b253cd82f07f63ea9cbbf",
1045+
status=PRStatus.open,
1046+
labels=pr_labels,
1047+
)
1048+
]
1049+
)
1050+
1051+
checker = LabelsOnDistgitPR(
1052+
package_config, job_config, distgit_push_event.get_dict()
1053+
)
1054+
assert checker.pre_check() == should_pass

0 commit comments

Comments
 (0)