Merge ~ines-almeida/launchpad:fetch-service-option-model-update into launchpad:master

Proposed by Ines Almeida
Status: Merged
Approved by: Ines Almeida
Approved revision: 3fbd84d28e74089c9f7c0464f473dc95d86617af
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~ines-almeida/launchpad:fetch-service-option-model-update
Merge into: launchpad:master
Diff against target: 260 lines (+99/-1)
5 files modified
lib/lp/services/features/flags.py (+10/-0)
lib/lp/snappy/interfaces/snap.py (+15/-0)
lib/lp/snappy/model/snap.py (+19/-0)
lib/lp/snappy/tests/test_snap.py (+53/-1)
lib/lp/testing/factory.py (+2/-0)
Reviewer Review Type Date Requested Status
Jürgen Gmach Approve
Simone Pelosi Approve
Review via email: mp+461552@code.launchpad.net

Commit message

Add Snap.use_fetch_service field to model and API

The field will only be updatable by admins. Although we can't hide the API endpoint itself, we are hidding the endpoint setting and getting behind a new feature flag "snap.fetch_service.enable"

Description of the change

To post a comment you must log in.
Revision history for this message
Simone Pelosi (pelpsi) wrote :

LGTM!

review: Approve
Revision history for this message
Simone Pelosi (pelpsi) wrote :

Could you also add the parameter to the `makeSnap` function in `lp\testing\factory.py` ?

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

Sure, done

Revision history for this message
Jürgen Gmach (jugmac00) wrote :

Thank you! I left a couple of comments. I think the missing assert statements need to be handled, the other things are optional or questions.

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

Comments addressed, thank you for the review!

Revision history for this message
Jürgen Gmach (jugmac00) :
Revision history for this message
Jürgen Gmach (jugmac00) :
Revision history for this message
Ines Almeida (ines-almeida) :
Revision history for this message
Ines Almeida (ines-almeida) :
Revision history for this message
Jürgen Gmach (jugmac00) :
Revision history for this message
Ines Almeida (ines-almeida) :
Revision history for this message
Jürgen Gmach (jugmac00) :
review: Approve
Revision history for this message
Ines Almeida (ines-almeida) wrote :

Waiting for DB change to be ready for deployment before merging this change.

If this goes in before the DB change, any other deployments will be blocked while the DB change isn't ready to be deployed.

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

