launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #19447
[Merge] lp:~cjwatson/launchpad/snap-processors-for-everyone into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/snap-processors-for-everyone into lp:launchpad.
Commit message:
Allow snap package owners to enable/disable unrestricted processors.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/snap-processors-for-everyone/+merge/272507
Allow snap package owners to enable/disable unrestricted processors. This is fairly similar to https://code.launchpad.net/~cjwatson/launchpad/processors-for-everyone/+merge/272093, but I felt that a slightly different choice of available processors made sense given that we know the distroseries.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/snap-processors-for-everyone into lp:launchpad.
=== modified file 'lib/lp/code/browser/widgets/gitref.py'
--- lib/lp/code/browser/widgets/gitref.py 2015-09-10 17:20:55 +0000
+++ lib/lp/code/browser/widgets/gitref.py 2015-09-26 00:51:11 +0000
@@ -85,9 +85,13 @@
try:
repository = self.repository_widget.getInputValue()
except MissingInputError:
- raise WidgetInputError(
- self.name, self.label,
- LaunchpadValidationError("Please choose a Git repository."))
+ if self.context.required:
+ raise WidgetInputError(
+ self.name, self.label,
+ LaunchpadValidationError(
+ "Please choose a Git repository."))
+ else:
+ return None
except ConversionError:
entered_name = self.request.form_ng.getOne(
"%s.repository" % self.name)
@@ -101,9 +105,13 @@
else:
path = None
if not path:
- raise WidgetInputError(
- self.name, self.label,
- LaunchpadValidationError("Please enter a Git branch path."))
+ if self.context.required:
+ raise WidgetInputError(
+ self.name, self.label,
+ LaunchpadValidationError(
+ "Please enter a Git branch path."))
+ else:
+ return
ref = repository.getRefByPath(path)
if ref is None:
raise WidgetInputError(
=== modified file 'lib/lp/code/browser/widgets/tests/test_gitrefwidget.py'
--- lib/lp/code/browser/widgets/tests/test_gitrefwidget.py 2015-09-09 14:17:46 +0000
+++ lib/lp/code/browser/widgets/tests/test_gitrefwidget.py 2015-09-26 00:51:11 +0000
@@ -166,6 +166,16 @@
"The repository at %s does not contain a branch named "
"'non-existent'." % ref.repository.display_name)
+ def test_getInputValue_empty_not_required(self):
+ # If the field is not required, empty input fields are allowed.
+ self.widget.context.required = False
+ form = {
+ "field.git_ref.repository": "",
+ "field.git_ref.path": "",
+ }
+ self.widget.request = LaunchpadTestRequest(form=form)
+ self.assertIsNone(self.widget.getInputValue())
+
def test_getInputValue_valid(self):
# When both the repository and the path are valid, the field value
# is the reference they identify.
=== modified file 'lib/lp/snappy/browser/snap.py'
--- lib/lp/snappy/browser/snap.py 2015-09-23 14:21:15 +0000
+++ lib/lp/snappy/browser/snap.py 2015-09-26 00:51:11 +0000
@@ -73,6 +73,7 @@
SnapFeatureDisabled,
)
from lp.snappy.interfaces.snapbuild import ISnapBuildSet
+from lp.soyuz.browser.archive import EnableProcessorsMixin
from lp.soyuz.browser.build import get_build_by_id_str
from lp.soyuz.interfaces.archive import IArchive
@@ -357,7 +358,7 @@
def validate_widgets(self, data, names=None):
"""See `LaunchpadFormView`."""
- if 'vcs' in self.widgets:
+ if self.widgets.get('vcs') is not None:
# Set widgets as required or optional depending on the vcs
# field.
super(BaseSnapEditView, self).validate_widgets(data, ['vcs'])
@@ -379,6 +380,12 @@
data['git_ref'] = None
elif vcs == VCSType.GIT:
data['branch'] = None
+ new_processors = data.get('processors')
+ if new_processors is not None:
+ if set(self.context.processors) != set(new_processors):
+ self.context.setProcessors(
+ new_processors, check_permissions=True, user=self.user)
+ del data['processors']
self.updateContextFromData(data)
self.next_url = canonical_url(self.context)
@@ -400,7 +407,7 @@
field_names = ['require_virtualized']
-class SnapEditView(BaseSnapEditView):
+class SnapEditView(BaseSnapEditView, EnableProcessorsMixin):
"""View for editing snap packages."""
@property
@@ -415,6 +422,15 @@
custom_widget('vcs', LaunchpadRadioWidget)
custom_widget('git_ref', GitRefWidget)
+ def setUpFields(self):
+ """See `LaunchpadFormView`."""
+ super(SnapEditView, self).setUpFields()
+ self.form_fields += self.createEnabledProcessors(
+ self.context.available_processors,
+ u"The architectures that are available to be enabled or disabled "
+ u"for this snap package. Some architectures are restricted and "
+ u"may only be enabled or disabled by administrators.")
+
@property
def initial_values(self):
if self.context.git_ref is not None:
@@ -437,6 +453,14 @@
'this name.' % owner.displayname)
except NoSuchSnap:
pass
+ if 'processors' in data:
+ available_processors = set(self.context.available_processors)
+ for processor in self.context.processors:
+ if (processor not in available_processors and
+ processor not in data['processors']):
+ # This processor is not currently available for
+ # selection, but is enabled. Leave it untouched.
+ data['processors'].append(processor)
class SnapDeleteView(BaseSnapEditView):
=== modified file 'lib/lp/snappy/browser/tests/test_snap.py'
--- lib/lp/snappy/browser/tests/test_snap.py 2015-09-23 14:21:15 +0000
+++ lib/lp/snappy/browser/tests/test_snap.py 2015-09-26 00:51:11 +0000
@@ -16,6 +16,10 @@
from mechanize import LinkNotFoundError
import pytz
import soupmatchers
+from testtools.matchers import (
+ MatchesSetwise,
+ MatchesStructure,
+ )
from zope.component import getUtility
from zope.publisher.interfaces import NotFound
from zope.security.interfaces import Unauthorized
@@ -35,10 +39,12 @@
SnapView,
)
from lp.snappy.interfaces.snap import (
+ CannotModifySnapProcessor,
SNAP_FEATURE_FLAG,
SnapFeatureDisabled,
)
from lp.testing import (
+ admin_logged_in,
BrowserTestCase,
login,
login_person,
@@ -249,7 +255,7 @@
class TestSnapEditView(BrowserTestCase):
- layer = DatabaseFunctionalLayer
+ layer = LaunchpadFunctionalLayer
def setUp(self):
super(TestSnapEditView, self).setUp()
@@ -324,6 +330,116 @@
"name.",
extract_text(find_tags_by_class(browser.contents, "message")[1]))
+ def setUpDistroSeries(self):
+ """Set up a distroseries with some available processors."""
+ distroseries = self.factory.makeUbuntuDistroSeries()
+ processor_names = ["386", "amd64", "hppa"]
+ for name in processor_names:
+ processor = getUtility(IProcessorSet).getByName(name)
+ das = self.factory.makeDistroArchSeries(
+ distroseries=distroseries, architecturetag=name,
+ processor=processor)
+ das.addOrUpdateChroot(self.factory.makeLibraryFileAlias())
+ return distroseries
+
+ def test_display_processors(self):
+ distroseries = self.setUpDistroSeries()
+ snap = self.factory.makeSnap(
+ registrant=self.person, owner=self.person,
+ distroseries=distroseries)
+ browser = self.getViewBrowser(snap, view_name="+edit", user=snap.owner)
+ processors = browser.getControl(name="field.processors")
+ self.assertContentEqual(
+ ["Intel 386 (386)", "AMD 64bit (amd64)", "HPPA Processor (hppa)"],
+ [extract_text(option) for option in processors.displayOptions])
+ self.assertContentEqual(["386", "amd64", "hppa"], processors.options)
+
+ def test_edit_processors(self):
+ distroseries = self.setUpDistroSeries()
+ snap = self.factory.makeSnap(
+ registrant=self.person, owner=self.person,
+ distroseries=distroseries)
+ self.assertContentEqual(
+ ["386", "amd64", "hppa"],
+ [processor.name for processor in snap.processors])
+ browser = self.getViewBrowser(snap, view_name="+edit", user=snap.owner)
+ processors = browser.getControl(name="field.processors")
+ self.assertContentEqual(["386", "amd64", "hppa"], processors.value)
+ processors.value = ["386", "amd64"]
+ browser.getControl("Update snap package").click()
+ login_person(self.person)
+ self.assertContentEqual(
+ ["386", "amd64"],
+ [processor.name for processor in snap.processors])
+
+ def test_edit_with_invisible_processor(self):
+ # It's possible for existing snap packages to have an enabled
+ # processor that's no longer usable with the current distroseries,
+ # which will mean it's hidden from the UI, but the non-admin
+ # Snap.setProcessors isn't allowed to disable it. Editing the
+ # processor list of such a snap package leaves the invisible
+ # processor intact.
+ proc_386 = getUtility(IProcessorSet).getByName("386")
+ proc_amd64 = getUtility(IProcessorSet).getByName("amd64")
+ proc_armel = self.factory.makeProcessor(
+ name="armel", restricted=True, build_by_default=False)
+ distroseries = self.setUpDistroSeries()
+ snap = self.factory.makeSnap(
+ registrant=self.person, owner=self.person,
+ distroseries=distroseries)
+ with admin_logged_in():
+ snap.setProcessors([proc_386, proc_amd64, proc_armel])
+ browser = self.getViewBrowser(snap, view_name="+edit", user=snap.owner)
+ processors = browser.getControl(name="field.processors")
+ self.assertContentEqual(["386", "amd64"], processors.value)
+ processors.value = ["amd64"]
+ browser.getControl("Update snap package").click()
+ login_person(self.person)
+ self.assertContentEqual(
+ ["amd64", "armel"],
+ [processor.name for processor in snap.processors])
+
+ def test_edit_processors_restricted(self):
+ # A restricted processor is shown disabled in the UI and cannot be
+ # enabled.
+ self.useFixture(FakeLogger())
+ distroseries = self.setUpDistroSeries()
+ proc_armhf = self.factory.makeProcessor(
+ name="armhf", restricted=True, build_by_default=False)
+ das_armhf = self.factory.makeDistroArchSeries(
+ distroseries=distroseries, architecturetag="armhf",
+ processor=proc_armhf)
+ das_armhf.addOrUpdateChroot(self.factory.makeLibraryFileAlias())
+ snap = self.factory.makeSnap(
+ registrant=self.person, owner=self.person,
+ distroseries=distroseries)
+ self.assertContentEqual(
+ ["386", "amd64", "hppa"],
+ [processor.name for processor in snap.processors])
+ browser = self.getViewBrowser(snap, view_name="+edit", user=snap.owner)
+ processors = browser.getControl(name="field.processors")
+ self.assertContentEqual(["386", "amd64", "hppa"], processors.value)
+ self.assertThat(
+ processors.controls, MatchesSetwise(
+ MatchesStructure.byEquality(
+ optionValue="386", disabled=False),
+ MatchesStructure.byEquality(
+ optionValue="amd64", disabled=False),
+ MatchesStructure.byEquality(
+ optionValue="armhf", disabled=True),
+ MatchesStructure.byEquality(
+ optionValue="hppa", disabled=False),
+ ))
+ # Even if the user works around the disabled checkbox and forcibly
+ # enables it, they can't enable the restricted processor.
+ for control in processors.controls:
+ if control.optionValue == "armhf":
+ control.mech_item.disabled = False
+ processors.value = ["386", "amd64", "armhf"]
+ self.assertRaises(
+ CannotModifySnapProcessor,
+ browser.getControl("Update snap package").click)
+
class TestSnapDeleteView(BrowserTestCase):
=== modified file 'lib/lp/snappy/configure.zcml'
--- lib/lp/snappy/configure.zcml 2015-09-18 13:32:09 +0000
+++ lib/lp/snappy/configure.zcml 2015-09-26 00:51:11 +0000
@@ -27,7 +27,6 @@
set_schema="lp.snappy.interfaces.snap.ISnapEditableAttributes" />
<require
permission="launchpad.Admin"
- interface="lp.snappy.interfaces.snap.ISnapAdmin"
set_schema="lp.snappy.interfaces.snap.ISnapAdminAttributes" />
</class>
<subscriber
=== modified file 'lib/lp/snappy/interfaces/snap.py'
--- lib/lp/snappy/interfaces/snap.py 2015-09-18 14:14:34 +0000
+++ lib/lp/snappy/interfaces/snap.py 2015-09-26 00:51:11 +0000
@@ -8,6 +8,7 @@
__all__ = [
'BadSnapSearchContext',
'CannotDeleteSnap',
+ 'CannotModifySnapProcessor',
'DuplicateSnapName',
'ISnap',
'ISnapSet',
@@ -171,6 +172,19 @@
"""The context is not valid for a snap package search."""
+@error_status(httplib.FORBIDDEN)
+class CannotModifySnapProcessor(Exception):
+ """Tried to enable or disable a restricted processor on an snap package."""
+
+ _fmt = (
+ '%(processor)s is restricted, and may only be enabled or disabled '
+ 'by administrators.')
+
+ def __init__(self, processor):
+ super(CannotModifySnapProcessor, self).__init__(
+ self._fmt % {'processor': processor.name})
+
+
class ISnapView(Interface):
"""`ISnap` attributes that require launchpad.View permission."""
@@ -187,6 +201,19 @@
source = Attribute(
"The source branch for this snap package (VCS-agnostic).")
+ available_processors = Attribute(
+ "The architectures that are available to be enabled or disabled for "
+ "this snap package.")
+
+ @call_with(check_permissions=True, user=REQUEST_USER)
+ @operation_parameters(
+ processors=List(
+ value_type=Reference(schema=IProcessor), required=True))
+ @export_write_operation()
+ @operation_for_version("devel")
+ def setProcessors(processors, check_permissions=False, user=None):
+ """Set the architectures for which the snap package should be built."""
+
def getAllowedArchitectures():
"""Return all distroarchseries that this package can build for.
@@ -317,21 +344,8 @@
readonly=False))
-class ISnapAdmin(Interface):
- """`ISnap` methods that require launchpad.Admin permission."""
-
- @operation_parameters(
- processors=List(
- value_type=Reference(schema=IProcessor), required=True))
- @export_write_operation()
- @operation_for_version("devel")
- def setProcessors(processors):
- """Set the architectures for which the snap package should be built."""
-
-
class ISnap(
- ISnapView, ISnapEdit, ISnapEditableAttributes, ISnapAdminAttributes,
- ISnapAdmin):
+ ISnapView, ISnapEdit, ISnapEditableAttributes, ISnapAdminAttributes):
"""A buildable snap package."""
# XXX cjwatson 2015-07-17 bug=760849: "beta" is a lie to get WADL
=== modified file 'lib/lp/snappy/model/snap.py'
--- lib/lp/snappy/model/snap.py 2015-09-18 14:14:34 +0000
+++ lib/lp/snappy/model/snap.py 2015-09-26 00:51:11 +0000
@@ -19,10 +19,15 @@
Storm,
Unicode,
)
-from zope.component import getUtility
+from zope.component import (
+ getAdapter,
+ getUtility,
+ )
from zope.interface import implementer
+from zope.security.interfaces import Unauthorized
from zope.security.proxy import removeSecurityProxy
+from lp.app.interfaces.security import IAuthorization
from lp.buildmaster.enums import BuildStatus
from lp.buildmaster.interfaces.processor import IProcessorSet
from lp.buildmaster.model.processor import Processor
@@ -46,7 +51,10 @@
IPersonSet,
)
from lp.registry.interfaces.product import IProduct
-from lp.registry.interfaces.role import IHasOwner
+from lp.registry.interfaces.role import (
+ IHasOwner,
+ IPersonRoles,
+ )
from lp.services.database.bulk import load_related
from lp.services.database.constants import (
DEFAULT,
@@ -65,6 +73,7 @@
from lp.snappy.interfaces.snap import (
BadSnapSearchContext,
CannotDeleteSnap,
+ CannotModifySnapProcessor,
DuplicateSnapName,
ISnap,
ISnapSet,
@@ -84,6 +93,7 @@
Archive,
get_enabled_archive_filter,
)
+from lp.soyuz.model.distroarchseries import DistroArchSeries
def snap_modified(snap, event):
@@ -177,23 +187,55 @@
else:
return None
+ @property
+ def available_processors(self):
+ """See `ISnap`."""
+ processors = Store.of(self).find(
+ Processor,
+ Processor.id == DistroArchSeries.processor_id,
+ DistroArchSeries.id.is_in(
+ self.distro_series.buildable_architectures.get_select_expr(
+ DistroArchSeries.id)),
+ DistroArchSeries.enabled)
+ return processors.config(distinct=True)
+
def _getProcessors(self):
return list(Store.of(self).find(
Processor,
Processor.id == SnapArch.processor_id,
SnapArch.snap == self))
- def setProcessors(self, processors):
+ def setProcessors(self, processors, check_permissions=False, user=None):
"""See `ISnap`."""
+ if check_permissions:
+ can_modify = None
+ if user is not None:
+ roles = IPersonRoles(user)
+ authz = lambda perm: getAdapter(self, IAuthorization, perm)
+ if authz('launchpad.Admin').checkAuthenticated(roles):
+ can_modify = lambda proc: True
+ elif authz('launchpad.Edit').checkAuthenticated(roles):
+ can_modify = lambda proc: not proc.restricted
+ if can_modify is None:
+ raise Unauthorized(
+ 'Permission launchpad.Admin or launchpad.Edit required '
+ 'on %s.' % self)
+ else:
+ can_modify = lambda proc: True
+
enablements = dict(Store.of(self).find(
(Processor, SnapArch),
Processor.id == SnapArch.processor_id,
SnapArch.snap == self))
for proc in enablements:
if proc not in processors:
+ if not can_modify(proc):
+ raise CannotModifySnapProcessor(proc)
Store.of(self).remove(enablements[proc])
for proc in processors:
if proc not in self.processors:
+ if not can_modify(proc):
+ raise CannotModifySnapProcessor(proc)
snaparch = SnapArch()
snaparch.snap = self
snaparch.processor = proc
=== modified file 'lib/lp/snappy/templates/snap-edit.pt'
--- lib/lp/snappy/templates/snap-edit.pt 2015-09-09 14:17:46 +0000
+++ lib/lp/snappy/templates/snap-edit.pt 2015-09-26 00:51:11 +0000
@@ -59,6 +59,10 @@
</div>
</td>
</tr>
+
+ <tal:widget define="widget nocall:view/widgets/processors">
+ <metal:block use-macro="context/@@launchpad_form/widget_row" />
+ </tal:widget>
</table>
</metal:formbody>
</div>
=== modified file 'lib/lp/snappy/tests/test_snap.py'
--- lib/lp/snappy/tests/test_snap.py 2015-09-16 13:30:33 +0000
+++ lib/lp/snappy/tests/test_snap.py 2015-09-26 00:51:11 +0000
@@ -15,6 +15,7 @@
from zope.event import notify
from zope.security.proxy import removeSecurityProxy
+from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.buildmaster.enums import (
BuildQueueStatus,
BuildStatus,
@@ -33,6 +34,7 @@
from lp.snappy.interfaces.snap import (
BadSnapSearchContext,
CannotDeleteSnap,
+ CannotModifySnapProcessor,
ISnap,
ISnapSet,
ISnapView,
@@ -55,6 +57,7 @@
)
from lp.testing.layers import (
DatabaseFunctionalLayer,
+ LaunchpadFunctionalLayer,
LaunchpadZopelessLayer,
)
from lp.testing.matchers import (
@@ -598,10 +601,10 @@
class TestSnapProcessors(TestCaseWithFactory):
- layer = LaunchpadZopelessLayer
+ layer = LaunchpadFunctionalLayer
def setUp(self):
- super(TestSnapProcessors, self).setUp()
+ super(TestSnapProcessors, self).setUp(user="foo.bar@xxxxxxxxxxxxx")
self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
self.default_procs = [
getUtility(IProcessorSet).getByName("386"),
@@ -643,9 +646,45 @@
snap.setProcessors(self.unrestricted_procs + [self.arm])
self.assertContentEqual(
self.unrestricted_procs + [self.arm], snap.processors)
- snap.processors = []
+ snap.setProcessors([])
self.assertContentEqual([], snap.processors)
+ def test_set_non_admin(self):
+ """Non-admins can only enable or disable unrestricted processors."""
+ snap = self.factory.makeSnap()
+ snap.setProcessors(self.default_procs)
+ self.assertContentEqual(self.default_procs, snap.processors)
+ with person_logged_in(snap.owner) as owner:
+ # Adding arm is forbidden ...
+ self.assertRaises(
+ CannotModifySnapProcessor, snap.setProcessors,
+ [self.default_procs[0], self.arm],
+ check_permissions=True, user=owner)
+ # ... but removing amd64 is OK.
+ snap.setProcessors(
+ [self.default_procs[0]], check_permissions=True, user=owner)
+ self.assertContentEqual([self.default_procs[0]], snap.processors)
+ with admin_logged_in() as admin:
+ snap.setProcessors(
+ [self.default_procs[0], self.arm],
+ check_permissions=True, user=admin)
+ self.assertContentEqual(
+ [self.default_procs[0], self.arm], snap.processors)
+ with person_logged_in(snap.owner) as owner:
+ hppa = getUtility(IProcessorSet).getByName("hppa")
+ self.assertFalse(hppa.restricted)
+ # Adding hppa while removing arm is forbidden ...
+ self.assertRaises(
+ CannotModifySnapProcessor, snap.setProcessors,
+ [self.default_procs[0], hppa],
+ check_permissions=True, user=owner)
+ # ... but adding hppa while retaining arm is OK.
+ snap.setProcessors(
+ [self.default_procs[0], self.arm, hppa],
+ check_permissions=True, user=owner)
+ self.assertContentEqual(
+ [self.default_procs[0], self.arm, hppa], snap.processors)
+
class TestSnapWebservice(TestCaseWithFactory):
@@ -803,6 +842,77 @@
"No such snap package with this owner: 'nonexistent'.",
response.body)
+ def setProcessors(self, user, snap, names):
+ ws = webservice_for_person(
+ user, permission=OAuthPermission.WRITE_PUBLIC)
+ return ws.named_post(
+ snap["self_link"], "setProcessors",
+ processors=["/+processors/%s" % name for name in names],
+ api_version="devel")
+
+ def assertProcessors(self, user, snap, names):
+ body = webservice_for_person(user).get(
+ snap["self_link"] + "/processors", api_version="devel").jsonBody()
+ self.assertContentEqual(
+ names, [entry["name"] for entry in body["entries"]])
+
+ def test_setProcessors_admin(self):
+ """An admin can add a new processor to the enabled restricted set."""
+ commercial = getUtility(ILaunchpadCelebrities).commercial_admin
+ commercial_admin = self.factory.makePerson(member_of=[commercial])
+ self.factory.makeProcessor(
+ "arm", "ARM", "ARM", restricted=True, build_by_default=False)
+ snap = self.makeSnap()
+ self.assertProcessors(commercial_admin, snap, ["386", "hppa", "amd64"])
+
+ response = self.setProcessors(commercial_admin, snap, ["386", "arm"])
+ self.assertEqual(200, response.status)
+ self.assertProcessors(commercial_admin, snap, ["386", "arm"])
+
+ def test_setProcessors_non_owner_forbidden(self):
+ """Only commercial admins and snap owners can call setProcessors."""
+ self.factory.makeProcessor(
+ "unrestricted", "Unrestricted", "Unrestricted", restricted=False,
+ build_by_default=False)
+ non_owner = self.factory.makePerson()
+ snap = self.makeSnap()
+
+ response = self.setProcessors(non_owner, snap, ["386", "unrestricted"])
+ self.assertEqual(401, response.status)
+
+ def test_setProcessors_owner(self):
+ """The snap owner can enable/disable unrestricted processors."""
+ snap = self.makeSnap()
+ self.assertProcessors(self.person, snap, ["386", "hppa", "amd64"])
+
+ response = self.setProcessors(self.person, snap, ["386"])
+ self.assertEqual(200, response.status)
+ self.assertProcessors(self.person, snap, ["386"])
+
+ response = self.setProcessors(self.person, snap, ["386", "amd64"])
+ self.assertEqual(200, response.status)
+ self.assertProcessors(self.person, snap, ["386", "amd64"])
+
+ def test_setProcessors_owner_restricted_forbidden(self):
+ """The snap owner cannot enable/disable restricted processors."""
+ commercial = getUtility(ILaunchpadCelebrities).commercial_admin
+ commercial_admin = self.factory.makePerson(member_of=[commercial])
+ self.factory.makeProcessor(
+ "arm", "ARM", "ARM", restricted=True, build_by_default=False)
+ snap = self.makeSnap()
+
+ response = self.setProcessors(self.person, snap, ["386", "arm"])
+ self.assertEqual(403, response.status)
+
+ # If a commercial admin enables arm, the owner cannot disable it.
+ response = self.setProcessors(
+ commercial_admin, snap, ["386", "arm"])
+ self.assertEqual(200, response.status)
+ self.assertProcessors(self.person, snap, ["386", "arm"])
+
+ response = self.setProcessors(self.person, snap, ["386"])
+ self.assertEqual(403, response.status)
+
def makeBuildableDistroArchSeries(self, **kwargs):
das = self.factory.makeDistroArchSeries(**kwargs)
fake_chroot = self.factory.makeLibraryFileAlias(
Follow ups