launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #21307
[Merge] lp:~cjwatson/launchpad/snap-export-store-channels into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/snap-export-store-channels into lp:launchpad.
Commit message:
Export Snap.store_channels and SnapSet.new(store_channels=...) on the webservice.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/snap-export-store-channels/+merge/314116
build.snapcraft.io needs this so that it can set up snaps properly.
I started by pushing the adjusted value_type from SnapView.store_channels down to the interface, and then had to arrange for the channel list to be JSON-serialisable in order to work properly on the webservice. I opted for beefing up the vocabulary a bit so that it implicitly includes any channels already in its context as well as the set currently exposed by CPI, which simplifies validation. After that it's mostly just a matter of making sure all the tests tolerate these changes, which is a bit noisy but not very exciting.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/snap-export-store-channels into lp:launchpad.
=== modified file 'lib/lp/snappy/browser/snap.py'
--- lib/lp/snappy/browser/snap.py 2016-12-15 19:54:46 +0000
+++ lib/lp/snappy/browser/snap.py 2017-01-04 21:19:31 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Snap views."""
@@ -190,19 +190,12 @@
@cachedproperty
def store_channels(self):
- if self.context.store_channels is not None:
- vocabulary = getUtility(
- IVocabularyFactory, name='SnapStoreChannel')(self.context)
- channel_titles = []
- for channel in self.context.store_channels:
- try:
- channel_titles.append(
- vocabulary.getTermByToken(channel).title)
- except LookupError:
- channel_titles.append(channel)
- return ', '.join(channel_titles)
- else:
- return None
+ vocabulary = getUtility(
+ IVocabularyFactory, name='SnapStoreChannel')(self.context)
+ channel_titles = []
+ for channel in self.context.store_channels:
+ channel_titles.append(vocabulary.getTermByToken(channel).title)
+ return ', '.join(channel_titles)
def builds_for_snap(snap):
@@ -356,9 +349,7 @@
# This is only required if store_upload is True. Later validation takes
# care of adjusting the required attribute.
store_name = copy_field(ISnap['store_name'], required=True)
- store_channels = copy_field(
- ISnap['store_channels'],
- value_type=Choice(vocabulary='SnapStoreChannel'), required=True)
+ store_channels = copy_field(ISnap['store_channels'], required=True)
def log_oops(error, request):
=== modified file 'lib/lp/snappy/browser/tests/test_snap.py'
--- lib/lp/snappy/browser/tests/test_snap.py 2016-12-15 19:54:46 +0000
+++ lib/lp/snappy/browser/tests/test_snap.py 2017-01-04 21:19:31 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test snap package views."""
@@ -139,30 +139,36 @@
branch, "+new-snap")
-class TestSnapAddView(BrowserTestCase):
+class BaseTestSnapView(BrowserTestCase):
layer = LaunchpadFunctionalLayer
def setUp(self):
+ super(BaseTestSnapView, self).setUp()
+ self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
+ self.useFixture(FakeLogger())
+ self.snap_store_client = FakeMethod()
+ self.snap_store_client.listChannels = FakeMethod(result=[
+ {"name": "stable", "display_name": "Stable"},
+ {"name": "edge", "display_name": "Edge"},
+ ])
+ self.snap_store_client.requestPackageUploadPermission = (
+ getUtility(ISnapStoreClient).requestPackageUploadPermission)
+ self.useFixture(
+ ZopeUtilityFixture(self.snap_store_client, ISnapStoreClient))
+ self.person = self.factory.makePerson(
+ name="test-person", displayname="Test Person")
+
+
+class TestSnapAddView(BaseTestSnapView):
+
+ def setUp(self):
super(TestSnapAddView, self).setUp()
- self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
- self.useFixture(FakeLogger())
- self.person = self.factory.makePerson(
- name="test-person", displayname="Test Person")
self.distroseries = self.factory.makeUbuntuDistroSeries(
version="13.10")
with admin_logged_in():
self.snappyseries = self.factory.makeSnappySeries(
preferred_distro_series=self.distroseries)
- self.snap_store_client = FakeMethod()
- self.snap_store_client.listChannels = FakeMethod(result=[
- {"name": "stable", "display_name": "Stable"},
- {"name": "edge", "display_name": "Edge"},
- ])
- self.snap_store_client.requestPackageUploadPermission = (
- getUtility(ISnapStoreClient).requestPackageUploadPermission)
- self.useFixture(
- ZopeUtilityFixture(self.snap_store_client, ISnapStoreClient))
def setUpDistroSeries(self):
"""Set up a distroseries with some available processors."""
@@ -512,16 +518,7 @@
self.assertEqual(1, safe_load.call_count)
-class TestSnapAdminView(BrowserTestCase):
-
- layer = DatabaseFunctionalLayer
-
- def setUp(self):
- super(TestSnapAdminView, self).setUp()
- self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
- self.useFixture(FakeLogger())
- self.person = self.factory.makePerson(
- name="test-person", displayname="Test Person")
+class TestSnapAdminView(BaseTestSnapView):
def test_unauthorized(self):
# A non-admin user cannot administer a snap package.
@@ -590,30 +587,15 @@
self.assertSqlAttributeEqualsDate(snap, "date_last_modified", UTC_NOW)
-class TestSnapEditView(BrowserTestCase):
-
- layer = LaunchpadFunctionalLayer
+class TestSnapEditView(BaseTestSnapView):
def setUp(self):
super(TestSnapEditView, self).setUp()
- self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
- self.useFixture(FakeLogger())
- self.person = self.factory.makePerson(
- name="test-person", displayname="Test Person")
self.distroseries = self.factory.makeUbuntuDistroSeries(
version="13.10")
with admin_logged_in():
self.snappyseries = self.factory.makeSnappySeries(
usable_distro_series=[self.distroseries])
- self.snap_store_client = FakeMethod()
- self.snap_store_client.listChannels = FakeMethod(result=[
- {"name": "stable", "display_name": "Stable"},
- {"name": "edge", "display_name": "Edge"},
- ])
- self.snap_store_client.requestPackageUploadPermission = (
- getUtility(ISnapStoreClient).requestPackageUploadPermission)
- self.useFixture(
- ZopeUtilityFixture(self.snap_store_client, ISnapStoreClient))
def test_initial_store_series(self):
# The initial store_series is the newest that is usable for the
@@ -825,7 +807,6 @@
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)
@@ -976,15 +957,10 @@
self.assertEqual(expected_args, parse_qs(parsed_location[3]))
-class TestSnapAuthorizeView(BrowserTestCase):
-
- layer = LaunchpadFunctionalLayer
+class TestSnapAuthorizeView(BaseTestSnapView):
def setUp(self):
super(TestSnapAuthorizeView, self).setUp()
- self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
- self.person = self.factory.makePerson(
- name="test-person", displayname="Test Person")
self.distroseries = self.factory.makeUbuntuDistroSeries()
with admin_logged_in():
self.snappyseries = self.factory.makeSnappySeries(
@@ -997,7 +973,6 @@
def test_unauthorized(self):
# A user without edit access cannot authorize snap package uploads.
- self.useFixture(FakeLogger())
other_person = self.factory.makePerson()
self.assertRaises(
Unauthorized, self.getUserBrowser,
@@ -1093,19 +1068,10 @@
self.snap.store_secrets)
-class TestSnapDeleteView(BrowserTestCase):
-
- layer = LaunchpadFunctionalLayer
-
- def setUp(self):
- super(TestSnapDeleteView, self).setUp()
- self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
- self.person = self.factory.makePerson(
- name="test-person", displayname="Test Person")
+class TestSnapDeleteView(BaseTestSnapView):
def test_unauthorized(self):
# A user without edit access cannot delete a snap package.
- self.useFixture(FakeLogger())
snap = self.factory.makeSnap(registrant=self.person, owner=self.person)
snap_url = canonical_url(snap)
other_person = self.factory.makePerson()
@@ -1118,7 +1084,6 @@
def test_delete_snap_without_builds(self):
# A snap package without builds can be deleted.
- self.useFixture(FakeLogger())
snap = self.factory.makeSnap(registrant=self.person, owner=self.person)
snap_url = canonical_url(snap)
owner_url = canonical_url(self.person)
@@ -1130,7 +1095,6 @@
def test_delete_snap_with_builds(self):
# A snap package with builds can be deleted.
- self.useFixture(FakeLogger())
snap = self.factory.makeSnap(registrant=self.person, owner=self.person)
build = self.factory.makeSnapBuild(snap=snap)
self.factory.makeSnapFile(snapbuild=build)
@@ -1143,13 +1107,10 @@
self.assertRaises(NotFound, browser.open, snap_url)
-class TestSnapView(BrowserTestCase):
-
- layer = LaunchpadFunctionalLayer
+class TestSnapView(BaseTestSnapView):
def setUp(self):
super(TestSnapView, self).setUp()
- self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
self.ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
self.distroseries = self.factory.makeDistroSeries(
distribution=self.ubuntu, name="shiny", displayname="Shiny")
@@ -1157,8 +1118,6 @@
self.distroarchseries = self.factory.makeDistroArchSeries(
distroseries=self.distroseries, architecturetag="i386",
processor=processor)
- self.person = self.factory.makePerson(
- name="test-person", displayname="Test Person")
self.factory.makeBuilder(virtualized=True)
def makeSnap(self, **kwargs):
@@ -1372,32 +1331,35 @@
self.setStatus(build, BuildStatus.FULLYBUILT)
self.assertEqual(list(reversed(builds[1:])), view.builds)
- def test_store_channels_none(self):
+ def test_store_channels_empty(self):
snap = self.factory.makeSnap()
view = create_initialized_view(snap, "+index")
- self.assertIsNone(view.store_channels)
+ self.assertEqual("", view.store_channels)
def test_store_channels_uses_titles(self):
snap_store_client = FakeMethod()
snap_store_client.listChannels = FakeMethod(result=[
{"name": "stable", "display_name": "Stable"},
{"name": "edge", "display_name": "Edge"},
+ {"name": "old", "display_name": "Old channel"},
])
self.useFixture(
ZopeUtilityFixture(snap_store_client, ISnapStoreClient))
- snap = self.factory.makeSnap(store_channels=["stable", "nonexistent"])
- view = create_initialized_view(snap, "+index")
- self.assertEqual("Stable, nonexistent", view.store_channels)
-
-
-class TestSnapRequestBuildsView(BrowserTestCase):
-
- layer = LaunchpadFunctionalLayer
+ snap = self.factory.makeSnap(store_channels=["stable", "old"])
+ view = create_initialized_view(snap, "+index")
+ self.assertEqual("Stable, Old channel", view.store_channels)
+ snap_store_client.listChannels.result = [
+ {"name": "stable", "display_name": "Stable"},
+ {"name": "edge", "display_name": "Edge"},
+ ]
+ view = create_initialized_view(snap, "+index")
+ self.assertEqual("Stable, old", view.store_channels)
+
+
+class TestSnapRequestBuildsView(BaseTestSnapView):
def setUp(self):
super(TestSnapRequestBuildsView, self).setUp()
- self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
- self.useFixture(FakeLogger())
self.ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
self.distroseries = self.factory.makeDistroSeries(
distribution=self.ubuntu, name="shiny", displayname="Shiny")
@@ -1408,7 +1370,6 @@
processor=getUtility(IProcessorSet).getByName(processor))
das.addOrUpdateChroot(self.factory.makeLibraryFileAlias())
self.architectures.append(das)
- self.person = self.factory.makePerson()
self.snap = self.factory.makeSnap(
registrant=self.person, owner=self.person,
distroseries=self.distroseries, name=u"snap-name")
=== modified file 'lib/lp/snappy/interfaces/snap.py'
--- lib/lp/snappy/interfaces/snap.py 2016-12-15 19:54:46 +0000
+++ lib/lp/snappy/interfaces/snap.py 2017-01-04 21:19:31 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Snap package interfaces."""
@@ -523,12 +523,12 @@
"Serialized secrets issued by the store and the login service to "
"authorize uploads of this snap package."))
- store_channels = List(
- value_type=TextLine(), title=_("Store channels"),
- required=False, readonly=False,
+ store_channels = exported(List(
+ value_type=Choice(vocabulary="SnapStoreChannel"),
+ title=_("Store channels"), required=False, readonly=False,
description=_(
"Channels to release this snap package to after uploading it to "
- "the store."))
+ "the store.")))
class ISnapAdminAttributes(Interface):
@@ -578,7 +578,8 @@
"owner", "distro_series", "name", "description", "branch",
"git_repository", "git_repository_url", "git_path", "git_ref",
"auto_build", "auto_build_archive", "auto_build_pocket",
- "private", "store_upload", "store_series", "store_name"])
+ "private", "store_upload", "store_series", "store_name",
+ "store_channels"])
@operation_for_version("devel")
def new(registrant, owner, distro_series, name, description=None,
branch=None, git_repository=None, git_repository_url=None,
@@ -586,7 +587,7 @@
auto_build_archive=None, auto_build_pocket=None,
require_virtualized=True, processors=None, date_created=None,
private=False, store_upload=False, store_series=None,
- store_name=None, store_secrets=None):
+ store_name=None, store_secrets=None, store_channels=None):
"""Create an `ISnap`."""
def exists(owner, name):
=== modified file 'lib/lp/snappy/model/snap.py'
--- lib/lp/snappy/model/snap.py 2016-12-15 19:54:46 +0000
+++ lib/lp/snappy/model/snap.py 2017-01-04 21:19:31 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -202,7 +202,7 @@
store_secrets = JSON('store_secrets', allow_none=True)
- store_channels = JSON('store_channels', allow_none=True)
+ _store_channels = JSON('store_channels', allow_none=True)
def __init__(self, registrant, owner, distro_series, name,
description=None, branch=None, git_ref=None, auto_build=False,
@@ -346,6 +346,14 @@
self.distro_series = value.distro_series
self.store_series = value.snappy_series
+ @property
+ def store_channels(self):
+ return [] if self._store_channels is None else self._store_channels
+
+ @store_channels.setter
+ def store_channels(self, value):
+ self._store_channels = value if value else None
+
@staticmethod
def extractSSOCaveat(macaroon):
locations = [
=== modified file 'lib/lp/snappy/templates/snap-index.pt'
--- lib/lp/snappy/templates/snap-index.pt 2016-07-26 13:20:56 +0000
+++ lib/lp/snappy/templates/snap-index.pt 2017-01-04 21:19:31 +0000
@@ -111,7 +111,7 @@
<a tal:replace="structure view/menu:overview/edit/fmt:icon"/>
</dd>
</dl>
- <p id="store_channels" tal:condition="not: context/store_channels">
+ <p id="store_channels" tal:condition="not: view/store_channels">
This snap package will not be released to any channels on the store.
</p>
</div>
=== modified file 'lib/lp/snappy/tests/test_snap.py'
--- lib/lp/snappy/tests/test_snap.py 2016-12-16 00:50:53 +0000
+++ lib/lp/snappy/tests/test_snap.py 2017-01-04 21:19:31 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
+# Copyright 2015-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test snap packages."""
@@ -72,6 +72,7 @@
ISnapBuildSet,
)
from lp.snappy.interfaces.snapbuildjob import ISnapStoreUploadJobSource
+from lp.snappy.interfaces.snapstoreclient import ISnapStoreClient
from lp.snappy.model.snap import SnapSet
from lp.snappy.model.snapbuild import SnapFile
from lp.snappy.model.snapbuildjob import SnapBuildJob
@@ -86,6 +87,8 @@
StormStatementRecorder,
TestCaseWithFactory,
)
+from lp.testing.fakemethod import FakeMethod
+from lp.testing.fixture import ZopeUtilityFixture
from lp.testing.layers import (
DatabaseFunctionalLayer,
LaunchpadFunctionalLayer,
@@ -1119,6 +1122,15 @@
def setUp(self):
super(TestSnapWebservice, self).setUp()
self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
+ self.snap_store_client = FakeMethod()
+ self.snap_store_client.listChannels = FakeMethod(result=[
+ {"name": "stable", "display_name": "Stable"},
+ {"name": "edge", "display_name": "Edge"},
+ ])
+ self.snap_store_client.requestPackageUploadPermission = (
+ getUtility(ISnapStoreClient).requestPackageUploadPermission)
+ self.useFixture(
+ ZopeUtilityFixture(self.snap_store_client, ISnapStoreClient))
self.person = self.factory.makePerson(displayname="Test Person")
self.webservice = webservice_for_person(
self.person, permission=OAuthPermission.WRITE_PUBLIC)
@@ -1131,14 +1143,13 @@
def makeSnap(self, owner=None, distroseries=None, branch=None,
git_ref=None, processors=None, webservice=None,
private=False, auto_build_archive=None,
- auto_build_pocket=None):
+ auto_build_pocket=None, **kwargs):
if owner is None:
owner = self.person
if distroseries is None:
distroseries = self.factory.makeDistroSeries(registrant=owner)
if branch is None and git_ref is None:
branch = self.factory.makeAnyBranch()
- kwargs = {}
if webservice is None:
webservice = self.webservice
transaction.commit()
@@ -1222,6 +1233,21 @@
with person_logged_in(self.person):
self.assertTrue(snap["private"])
+ def test_new_store_options(self):
+ # Ensure store-related options in Snap.new work.
+ with admin_logged_in():
+ snappy_series = self.factory.makeSnappySeries()
+ store_name = self.factory.getUniqueUnicode()
+ snap = self.makeSnap(
+ store_upload=True, store_series=api_url(snappy_series),
+ store_name=store_name, store_channels=["edge"])
+ with person_logged_in(self.person):
+ self.assertTrue(snap["store_upload"])
+ self.assertEqual(
+ self.getURL(snappy_series), snap["store_series_link"])
+ self.assertEqual(store_name, snap["store_name"])
+ self.assertEqual(["edge"], snap["store_channels"])
+
def test_duplicate(self):
# An attempt to create a duplicate Snap fails.
team = self.factory.makeTeam(owner=self.person)
=== modified file 'lib/lp/snappy/tests/test_snapbuildjob.py'
--- lib/lp/snappy/tests/test_snapbuildjob.py 2016-11-03 20:31:00 +0000
+++ lib/lp/snappy/tests/test_snapbuildjob.py 2017-01-04 21:19:31 +0000
@@ -1,4 +1,4 @@
-# Copyright 2016 Canonical Ltd. This software is licensed under the
+# Copyright 2016-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for snap build jobs."""
@@ -49,6 +49,7 @@
def __init__(self):
self.upload = FakeMethod()
self.checkStatus = FakeMethod()
+ self.listChannels = FakeMethod(result=[])
self.release = FakeMethod()
=== modified file 'lib/lp/snappy/tests/test_snapstoreclient.py'
--- lib/lp/snappy/tests/test_snapstoreclient.py 2016-09-19 13:30:27 +0000
+++ lib/lp/snappy/tests/test_snapstoreclient.py 2017-01-04 21:19:31 +0000
@@ -1,4 +1,4 @@
-# Copyright 2016 Canonical Ltd. This software is licensed under the
+# Copyright 2016-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for communication with the snap store."""
@@ -182,6 +182,10 @@
"launchpad", openid_provider_root="http://sso.example/")
self.client = getUtility(ISnapStoreClient)
self.unscanned_upload_requests = []
+ self.channels = [
+ {"name": "stable", "display_name": "Stable"},
+ {"name": "edge", "display_name": "Edge"},
+ ]
def _make_store_secrets(self):
self.root_key = hashlib.sha256(
@@ -231,6 +235,14 @@
"content": {"discharge_macaroon": new_macaroon.serialize()},
}
+ @urlmatch(path=r".*/api/v1/channels$")
+ def _channels_handler(self, url, request):
+ self.channels_request = request
+ return {
+ "status_code": 200,
+ "content": {"_embedded": {"clickindex:channel": self.channels}},
+ }
+
@urlmatch(path=r".*/snap-release/$")
def _snap_release_handler(self, url, request):
self.snap_release_request = request
@@ -498,35 +510,22 @@
self.client.checkStatus, status_url)
def test_listChannels(self):
- expected_channels = [
- {"name": "stable", "display_name": "Stable"},
- {"name": "edge", "display_name": "Edge"},
- ]
-
- @all_requests
- def handler(url, request):
- self.request = request
- return {
- "status_code": 200,
- "content": {
- "_embedded": {"clickindex:channel": expected_channels}}}
-
memcache_key = "search.example:channels".encode("UTF-8")
try:
- with HTTMock(handler):
- self.assertEqual(expected_channels, self.client.listChannels())
- self.assertThat(self.request, RequestMatches(
+ with HTTMock(self._channels_handler):
+ self.assertEqual(self.channels, self.client.listChannels())
+ self.assertThat(self.channels_request, RequestMatches(
url=Equals("http://search.example/api/v1/channels"),
method=Equals("GET"),
headers=ContainsDict(
{"Accept": Equals("application/hal+json")})))
self.assertEqual(
- expected_channels,
+ self.channels,
json.loads(getUtility(IMemcacheClient).get(memcache_key)))
- self.request = None
- with HTTMock(handler):
- self.assertEqual(expected_channels, self.client.listChannels())
- self.assertIsNone(self.request)
+ self.channels_request = None
+ with HTTMock(self._channels_handler):
+ self.assertEqual(self.channels, self.client.listChannels())
+ self.assertIsNone(self.channels_request)
finally:
getUtility(IMemcacheClient).delete(memcache_key)
@@ -562,11 +561,13 @@
self.assertIsNone(getUtility(IMemcacheClient).get(memcache_key))
def test_release(self):
- snap = self.factory.makeSnap(
- store_upload=True,
- store_series=self.factory.makeSnappySeries(name="rolling"),
- store_name="test-snap", store_secrets=self._make_store_secrets(),
- store_channels=["stable", "edge"])
+ with HTTMock(self._channels_handler):
+ snap = self.factory.makeSnap(
+ store_upload=True,
+ store_series=self.factory.makeSnappySeries(name="rolling"),
+ store_name="test-snap",
+ store_secrets=self._make_store_secrets(),
+ store_channels=["stable", "edge"])
snapbuild = self.factory.makeSnapBuild(snap=snap)
with HTTMock(self._snap_release_handler):
self.client.release(snapbuild, 1)
@@ -588,11 +589,13 @@
"content": {"success": False, "errors": "Failed to publish"},
}
- snap = self.factory.makeSnap(
- store_upload=True,
- store_series=self.factory.makeSnappySeries(name="rolling"),
- store_name="test-snap", store_secrets=self._make_store_secrets(),
- store_channels=["stable", "edge"])
+ with HTTMock(self._channels_handler):
+ snap = self.factory.makeSnap(
+ store_upload=True,
+ store_series=self.factory.makeSnappySeries(name="rolling"),
+ store_name="test-snap",
+ store_secrets=self._make_store_secrets(),
+ store_channels=["stable", "edge"])
snapbuild = self.factory.makeSnapBuild(snap=snap)
with HTTMock(handler):
self.assertRaisesWithContent(
@@ -604,11 +607,13 @@
def handler(url, request):
return {"status_code": 404, "reason": b"Not found"}
- snap = self.factory.makeSnap(
- store_upload=True,
- store_series=self.factory.makeSnappySeries(name="rolling"),
- store_name="test-snap", store_secrets=self._make_store_secrets(),
- store_channels=["stable", "edge"])
+ with HTTMock(self._channels_handler):
+ snap = self.factory.makeSnap(
+ store_upload=True,
+ store_series=self.factory.makeSnappySeries(name="rolling"),
+ store_name="test-snap",
+ store_secrets=self._make_store_secrets(),
+ store_channels=["stable", "edge"])
snapbuild = self.factory.makeSnapBuild(snap=snap)
with HTTMock(handler):
self.assertRaisesWithContent(
=== modified file 'lib/lp/snappy/vocabularies.py'
--- lib/lp/snappy/vocabularies.py 2016-06-30 17:15:26 +0000
+++ lib/lp/snappy/vocabularies.py 2017-01-04 21:19:31 +0000
@@ -1,4 +1,4 @@
-# Copyright 2015-2016 Canonical Ltd. This software is licensed under the GNU
+# Copyright 2015-2017 Canonical Ltd. This software is licensed under the GNU
# Affero General Public License version 3 (see the file LICENSE).
"""Snappy vocabularies."""
@@ -10,18 +10,22 @@
'SnappySeriesVocabulary',
]
+from lazr.restful.interfaces import IJSONPublishable
from storm.locals import Desc
from zope.component import getUtility
+from zope.interface import implementer
from zope.schema.vocabulary import (
SimpleTerm,
SimpleVocabulary,
)
+from zope.security.proxy import removeSecurityProxy
from lp.registry.model.distribution import Distribution
from lp.registry.model.distroseries import DistroSeries
from lp.registry.model.series import ACTIVE_STATUSES
from lp.services.database.interfaces import IStore
from lp.services.webapp.vocabulary import StormVocabularyBase
+from lp.snappy.interfaces.snap import ISnap
from lp.snappy.interfaces.snapstoreclient import ISnapStoreClient
from lp.snappy.model.snappyseries import (
SnappyDistroSeries,
@@ -116,6 +120,15 @@
]
+@implementer(IJSONPublishable)
+class SnapStoreChannel(SimpleTerm):
+ """A store channel."""
+
+ def toDataForJSON(self, media_type):
+ """See `IJSONPublishable`."""
+ return self.token
+
+
class SnapStoreChannelVocabulary(SimpleVocabulary):
"""A vocabulary for searching store channels."""
@@ -125,4 +138,18 @@
self.createTerm(
channel["name"], channel["name"], channel["display_name"])
for channel in channels]
+ if ISnap.providedBy(context):
+ # Supplement the vocabulary with any obsolete channels still
+ # used by this context.
+ context_channels = removeSecurityProxy(context)._store_channels
+ if context_channels is not None:
+ known_names = set(channel["name"] for channel in channels)
+ for name in context_channels:
+ if name not in known_names:
+ terms.append(self.createTerm(name, name, name))
super(SnapStoreChannelVocabulary, self).__init__(terms)
+
+ @classmethod
+ def createTerm(cls, *args):
+ """See `SimpleVocabulary`."""
+ return SnapStoreChannel(*args)
=== modified file 'lib/lp/snappy/vocabularies.zcml'
--- lib/lp/snappy/vocabularies.zcml 2016-06-30 17:15:26 +0000
+++ lib/lp/snappy/vocabularies.zcml 2017-01-04 21:19:31 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2015-2016 Canonical Ltd. This software is licensed under the
+<!-- Copyright 2015-2017 Canonical Ltd. This software is licensed under the
GNU Affero General Public License version 3 (see the file LICENSE).
-->
@@ -48,6 +48,11 @@
<allow interface="lp.services.webapp.vocabulary.IHugeVocabulary" />
</class>
+ <class class="lp.snappy.vocabularies.SnapStoreChannel">
+ <allow interface="zope.schema.interfaces.ITitledTokenizedTerm
+ lazr.restful.interfaces.IJSONPublishable" />
+ </class>
+
<securedutility
name="SnapStoreChannel"
component="lp.snappy.vocabularies.SnapStoreChannelVocabulary"
Follow ups