Opened a new MP to address the post-merge comments from this MP: https://code.launchpad.net/~ines-almeida/launchpad/+git/launchpad/+merge/462338

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/services/features/flags.py b/lib/lp/services/features/flags.py
2index f15a6df..ae0bf52 100644
3--- a/lib/lp/services/features/flags.py
4+++ b/lib/lp/services/features/flags.py
5@@ -304,6 +304,16 @@ flag_info = sorted(
6 "",
7 "",
8 ),
9+ (
10+ "snap.fetch_service.enable",
11+ "boolean",
12+ "If set, allow admins to set snap.use_fetch_service field, which "
13+ "sets a snap build to use the fetch-service instead of the "
14+ "builder-proxy",
15+ "",
16+ "",
17+ "",
18+ ),
19 ]
20 )
21
22diff --git a/lib/lp/snappy/interfaces/snap.py b/lib/lp/snappy/interfaces/snap.py
23index ae2e3df..8364c58 100644
24--- a/lib/lp/snappy/interfaces/snap.py
25+++ b/lib/lp/snappy/interfaces/snap.py
26@@ -23,6 +23,7 @@ __all__ = [
27 "NoSourceForSnap",
28 "NoSuchSnap",
29 "SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG",
30+ "SNAP_USE_FETCH_SERVICE_FEATURE_FLAG",
31 "SnapAuthorizationBadGeneratedMacaroon",
32 "SnapBuildAlreadyPending",
33 "SnapBuildArchiveOwnerMismatch",
34@@ -101,6 +102,7 @@ from lp.soyuz.interfaces.archive import IArchive
35 from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries
36
37 SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG = "snap.channels.snapcraft"
38+SNAP_USE_FETCH_SERVICE_FEATURE_FLAG = "snap.fetch_service.enable"
39
40
41 @error_status(http.client.BAD_REQUEST)
42@@ -1189,6 +1191,18 @@ class ISnapAdminAttributes(Interface):
43 )
44 )
45
46+ use_fetch_service = exported(
47+ Bool(
48+ title=_("Use fetch service"),
49+ required=True,
50+ readonly=False,
51+ description=_(
52+ "If set, Snap builds will use the fetch-service instead "
53+ "of the builder-proxy to access external resources."
54+ ),
55+ )
56+ )
57+
58 def subscribe(person, subscribed_by):
59 """Subscribe a person to this snap recipe."""
60
61@@ -1267,6 +1281,7 @@ class ISnapSet(Interface):
62 store_channels=None,
63 project=None,
64 pro_enable=None,
65+ use_fetch_service=False,
66 ):
67 """Create an `ISnap`."""
68
69diff --git a/lib/lp/snappy/model/snap.py b/lib/lp/snappy/model/snap.py
70index eeeb056..cf18262 100644
71--- a/lib/lp/snappy/model/snap.py
72+++ b/lib/lp/snappy/model/snap.py
73@@ -124,6 +124,7 @@ from lp.services.database.stormexpr import (
74 IsDistinctFrom,
75 NullsLast,
76 )
77+from lp.services.features import getFeatureFlag
78 from lp.services.job.interfaces.job import JobStatus
79 from lp.services.job.model.job import Job
80 from lp.services.librarian.model import LibraryFileAlias, LibraryFileContent
81@@ -137,6 +138,7 @@ from lp.services.webhooks.interfaces import IWebhookSet
82 from lp.services.webhooks.model import WebhookTargetMixin
83 from lp.snappy.adapters.buildarch import determine_architectures_to_build
84 from lp.snappy.interfaces.snap import (
85+ SNAP_USE_FETCH_SERVICE_FEATURE_FLAG,
86 BadMacaroon,
87 BadSnapSearchContext,
88 BadSnapSource,
89@@ -394,6 +396,8 @@ class Snap(StormBase, WebhookTargetMixin):
90
91 _pro_enable = Bool(name="pro_enable", allow_none=True)
92
93+ _use_fetch_service = Bool(name="use_fetch_service", allow_none=False)
94+
95 def __init__(
96 self,
97 registrant,
98@@ -419,6 +423,7 @@ class Snap(StormBase, WebhookTargetMixin):
99 store_channels=None,
100 project=None,
101 pro_enable=False,
102+ use_fetch_service=False,
103 ):
104 """Construct a `Snap`."""
105 super().__init__()
106@@ -454,6 +459,7 @@ class Snap(StormBase, WebhookTargetMixin):
107 self.store_secrets = store_secrets
108 self.store_channels = store_channels
109 self._pro_enable = pro_enable
110+ self.use_fetch_service = use_fetch_service
111
112 def __repr__(self):
113 return "<Snap ~%s/+snap/%s>" % (self.owner.name, self.name)
114@@ -693,6 +699,17 @@ class Snap(StormBase, WebhookTargetMixin):
115 def pro_enable(self, value):
116 self._pro_enable = value
117
118+ @property
119+ def use_fetch_service(self):
120+ if getFeatureFlag(SNAP_USE_FETCH_SERVICE_FEATURE_FLAG):
121+ return self._use_fetch_service
122+ return None
123+
124+ @use_fetch_service.setter
125+ def use_fetch_service(self, value):
126+ if getFeatureFlag(SNAP_USE_FETCH_SERVICE_FEATURE_FLAG):
127+ self._use_fetch_service = value
128+
129 def getAllowedInformationTypes(self, user):
130 """See `ISnap`."""
131 if user_has_special_snap_access(user):
132@@ -1534,6 +1551,7 @@ class SnapSet:
133 store_channels=None,
134 project=None,
135 pro_enable=None,
136+ use_fetch_service=False,
137 ):
138 """See `ISnapSet`."""
139 if not registrant.inTeam(owner):
140@@ -1618,6 +1636,7 @@ class SnapSet:
141 store_channels=store_channels,
142 project=project,
143 pro_enable=pro_enable,
144+ use_fetch_service=use_fetch_service,
145 )
146 store.add(snap)
147 snap._reconcileAccess()
148diff --git a/lib/lp/snappy/tests/test_snap.py b/lib/lp/snappy/tests/test_snap.py
149index 8f35aa1..07fc8d2 100644
150--- a/lib/lp/snappy/tests/test_snap.py
151+++ b/lib/lp/snappy/tests/test_snap.py
152@@ -69,7 +69,7 @@ from lp.services.database.sqlbase import (
153 flush_database_caches,
154 get_transaction_timestamp,
155 )
156-from lp.services.features.testing import MemoryFeatureFixture
157+from lp.services.features.testing import FeatureFixture, MemoryFeatureFixture
158 from lp.services.job.interfaces.job import JobStatus
159 from lp.services.job.runner import JobRunner
160 from lp.services.log.logger import BufferLogger
161@@ -80,6 +80,7 @@ from lp.services.webapp.publisher import canonical_url
162 from lp.services.webapp.snapshot import notify_modified
163 from lp.services.webhooks.testing import LogsScheduledWebhooks
164 from lp.snappy.interfaces.snap import (
165+ SNAP_USE_FETCH_SERVICE_FEATURE_FLAG,
166 BadSnapSearchContext,
167 CannotFetchSnapcraftYaml,
168 CannotModifySnapProcessor,
169@@ -2318,6 +2319,7 @@ class TestSnapSet(TestCaseWithFactory):
170 self.assertTrue(snap.allow_internet)
171 self.assertFalse(snap.build_source_tarball)
172 self.assertFalse(snap.pro_enable)
173+ self.assertFalse(snap.use_fetch_service)
174
175 def test_creation_git(self):
176 # The metadata entries supplied when a Snap is created for a Git
177@@ -2342,6 +2344,7 @@ class TestSnapSet(TestCaseWithFactory):
178 self.assertTrue(snap.allow_internet)
179 self.assertFalse(snap.build_source_tarball)
180 self.assertFalse(snap.pro_enable)
181+ self.assertFalse(snap.use_fetch_service)
182
183 def test_creation_git_url(self):
184 # A Snap can be backed directly by a URL for an external Git
185@@ -2355,6 +2358,55 @@ class TestSnapSet(TestCaseWithFactory):
186 self.assertEqual(ref.path, snap.git_path)
187 self.assertEqual(ref, snap.git_ref)
188
189+ def test_auth_to_edit_admin_only_fields(self):
190+ # The admin fields can only be updated by an admin
191+ self.useFixture(
192+ FeatureFixture({SNAP_USE_FETCH_SERVICE_FEATURE_FLAG: "on"})
193+ )
194+
195+ non_admin = self.factory.makePerson()
196+ [ref] = self.factory.makeGitRefs(owner=non_admin)
197+ components = self.makeSnapComponents(git_ref=ref)
198+ snap = getUtility(ISnapSet).new(**components)
199+
200+ admin_fields = [
201+ "allow_internet",
202+ "pro_enable",
203+ "require_virtualized",
204+ "use_fetch_service",
205+ ]
206+
207+ for field_name in admin_fields:
208+ # exception is raised when user tries updating admin-only fields
209+ with person_logged_in(non_admin):
210+ self.assertRaises(
211+ Unauthorized, setattr, snap, field_name, True
212+ )
213+ # exception isn't raised when an admin does the same
214+ with admin_logged_in():
215+ setattr(snap, field_name, True)
216+ self.assertTrue(getattr(snap, field_name))
217+
218+ def test_snap_use_fetch_service_feature_flag(self):
219+ # The snap.use_fetch_service API only works when feature flag is set
220+ [ref] = self.factory.makeGitRefs()
221+ components = self.makeSnapComponents(git_ref=ref)
222+ snap = getUtility(ISnapSet).new(**components)
223+
224+ # admin cannot get the actual value or set a new value to the
225+ # use_fetch_service field when the feature flag is OFF
226+ login_admin()
227+ self.assertEqual(None, snap.use_fetch_service)
228+ snap.use_fetch_service = True
229+ self.assertEqual(None, snap.use_fetch_service)
230+
231+ # when feature flag is ON, admin can see the real value of
232+ # `use_fetch_service` and opt in and out of it
233+ with MemoryFeatureFixture({SNAP_USE_FETCH_SERVICE_FEATURE_FLAG: "on"}):
234+ self.assertFalse(snap.use_fetch_service)
235+ snap.use_fetch_service = True
236+ self.assertTrue(snap.use_fetch_service)
237+
238 def test_create_private_snap_with_open_team_as_owner_fails(self):
239 components = self.makeSnapComponents()
240 with admin_logged_in():
241diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
242index 61e4f6e..57d1d45 100644
243--- a/lib/lp/testing/factory.py
244+++ b/lib/lp/testing/factory.py
245@@ -6209,6 +6209,7 @@ class LaunchpadObjectFactory(ObjectFactory):
246 store_channels=None,
247 project=_DEFAULT,
248 pro_enable=False,
249+ use_fetch_service=False,
250 ):
251 """Make a new Snap."""
252 assert information_type is None or private is None
253@@ -6290,6 +6291,7 @@ class LaunchpadObjectFactory(ObjectFactory):
254 store_channels=store_channels,
255 project=project,
256 pro_enable=pro_enable,
257+ use_fetch_service=use_fetch_service,
258 )
259 if is_stale is not None:
260 removeSecurityProxy(snap).is_stale = is_stale

Subscribers

People subscribed via source and target branches

to status/vote changes: