Merge ~ines-almeida/launchpad:webhook-patterns/update-models into launchpad:master

Proposed by Ines Almeida
Status: Merged
Approved by: Ines Almeida
Approved revision: 7b067caf1d31c625c447c9cd8932308b3388413d
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~ines-almeida/launchpad:webhook-patterns/update-models
Merge into: launchpad:master
Diff against target: 543 lines (+224/-21)
9 files modified
lib/lp/code/interfaces/cibuild.py (+13/-1)
lib/lp/code/model/cibuild.py (+51/-12)
lib/lp/code/model/tests/test_cibuild.py (+53/-2)
lib/lp/services/webhooks/browser.py (+1/-0)
lib/lp/services/webhooks/interfaces.py (+27/-2)
lib/lp/services/webhooks/model.py (+35/-3)
lib/lp/services/webhooks/tests/test_model.py (+41/-1)
lib/lp/services/webhooks/tests/test_webservice.py (+1/-0)
lib/lp/testing/factory.py (+2/-0)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+446948@code.launchpad.net

Commit message

Update Webhook and CIBuild models with the new fields for webhook pattern filtering

`git_ref_pattern` field and `check_webhook_git_ref_pattern` function added to Webhook model to enable filtering webhook triggers from git repository according to their git refs
`git_refs` added to CIBuild model will be used to store the git refs that originate the builds

Description of the change

This is the preparation work needed to filter git repo webhooks: https://warthogs.atlassian.net/browse/LP-1151

This MP adds 3 main things:
 - `git_ref_pattern` field to Webhook model - to store the pattern used to filter webhooks
 - `checkGitRefPattern` endpoint to Webhook model - to check if a git ref matches the ref_pattern of a given webhook
 - `git_refs` field to CIBuild model - to store the git refs that originate a given build, to later be used to check against the webhooks ref_pattern

The final functionality to actually filter webhooks according to the `ref_pattern` will be added in another MP.

To post a comment you must log in.
Revision history for this message
Ines Almeida (ines-almeida) wrote :

Will update `ref_pattern` to `git_ref_pattern` as I also just changed it in the DB MP. I will wait for the approval of that MP before making that change.

This MP can still be reviewed with that in mind.

https://code.launchpad.net/~ines-almeida/launchpad/+git/launchpad/+merge/446950

Revision history for this message
Ines Almeida (ines-almeida) wrote :

> Will update `ref_pattern` to `git_ref_pattern` as I also just changed it in
> the DB MP. I will wait for the approval of that MP before making that change.

Done

Revision history for this message
Colin Watson (cjwatson) :
review: Approve
Revision history for this message
Ines Almeida (ines-almeida) :
Revision history for this message
Colin Watson (cjwatson) :
Revision history for this message
Ines Almeida (ines-almeida) wrote :

Addressed all comments and cleaned up commit tree. Will merge after checks pass

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/code/interfaces/cibuild.py b/lib/lp/code/interfaces/cibuild.py
index bac6181..472b67e 100644
--- a/lib/lp/code/interfaces/cibuild.py
+++ b/lib/lp/code/interfaces/cibuild.py
@@ -105,6 +105,15 @@ class ICIBuildView(IPackageBuildView, IPrivacy):
105 )105 )
106 )106 )
107107
108 git_refs = exported(
109 List(
110 TextLine(),
111 title=_("The git references that originated this CI Build."),
112 required=False,
113 readonly=True,
114 )
115 )
116
108 distro_arch_series = exported(117 distro_arch_series = exported(
109 Reference(118 Reference(
110 IDistroArchSeries,119 IDistroArchSeries,
@@ -274,6 +283,7 @@ class ICIBuildSet(ISpecificBuildFarmJobSource):
274 distro_arch_series,283 distro_arch_series,
275 stages,284 stages,
276 date_created=DEFAULT,285 date_created=DEFAULT,
286 git_refs=None,
277 ):287 ):
278 """Create an `ICIBuild`."""288 """Create an `ICIBuild`."""
279289
@@ -285,7 +295,9 @@ class ICIBuildSet(ISpecificBuildFarmJobSource):
285 these Git commit IDs.295 these Git commit IDs.
286 """296 """
287297
288 def requestBuild(git_repository, commit_sha1, distro_arch_series, stages):298 def requestBuild(
299 git_repository, commit_sha1, distro_arch_series, stages, git_refs=None
300 ):
289 """Request a CI build.301 """Request a CI build.
290302
291 This checks that the architecture is allowed and that there isn't303 This checks that the architecture is allowed and that there isn't
diff --git a/lib/lp/code/model/cibuild.py b/lib/lp/code/model/cibuild.py
index 1cf5a19..0634d50 100644
--- a/lib/lp/code/model/cibuild.py
+++ b/lib/lp/code/model/cibuild.py
@@ -14,7 +14,16 @@ from operator import itemgetter
1414
15from lazr.lifecycle.event import ObjectCreatedEvent15from lazr.lifecycle.event import ObjectCreatedEvent
16from storm.databases.postgres import JSON16from storm.databases.postgres import JSON
17from storm.locals import Bool, DateTime, Desc, Int, Reference, Store, Unicode17from storm.locals import (
18 Bool,
19 DateTime,
20 Desc,
21 Int,
22 List,
23 Reference,
24 Store,
25 Unicode,
26)
18from storm.store import EmptyResultSet27from storm.store import EmptyResultSet
19from zope.component import getUtility28from zope.component import getUtility
20from zope.event import notify29from zope.event import notify
@@ -171,12 +180,14 @@ def determine_DASes_to_build(configuration, logger=None):
171180
172181
173def get_all_commits_for_paths(git_repository, paths):182def get_all_commits_for_paths(git_repository, paths):
174 return [183 commits = {}
175 ref.commit_sha1184 for ref in GitRef.findByReposAndPaths(
176 for ref in GitRef.findByReposAndPaths(185 [(git_repository, ref_path) for ref_path in paths]
177 [(git_repository, ref_path) for ref_path in paths]186 ).values():
178 ).values()187 if ref.commit_sha1 not in commits:
179 ]188 commits[ref.commit_sha1] = []
189 commits[ref.commit_sha1].append(ref.path)
190 return commits
180191
181192
182def parse_configuration(git_repository, blob):193def parse_configuration(git_repository, blob):
@@ -204,6 +215,7 @@ class CIBuild(PackageBuildMixin, StormBase):
204 git_repository = Reference(git_repository_id, "GitRepository.id")215 git_repository = Reference(git_repository_id, "GitRepository.id")
205216
206 commit_sha1 = Unicode(name="commit_sha1", allow_none=False)217 commit_sha1 = Unicode(name="commit_sha1", allow_none=False)
218 git_refs = List(name="git_refs", allow_none=True)
207219
208 distro_arch_series_id = Int(name="distro_arch_series", allow_none=False)220 distro_arch_series_id = Int(name="distro_arch_series", allow_none=False)
209 distro_arch_series = Reference(221 distro_arch_series = Reference(
@@ -261,12 +273,14 @@ class CIBuild(PackageBuildMixin, StormBase):
261 builder_constraints,273 builder_constraints,
262 stages,274 stages,
263 date_created=DEFAULT,275 date_created=DEFAULT,
276 git_refs=None,
264 ):277 ):
265 """Construct a `CIBuild`."""278 """Construct a `CIBuild`."""
266 super().__init__()279 super().__init__()
267 self.build_farm_job = build_farm_job280 self.build_farm_job = build_farm_job
268 self.git_repository = git_repository281 self.git_repository = git_repository
269 self.commit_sha1 = commit_sha1282 self.commit_sha1 = commit_sha1
283 self.git_refs = git_refs
270 self.distro_arch_series = distro_arch_series284 self.distro_arch_series = distro_arch_series
271 self.processor = processor285 self.processor = processor
272 self.virtualized = virtualized286 self.virtualized = virtualized
@@ -655,6 +669,7 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
655 commit_sha1,669 commit_sha1,
656 distro_arch_series,670 distro_arch_series,
657 stages,671 stages,
672 git_refs=None,
658 date_created=DEFAULT,673 date_created=DEFAULT,
659 ):674 ):
660 """See `ICIBuildSet`."""675 """See `ICIBuildSet`."""
@@ -674,6 +689,7 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
674 ),689 ),
675 stages=stages,690 stages=stages,
676 date_created=date_created,691 date_created=date_created,
692 git_refs=git_refs,
677 )693 )
678 store.add(cibuild)694 store.add(cibuild)
679 store.flush()695 store.flush()
@@ -712,7 +728,12 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
712 ) is not None and self._isBuildableArchitectureAllowed(das)728 ) is not None and self._isBuildableArchitectureAllowed(das)
713729
714 def requestBuild(730 def requestBuild(
715 self, git_repository, commit_sha1, distro_arch_series, stages731 self,
732 git_repository,
733 commit_sha1,
734 distro_arch_series,
735 stages,
736 git_refs=None,
716 ):737 ):
717 """See `ICIBuildSet`."""738 """See `ICIBuildSet`."""
718 pocket = PackagePublishingPocket.UPDATES739 pocket = PackagePublishingPocket.UPDATES
@@ -726,17 +747,32 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
726 CIBuild.distro_arch_series == distro_arch_series,747 CIBuild.distro_arch_series == distro_arch_series,
727 )748 )
728 if not result.is_empty():749 if not result.is_empty():
750 # We append the new git_refs to existing builds here to keep the
751 # git_refs list up-to-date, and potentially filter git repository
752 # webhooks by their git refs if the status of the build changes
753 if git_refs:
754 for cibuild in result:
755 if cibuild.git_refs is None:
756 cibuild.git_refs = []
757 cibuild.git_refs.extend(git_refs)
729 raise CIBuildAlreadyRequested758 raise CIBuildAlreadyRequested
730759
731 build = self.new(760 build = self.new(
732 git_repository, commit_sha1, distro_arch_series, stages761 git_repository, commit_sha1, distro_arch_series, stages, git_refs
733 )762 )
734 build.queueBuild()763 build.queueBuild()
735 notify(ObjectCreatedEvent(build))764 notify(ObjectCreatedEvent(build))
736 return build765 return build
737766
738 def _tryToRequestBuild(767 def _tryToRequestBuild(
739 self, git_repository, commit_sha1, configuration, das, stages, logger768 self,
769 git_repository,
770 commit_sha1,
771 configuration,
772 das,
773 stages,
774 logger,
775 git_refs=None,
740 ):776 ):
741 try:777 try:
742 if logger is not None:778 if logger is not None:
@@ -746,7 +782,9 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
746 das.distroseries.name,782 das.distroseries.name,
747 das.architecturetag,783 das.architecturetag,
748 )784 )
749 build = self.requestBuild(git_repository, commit_sha1, das, stages)785 build = self.requestBuild(
786 git_repository, commit_sha1, das, stages, git_refs
787 )
750 # Create reports for each individual job in this build so that788 # Create reports for each individual job in this build so that
751 # they show up as pending in the web UI. The job names789 # they show up as pending in the web UI. The job names
752 # generated here should match those generated by790 # generated here should match those generated by
@@ -778,7 +816,7 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
778 # getCommits performs a web request!816 # getCommits performs a web request!
779 commits = getUtility(IGitHostingClient).getCommits(817 commits = getUtility(IGitHostingClient).getCommits(
780 git_repository.getInternalPath(),818 git_repository.getInternalPath(),
781 commit_sha1s,819 list(commit_sha1s),
782 # XXX cjwatson 2022-01-19: We should also fetch820 # XXX cjwatson 2022-01-19: We should also fetch
783 # debian/.launchpad.yaml (or perhaps make the path a property of821 # debian/.launchpad.yaml (or perhaps make the path a property of
784 # the repository) once lpci and launchpad-buildd support using822 # the repository) once lpci and launchpad-buildd support using
@@ -814,6 +852,7 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
814 das,852 das,
815 stages[das.architecturetag],853 stages[das.architecturetag],
816 logger,854 logger,
855 git_refs=commit_sha1s.get(commit["sha1"]),
817 )856 )
818857
819 def getByID(self, build_id):858 def getByID(self, build_id):
diff --git a/lib/lp/code/model/tests/test_cibuild.py b/lib/lp/code/model/tests/test_cibuild.py
index c49024a..bc3fd83 100644
--- a/lib/lp/code/model/tests/test_cibuild.py
+++ b/lib/lp/code/model/tests/test_cibuild.py
@@ -94,7 +94,7 @@ class TestGetAllCommitsForPaths(TestCaseWithFactory):
9494
95 rv = get_all_commits_for_paths(repository, ref_paths)95 rv = get_all_commits_for_paths(repository, ref_paths)
9696
97 self.assertEqual([], rv)97 self.assertEqual({}, rv)
9898
99 def test_one_ref_one_path(self):99 def test_one_ref_one_path(self):
100 repository = self.factory.makeGitRepository()100 repository = self.factory.makeGitRepository()
@@ -104,7 +104,7 @@ class TestGetAllCommitsForPaths(TestCaseWithFactory):
104 rv = get_all_commits_for_paths(repository, ref_paths)104 rv = get_all_commits_for_paths(repository, ref_paths)
105105
106 self.assertEqual(1, len(rv))106 self.assertEqual(1, len(rv))
107 self.assertEqual(ref.commit_sha1, rv[0])107 self.assertIn(ref.commit_sha1, rv)
108108
109 def test_multiple_refs_and_paths(self):109 def test_multiple_refs_and_paths(self):
110 repository = self.factory.makeGitRepository()110 repository = self.factory.makeGitRepository()
@@ -781,6 +781,56 @@ class TestCIBuildSet(TestCaseWithFactory):
781 self.assertIsNone(build_queue.builder_constraints)781 self.assertIsNone(build_queue.builder_constraints)
782 self.assertEqual(BuildQueueStatus.WAITING, build_queue.status)782 self.assertEqual(BuildQueueStatus.WAITING, build_queue.status)
783783
784 def test_requestCIBuild_with_git_refs(self):
785 # requestBuild creates a new CIBuild with the given git_refs
786 repository = self.factory.makeGitRepository()
787 commit_sha1 = hashlib.sha1(self.factory.getUniqueBytes()).hexdigest()
788 das = self.factory.makeBuildableDistroArchSeries()
789 stages = [[("build", 0)]]
790
791 git_refs = ["ref/test", "ref/foo"]
792 build = getUtility(ICIBuildSet).requestBuild(
793 repository, commit_sha1, das, stages, git_refs
794 )
795
796 self.assertTrue(ICIBuild.providedBy(build))
797 self.assertThat(
798 build,
799 MatchesStructure.byEquality(
800 git_repository=repository,
801 commit_sha1=commit_sha1,
802 distro_arch_series=das,
803 stages=stages,
804 status=BuildStatus.NEEDSBUILD,
805 ),
806 )
807 store = Store.of(build)
808 store.flush()
809 build_queue = store.find(
810 BuildQueue,
811 BuildQueue._build_farm_job_id
812 == removeSecurityProxy(build).build_farm_job_id,
813 ).one()
814 self.assertProvides(build_queue, IBuildQueue)
815 self.assertTrue(build_queue.virtualized)
816 self.assertIsNone(build_queue.builder_constraints)
817 self.assertEqual(BuildQueueStatus.WAITING, build_queue.status)
818 self.assertEqual(git_refs, build.git_refs)
819
820 # Rescheduling a build for the same commit_sha1 raises an error, but
821 # git_refs of the build are updated
822 self.assertRaises(
823 CIBuildAlreadyRequested,
824 getUtility(ICIBuildSet).requestBuild,
825 repository,
826 commit_sha1,
827 das,
828 stages,
829 ["ref/bar"],
830 )
831 git_refs.append("ref/bar")
832 self.assertEqual(git_refs, build.git_refs)
833
784 def test_requestBuild_score(self):834 def test_requestBuild_score(self):
785 # CI builds have an initial queue score of 2600.835 # CI builds have an initial queue score of 2600.
786 repository = self.factory.makeGitRepository()836 repository = self.factory.makeGitRepository()
@@ -1022,6 +1072,7 @@ class TestCIBuildSet(TestCaseWithFactory):
1022 )1072 )
1023 ),1073 ),
1024 )1074 )
1075 self.assertEqual(build.git_refs, ref_paths)
10251076
1026 def test_requestBuildsForRefs_multiple_architectures(self):1077 def test_requestBuildsForRefs_multiple_architectures(self):
1027 ubuntu = getUtility(ILaunchpadCelebrities).ubuntu1078 ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
diff --git a/lib/lp/services/webhooks/browser.py b/lib/lp/services/webhooks/browser.py
index bb53917..991d7ef 100644
--- a/lib/lp/services/webhooks/browser.py
+++ b/lib/lp/services/webhooks/browser.py
@@ -134,6 +134,7 @@ class WebhookAddView(LaunchpadFormView):
134 event_types=data["event_types"],134 event_types=data["event_types"],
135 active=data["active"],135 active=data["active"],
136 secret=data["secret"],136 secret=data["secret"],
137 git_ref_pattern=data.get("git_ref_pattern"),
137 )138 )
138 self.next_url = canonical_url(webhook)139 self.next_url = canonical_url(webhook)
139140
diff --git a/lib/lp/services/webhooks/interfaces.py b/lib/lp/services/webhooks/interfaces.py
index b00d45f..9b32ba1 100644
--- a/lib/lp/services/webhooks/interfaces.py
+++ b/lib/lp/services/webhooks/interfaces.py
@@ -176,6 +176,18 @@ class IWebhook(Interface):
176 )176 )
177 )177 )
178178
179 git_ref_pattern = exported(
180 TextLine(
181 title=_("Git ref pattern"),
182 required=False,
183 description=_(
184 "Pattern to match against git-ref/branch name of an event, "
185 "to filter webhook triggers"
186 ),
187 max_length=200,
188 )
189 )
190
179 def getDelivery(id):191 def getDelivery(id):
180 """Retrieve a delivery by ID, or None if it doesn't exist."""192 """Retrieve a delivery by ID, or None if it doesn't exist."""
181193
@@ -197,7 +209,15 @@ class IWebhook(Interface):
197209
198210
199class IWebhookSet(Interface):211class IWebhookSet(Interface):
200 def new(target, registrant, delivery_url, event_types, active, secret):212 def new(
213 target,
214 registrant,
215 delivery_url,
216 event_types,
217 active,
218 secret,
219 git_ref_pattern=None,
220 ):
201 """Create a new webhook."""221 """Create a new webhook."""
202222
203 def delete(hooks):223 def delete(hooks):
@@ -250,7 +270,12 @@ class IWebhookTarget(Interface):
250 )270 )
251 @operation_for_version("devel")271 @operation_for_version("devel")
252 def newWebhook(272 def newWebhook(
253 registrant, delivery_url, event_types, active=True, secret=None273 registrant,
274 delivery_url,
275 event_types,
276 active=True,
277 secret=None,
278 git_ref_pattern=None,
254 ):279 ):
255 """Create a new webhook."""280 """Create a new webhook."""
256281
diff --git a/lib/lp/services/webhooks/model.py b/lib/lp/services/webhooks/model.py
index bafd604..d5e1267 100644
--- a/lib/lp/services/webhooks/model.py
+++ b/lib/lp/services/webhooks/model.py
@@ -12,6 +12,7 @@ import ipaddress
12import re12import re
13import socket13import socket
14from datetime import datetime, timedelta, timezone14from datetime import datetime, timedelta, timezone
15from fnmatch import fnmatch
15from urllib.parse import urlsplit16from urllib.parse import urlsplit
1617
17import iso860118import iso8601
@@ -66,6 +67,15 @@ def webhook_modified(webhook, event):
66 removeSecurityProxy(webhook).date_last_modified = UTC_NOW67 removeSecurityProxy(webhook).date_last_modified = UTC_NOW
6768
6869
70def check_webhook_git_ref_pattern(webhook: IWebhook, git_ref: str):
71 """Check if a given git ref matches against the webhook's
72 `git_ref_pattern` if it has one (only Git Repository webhooks can have
73 a `git_ref_pattern` value)"""
74 if not webhook.git_ref_pattern:
75 return True
76 return fnmatch(git_ref, webhook.git_ref_pattern)
77
78
69@implementer(IWebhook)79@implementer(IWebhook)
70class Webhook(StormBase):80class Webhook(StormBase):
71 """See `IWebhook`."""81 """See `IWebhook`."""
@@ -114,6 +124,8 @@ class Webhook(StormBase):
114124
115 json_data = JSON(name="json_data")125 json_data = JSON(name="json_data")
116126
127 git_ref_pattern = Unicode(allow_none=True)
128
117 @property129 @property
118 def target(self):130 def target(self):
119 if self.git_repository is not None:131 if self.git_repository is not None:
@@ -192,7 +204,14 @@ class WebhookSet:
192 """See `IWebhookSet`."""204 """See `IWebhookSet`."""
193205
194 def new(206 def new(
195 self, target, registrant, delivery_url, event_types, active, secret207 self,
208 target,
209 registrant,
210 delivery_url,
211 event_types,
212 active,
213 secret,
214 git_ref_pattern=None,
196 ):215 ):
197 from lp.charms.interfaces.charmrecipe import ICharmRecipe216 from lp.charms.interfaces.charmrecipe import ICharmRecipe
198 from lp.code.interfaces.branch import IBranch217 from lp.code.interfaces.branch import IBranch
@@ -233,6 +252,7 @@ class WebhookSet:
233 hook.delivery_url = delivery_url252 hook.delivery_url = delivery_url
234 hook.active = active253 hook.active = active
235 hook.secret = secret254 hook.secret = secret
255 hook.git_ref_pattern = git_ref_pattern
236 hook.event_types = event_types256 hook.event_types = event_types
237 IStore(Webhook).add(hook)257 IStore(Webhook).add(hook)
238 IStore(Webhook).flush()258 IStore(Webhook).flush()
@@ -346,10 +366,22 @@ class WebhookTargetMixin:
346 return self.valid_webhook_event_types366 return self.valid_webhook_event_types
347367
348 def newWebhook(368 def newWebhook(
349 self, registrant, delivery_url, event_types, active=True, secret=None369 self,
370 registrant,
371 delivery_url,
372 event_types,
373 active=True,
374 secret=None,
375 git_ref_pattern=None,
350 ):376 ):
351 return getUtility(IWebhookSet).new(377 return getUtility(IWebhookSet).new(
352 self, registrant, delivery_url, event_types, active, secret378 self,
379 registrant,
380 delivery_url,
381 event_types,
382 active,
383 secret,
384 git_ref_pattern,
353 )385 )
354386
355387
diff --git a/lib/lp/services/webhooks/tests/test_model.py b/lib/lp/services/webhooks/tests/test_model.py
index ede094c..7053899 100644
--- a/lib/lp/services/webhooks/tests/test_model.py
+++ b/lib/lp/services/webhooks/tests/test_model.py
@@ -20,7 +20,11 @@ from lp.services.features.testing import FeatureFixture
20from lp.services.webapp.authorization import check_permission20from lp.services.webapp.authorization import check_permission
21from lp.services.webapp.snapshot import notify_modified21from lp.services.webapp.snapshot import notify_modified
22from lp.services.webhooks.interfaces import IWebhookSet22from lp.services.webhooks.interfaces import IWebhookSet
23from lp.services.webhooks.model import WebhookJob, WebhookSet23from lp.services.webhooks.model import (
24 WebhookJob,
25 WebhookSet,
26 check_webhook_git_ref_pattern,
27)
24from lp.soyuz.interfaces.livefs import (28from lp.soyuz.interfaces.livefs import (
25 LIVEFS_FEATURE_FLAG,29 LIVEFS_FEATURE_FLAG,
26 LIVEFS_WEBHOOKS_FEATURE_FLAG,30 LIVEFS_WEBHOOKS_FEATURE_FLAG,
@@ -71,6 +75,40 @@ class TestWebhook(TestCaseWithFactory):
71 webhook.event_types = ["foo", [1]]75 webhook.event_types = ["foo", [1]]
72 self.assertContentEqual([], webhook.event_types)76 self.assertContentEqual([], webhook.event_types)
7377
78 def test_check_webhook_git_ref_pattern(self):
79 # See lib/lp/code/templates/gitrepository-permissions.pt for an
80 # explanation of the wildcards logic
81 git_ref = "refs/heads/foo-test"
82 expected_results = {
83 None: True,
84 "": True,
85 "*": True,
86 "*foo*": True,
87 "foo": False,
88 "foo-test": False, # it needs to match the full git ref
89 "foo*": False,
90 "*foo": False,
91 "*foo?test": True,
92 "*foo[-_.]test": True,
93 "*foo![-]test": False,
94 "*bar*": False,
95 "refs/heads/*": True, # this should match all branches (not tags)
96 "refs/heads/foo*": True,
97 "refs/heads/bar*": False,
98 "refs/heads/*-test": True,
99 }
100
101 results = dict()
102 webhook = self.factory.makeWebhook()
103 with admin_logged_in():
104 for pattern in expected_results:
105 webhook.git_ref_pattern = pattern
106 results[pattern] = check_webhook_git_ref_pattern(
107 webhook, git_ref
108 )
109
110 self.assertEqual(expected_results, results)
111
74112
75class TestWebhookPermissions(TestCaseWithFactory):113class TestWebhookPermissions(TestCaseWithFactory):
76 layer = DatabaseFunctionalLayer114 layer = DatabaseFunctionalLayer
@@ -112,6 +150,7 @@ class TestWebhookPermissions(TestCaseWithFactory):
112 "secret",150 "secret",
113 "setSecret",151 "setSecret",
114 "target",152 "target",
153 "git_ref_pattern",
115 },154 },
116 }155 }
117 webhook = self.factory.makeWebhook()156 webhook = self.factory.makeWebhook()
@@ -128,6 +167,7 @@ class TestWebhookPermissions(TestCaseWithFactory):
128 "event_types",167 "event_types",
129 "registrant_id",168 "registrant_id",
130 "secret",169 "secret",
170 "git_ref_pattern",
131 },171 },
132 }172 }
133 webhook = self.factory.makeWebhook()173 webhook = self.factory.makeWebhook()
diff --git a/lib/lp/services/webhooks/tests/test_webservice.py b/lib/lp/services/webhooks/tests/test_webservice.py
index 810a83d..f7d9d0b 100644
--- a/lib/lp/services/webhooks/tests/test_webservice.py
+++ b/lib/lp/services/webhooks/tests/test_webservice.py
@@ -78,6 +78,7 @@ class TestWebhook(TestCaseWithFactory):
78 "self_link",78 "self_link",
79 "target_link",79 "target_link",
80 "web_link",80 "web_link",
81 "git_ref_pattern",
81 ),82 ),
82 )83 )
8384
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index 11d337b..2ac51bc 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -6163,6 +6163,7 @@ class LaunchpadObjectFactory(ObjectFactory):
6163 secret=None,6163 secret=None,
6164 active=True,6164 active=True,
6165 event_types=None,6165 event_types=None,
6166 git_ref_pattern=None,
6166 ):6167 ):
6167 if target is None:6168 if target is None:
6168 target = self.makeGitRepository()6169 target = self.makeGitRepository()
@@ -6175,6 +6176,7 @@ class LaunchpadObjectFactory(ObjectFactory):
6175 event_types or [],6176 event_types or [],
6176 active,6177 active,
6177 secret,6178 secret,
6179 git_ref_pattern,
6178 )6180 )
61796181
6180 def makeSnap(6182 def makeSnap(

Subscribers

People subscribed via source and target branches

to status/vote changes: