launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #26760
[Merge] ~cjwatson/launchpad:archive-dependency-snap-base into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:archive-dependency-snap-base into launchpad:master.
Commit message:
Add archive dependencies for SnapBase
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/400356
This will be used for the "core" snap base once 16.04 enters ESM.
There's no dispatch support in the snap build behaviour yet; that will come in a future branch.
DB MP: https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/400347
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:archive-dependency-snap-base into launchpad:master.
diff --git a/lib/lp/_schema_circular_imports.py b/lib/lp/_schema_circular_imports.py
index 6e2cbfc..a61d39d 100644
--- a/lib/lp/_schema_circular_imports.py
+++ b/lib/lp/_schema_circular_imports.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Update the interface schema values due to circular imports.
@@ -167,6 +167,7 @@ from lp.services.worlddata.interfaces.language import (
ILanguage,
ILanguageSet,
)
+from lp.snappy.interfaces.snapbase import ISnapBase
from lp.soyuz.interfaces.archive import IArchive
from lp.soyuz.interfaces.archivedependency import IArchiveDependency
from lp.soyuz.interfaces.archivepermission import IArchivePermission
@@ -418,6 +419,9 @@ patch_plain_parameter_type(
patch_entry_return_type(
IArchive, '_addArchiveDependency', IArchiveDependency)
+# IArchiveDependency
+patch_reference_property(IArchiveDependency, 'snap_base', ISnapBase)
+
# IBuildFarmJob
patch_reference_property(IBuildFarmJob, 'buildqueue_record', IBuildQueue)
diff --git a/lib/lp/snappy/browser/configure.zcml b/lib/lp/snappy/browser/configure.zcml
index a21c0b4..21d849f 100644
--- a/lib/lp/snappy/browser/configure.zcml
+++ b/lib/lp/snappy/browser/configure.zcml
@@ -198,7 +198,9 @@
parent_utility="lp.services.webapp.interfaces.ILaunchpadRoot" />
<browser:navigation
module="lp.snappy.browser.snapbase"
- classes="SnapBaseSetNavigation" />
+ classes="
+ SnapBaseNavigation
+ SnapBaseSetNavigation" />
<browser:page
for="*"
diff --git a/lib/lp/snappy/browser/snapbase.py b/lib/lp/snappy/browser/snapbase.py
index 488aac6..73d1139 100644
--- a/lib/lp/snappy/browser/snapbase.py
+++ b/lib/lp/snappy/browser/snapbase.py
@@ -1,4 +1,4 @@
-# Copyright 2019 Canonical Ltd. This software is licensed under the
+# Copyright 2019-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Views of bases for snaps."""
@@ -7,11 +7,50 @@ from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
+ "SnapBaseNavigation",
"SnapBaseSetNavigation",
]
-from lp.services.webapp import GetitemNavigation
-from lp.snappy.interfaces.snapbase import ISnapBaseSet
+from sqlobject import SQLObjectNotFound
+from zope.component import getUtility
+
+from lp.services.webapp import (
+ GetitemNavigation,
+ Navigation,
+ stepthrough,
+ )
+from lp.snappy.interfaces.snapbase import (
+ ISnapBase,
+ ISnapBaseSet,
+ )
+from lp.soyuz.interfaces.archive import IArchiveSet
+
+
+class SnapBaseNavigation(Navigation):
+ """Navigation methods for `ISnapBase`."""
+
+ usedfor = ISnapBase
+
+ @stepthrough("+dependency")
+ def traverse_dependency(self, id):
+ """Traverse to an archive dependency by archive ID.
+
+ We use `IArchive.getArchiveDependency` here, which is protected by
+ `launchpad.View`, so you cannot get to a dependency of a private
+ archive that you can't see.
+ """
+ try:
+ id = int(id)
+ except ValueError:
+ # Not a number.
+ return None
+
+ try:
+ archive = getUtility(IArchiveSet).get(id)
+ except SQLObjectNotFound:
+ return None
+
+ return self.context.getArchiveDependency(archive)
class SnapBaseSetNavigation(GetitemNavigation):
diff --git a/lib/lp/snappy/interfaces/snapbase.py b/lib/lp/snappy/interfaces/snapbase.py
index 5a3a97c..a5fdf90 100644
--- a/lib/lp/snappy/interfaces/snapbase.py
+++ b/lib/lp/snappy/interfaces/snapbase.py
@@ -1,4 +1,4 @@
-# Copyright 2019 Canonical Ltd. This software is licensed under the
+# Copyright 2019-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Interfaces for bases for snaps."""
@@ -19,6 +19,7 @@ from lazr.restful.declarations import (
error_status,
export_destructor_operation,
export_factory_operation,
+ export_operation_as,
export_read_operation,
export_write_operation,
exported,
@@ -29,7 +30,12 @@ from lazr.restful.declarations import (
operation_returns_entry,
REQUEST_USER,
)
-from lazr.restful.fields import Reference
+from lazr.restful.fields import (
+ CollectionField,
+ Reference,
+ )
+from lazr.restful.interface import copy_field
+import six
from six.moves import http_client
from zope.component import getUtility
from zope.interface import Interface
@@ -49,6 +55,8 @@ from lp.services.fields import (
ContentNameField,
PublicPersonChoice,
)
+from lp.soyuz.interfaces.archive import IArchive
+from lp.soyuz.interfaces.archivedependency import IArchiveDependency
class NoSuchSnapBase(NameLookupFailed):
@@ -99,6 +107,24 @@ class ISnapBaseView(Interface):
"Whether this base is the default for snaps that do not specify a "
"base.")))
+ dependencies = exported(CollectionField(
+ title=_("Archive dependencies for this snap base."),
+ value_type=Reference(schema=IArchiveDependency),
+ readonly=True))
+
+ @operation_parameters(dependency=Reference(schema=IArchive))
+ @operation_returns_entry(schema=IArchiveDependency)
+ @export_read_operation()
+ @operation_for_version("devel")
+ def getArchiveDependency(dependency):
+ """Return the `IArchiveDependency` object for the given dependency.
+
+ :param dependency: an `IArchive`.
+
+ :return: an `IArchiveDependency`, or None if a corresponding object
+ could not be found.
+ """
+
class ISnapBaseEditableAttributes(Interface):
"""`ISnapBase` attributes that can be edited.
@@ -128,6 +154,49 @@ class ISnapBaseEditableAttributes(Interface):
class ISnapBaseEdit(Interface):
"""`ISnapBase` methods that require launchpad.Edit permission."""
+ def addArchiveDependency(dependency, pocket, component=None):
+ """Add an archive dependency for this snap base.
+
+ :param dependency: an `IArchive`.
+ :param pocket: a `PackagePublishingPocket`.
+ :param component: an optional `IComponent` object; if not given, the
+ archive dependency will use the component used for dependencies
+ on the primary archive.
+
+ :raise: `ArchiveDependencyError` if the given `dependency` does not
+ fit this snap base.
+ :return: an `IArchiveDependency`.
+ """
+
+ @operation_parameters(
+ component=copy_field(IArchiveDependency["component_name"]))
+ @export_operation_as(six.ensure_str("addArchiveDependency"))
+ @export_factory_operation(IArchiveDependency, ["dependency", "pocket"])
+ @operation_for_version("devel")
+ def _addArchiveDependency(dependency, pocket, component=None):
+ """Add an archive dependency for this snap base.
+
+ :param dependency: an `IArchive`.
+ :param pocket: a `PackagePublishingPocket`.
+ :param component: an optional component name; if not given, the
+ archive dependency will use the component used for dependencies
+ on the primary archive.
+
+ :raise: `ArchiveDependencyError` if the given `dependency` does not
+ fit this snap base.
+ :return: an `IArchiveDependency`.
+ """
+
+ @operation_parameters(
+ dependency=Reference(schema=IArchive, required=True))
+ @export_write_operation()
+ @operation_for_version("devel")
+ def removeArchiveDependency(dependency):
+ """Remove the archive dependency on the given archive.
+
+ :param dependency: an `IArchive`.
+ """
+
@export_destructor_operation()
@operation_for_version("devel")
def destroySelf():
@@ -141,7 +210,7 @@ class ISnapBaseEdit(Interface):
# generation working. Individual attributes must set their version to
# "devel".
@exported_as_webservice_entry(as_of="beta")
-class ISnapBase(ISnapBaseView, ISnapBaseEditableAttributes):
+class ISnapBase(ISnapBaseView, ISnapBaseEditableAttributes, ISnapBaseEdit):
"""A base for snaps."""
diff --git a/lib/lp/snappy/model/snapbase.py b/lib/lp/snappy/model/snapbase.py
index d915caa..615c783 100644
--- a/lib/lp/snappy/model/snapbase.py
+++ b/lib/lp/snappy/model/snapbase.py
@@ -1,4 +1,4 @@
-# Copyright 2019 Canonical Ltd. This software is licensed under the
+# Copyright 2019-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Bases for snaps."""
@@ -11,6 +11,7 @@ __all__ = [
]
import pytz
+import six
from storm.locals import (
Bool,
DateTime,
@@ -21,9 +22,13 @@ from storm.locals import (
Storm,
Unicode,
)
+from zope.component import getUtility
from zope.interface import implementer
from zope.security.proxy import removeSecurityProxy
+from lp.app.errors import NotFoundError
+from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.registry.model.person import Person
from lp.services.database.constants import DEFAULT
from lp.services.database.interfaces import (
IMasterStore,
@@ -35,6 +40,13 @@ from lp.snappy.interfaces.snapbase import (
ISnapBaseSet,
NoSuchSnapBase,
)
+from lp.soyuz.interfaces.archive import (
+ ArchiveDependencyError,
+ ComponentNotFound,
+ )
+from lp.soyuz.interfaces.component import IComponentSet
+from lp.soyuz.model.archive import Archive
+from lp.soyuz.model.archivedependency import ArchiveDependency
@implementer(ISnapBase)
@@ -73,6 +85,63 @@ class SnapBase(Storm):
self.date_created = date_created
self.is_default = False
+ @property
+ def dependencies(self):
+ """See `ISnapBase`."""
+ return IStore(ArchiveDependency).find(
+ ArchiveDependency,
+ ArchiveDependency.dependency == Archive.id,
+ Archive.owner == Person.id,
+ ArchiveDependency.snap_base == self).order_by(Person.display_name)
+
+ def getArchiveDependency(self, dependency):
+ """See `ISnapBase`."""
+ return IStore(ArchiveDependency).find(
+ ArchiveDependency, snap_base=self, dependency=dependency).one()
+
+ def addArchiveDependency(self, dependency, pocket, component=None):
+ """See `ISnapBase`."""
+ archive_dependency = self.getArchiveDependency(dependency)
+ if archive_dependency is not None:
+ raise ArchiveDependencyError(
+ "This dependency is already registered.")
+ # XXX cjwatson 2021-03-19: Relax this once we have a way to dispatch
+ # appropriate tokens for snap builds whose base has dependencies on
+ # private archives.
+ if dependency.private:
+ raise ArchiveDependencyError("This dependency is private.")
+ if not dependency.enabled:
+ raise ArchiveDependencyError("Dependencies must not be disabled.")
+
+ if dependency.is_ppa:
+ if pocket is not PackagePublishingPocket.RELEASE:
+ raise ArchiveDependencyError(
+ "Non-primary archives only support the RELEASE pocket.")
+ if (component is not None and
+ component != dependency.default_component):
+ raise ArchiveDependencyError(
+ "Non-primary archives only support the '%s' component." %
+ dependency.default_component.name)
+ return ArchiveDependency(
+ parent=self, dependency=dependency, pocket=pocket,
+ component=component)
+
+ def _addArchiveDependency(self, dependency, pocket, component=None):
+ """See `ISnapBase`."""
+ if isinstance(component, six.text_type):
+ try:
+ component = getUtility(IComponentSet)[component]
+ except NotFoundError as e:
+ raise ComponentNotFound(e)
+ return self.addArchiveDependency(dependency, pocket, component)
+
+ def removeArchiveDependency(self, dependency):
+ """See `ISnapBase`."""
+ archive_dependency = self.getArchiveDependency(dependency)
+ if archive_dependency is None:
+ raise ArchiveDependencyError("This dependency does not exist.")
+ archive_dependency.destroySelf()
+
def destroySelf(self):
"""See `ISnapBase`."""
# Guard against unfortunate accidents.
diff --git a/lib/lp/snappy/tests/test_snapbase.py b/lib/lp/snappy/tests/test_snapbase.py
index f45feda..67754d2 100644
--- a/lib/lp/snappy/tests/test_snapbase.py
+++ b/lib/lp/snappy/tests/test_snapbase.py
@@ -1,4 +1,4 @@
-# Copyright 2019 Canonical Ltd. This software is licensed under the
+# Copyright 2019-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test bases for snaps."""
@@ -11,6 +11,9 @@ from testtools.matchers import (
ContainsDict,
Equals,
Is,
+ MatchesListwise,
+ MatchesRegex,
+ MatchesStructure,
)
from zope.component import (
getAdapter,
@@ -18,6 +21,7 @@ from zope.component import (
)
from lp.app.interfaces.security import IAuthorization
+from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.services.webapp.interfaces import OAuthPermission
from lp.snappy.interfaces.snapbase import (
CannotDeleteSnapBase,
@@ -25,6 +29,7 @@ from lp.snappy.interfaces.snapbase import (
ISnapBaseSet,
NoSuchSnapBase,
)
+from lp.soyuz.interfaces.component import IComponentSet
from lp.testing import (
api_url,
celebrity_logged_in,
@@ -257,6 +262,112 @@ class TestSnapBaseWebservice(TestCaseWithFactory):
self.assertEqual(
snap_bases[1], getUtility(ISnapBaseSet).getDefault())
+ def test_addArchiveDependency_unpriv(self):
+ # An unprivileged user cannot add an archive dependency.
+ person = self.factory.makePerson()
+ with celebrity_logged_in("registry_experts"):
+ snap_base = self.factory.makeSnapBase()
+ archive = self.factory.makeArchive()
+ snap_base_url = api_url(snap_base)
+ archive_url = api_url(archive)
+ webservice = webservice_for_person(
+ person, permission=OAuthPermission.WRITE_PUBLIC)
+ webservice.default_api_version = "devel"
+ response = webservice.named_post(
+ snap_base_url, "addArchiveDependency",
+ dependency=archive_url, pocket="Release", component="main")
+ self.assertThat(response, MatchesStructure(
+ status=Equals(401),
+ body=MatchesRegex(br".*addArchiveDependency.*launchpad.Edit.*")))
+
+ def test_addArchiveDependency(self):
+ # A registry expert can add an archive dependency.
+ person = self.factory.makeRegistryExpert()
+ with person_logged_in(person):
+ snap_base = self.factory.makeSnapBase()
+ archive = self.factory.makeArchive()
+ snap_base_url = api_url(snap_base)
+ archive_url = api_url(archive)
+ self.assertEqual([], list(snap_base.dependencies))
+ webservice = webservice_for_person(
+ person, permission=OAuthPermission.WRITE_PUBLIC)
+ webservice.default_api_version = "devel"
+ response = webservice.named_post(
+ snap_base_url, "addArchiveDependency",
+ dependency=archive_url, pocket="Release", component="main")
+ self.assertEqual(201, response.status)
+ with person_logged_in(person):
+ self.assertThat(list(snap_base.dependencies), MatchesListwise([
+ MatchesStructure(
+ archive=Is(None),
+ snap_base=Equals(snap_base),
+ dependency=Equals(archive),
+ pocket=Equals(PackagePublishingPocket.RELEASE),
+ component=Equals(getUtility(IComponentSet)["main"]),
+ component_name=Equals("main"),
+ title=Equals(archive.displayname),
+ ),
+ ]))
+
+ def test_addArchiveDependency_invalid(self):
+ # Invalid requests generate a BadRequest error.
+ person = self.factory.makeRegistryExpert()
+ with person_logged_in(person):
+ snap_base = self.factory.makeSnapBase()
+ archive = self.factory.makeArchive()
+ snap_base.addArchiveDependency(
+ archive, PackagePublishingPocket.RELEASE)
+ snap_base_url = api_url(snap_base)
+ archive_url = api_url(archive)
+ webservice = webservice_for_person(
+ person, permission=OAuthPermission.WRITE_PUBLIC)
+ webservice.default_api_version = "devel"
+ response = webservice.named_post(
+ snap_base_url, "addArchiveDependency",
+ dependency=archive_url, pocket="Release", component="main")
+ self.assertThat(response, MatchesStructure.byEquality(
+ status=400, body=b"This dependency is already registered."))
+
+ def test_removeArchiveDependency_unpriv(self):
+ # An unprivileged user cannot remove an archive dependency.
+ person = self.factory.makePerson()
+ with celebrity_logged_in("registry_experts"):
+ snap_base = self.factory.makeSnapBase()
+ archive = self.factory.makeArchive()
+ snap_base.addArchiveDependency(
+ archive, PackagePublishingPocket.RELEASE)
+ snap_base_url = api_url(snap_base)
+ archive_url = api_url(archive)
+ webservice = webservice_for_person(
+ person, permission=OAuthPermission.WRITE_PUBLIC)
+ webservice.default_api_version = "devel"
+ response = webservice.named_post(
+ snap_base_url, "removeArchiveDependency", dependency=archive_url)
+ self.assertThat(response, MatchesStructure(
+ status=Equals(401),
+ body=MatchesRegex(
+ br".*removeArchiveDependency.*launchpad.Edit.*")))
+
+ def test_removeArchiveDependency(self):
+ # A registry expert can remove an archive dependency.
+ person = self.factory.makeRegistryExpert()
+ with person_logged_in(person):
+ snap_base = self.factory.makeSnapBase()
+ archive = self.factory.makeArchive()
+ snap_base.addArchiveDependency(
+ archive, PackagePublishingPocket.RELEASE)
+ snap_base_url = api_url(snap_base)
+ archive_url = api_url(archive)
+ self.assertNotEqual([], list(snap_base.dependencies))
+ webservice = webservice_for_person(
+ person, permission=OAuthPermission.WRITE_PUBLIC)
+ webservice.default_api_version = "devel"
+ response = webservice.named_post(
+ snap_base_url, "removeArchiveDependency", dependency=archive_url)
+ self.assertEqual(200, response.status)
+ with person_logged_in(person):
+ self.assertEqual([], list(snap_base.dependencies))
+
def test_collection(self):
# lp.snap_bases is a collection of all SnapBases.
person = self.factory.makePerson()
diff --git a/lib/lp/soyuz/browser/configure.zcml b/lib/lp/soyuz/browser/configure.zcml
index 5aea5bd..ea111dd 100644
--- a/lib/lp/soyuz/browser/configure.zcml
+++ b/lib/lp/soyuz/browser/configure.zcml
@@ -1,4 +1,4 @@
-<!-- Copyright 2009-2020 Canonical Ltd. This software is licensed under the
+<!-- Copyright 2009-2021 Canonical Ltd. This software is licensed under the
GNU Affero General Public License version 3 (see the file LICENSE).
-->
@@ -676,7 +676,7 @@
<browser:url
for="lp.soyuz.interfaces.archivedependency.IArchiveDependency"
path_expression="string:+dependency/${dependency/id}"
- attribute_to_parent="archive"
+ attribute_to_parent="parent"
/>
<browser:url
for="lp.soyuz.interfaces.binarypackagerelease.IBinaryPackageReleaseDownloadCount"
diff --git a/lib/lp/soyuz/interfaces/archivedependency.py b/lib/lp/soyuz/interfaces/archivedependency.py
index b6f7092..a3a1525 100644
--- a/lib/lp/soyuz/interfaces/archivedependency.py
+++ b/lib/lp/soyuz/interfaces/archivedependency.py
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""ArchiveDependency interface."""
@@ -14,7 +14,10 @@ from lazr.restful.declarations import (
exported_as_webservice_entry,
)
from lazr.restful.fields import Reference
-from zope.interface import Interface
+from zope.interface import (
+ Attribute,
+ Interface,
+ )
from zope.schema import (
Choice,
Datetime,
@@ -38,11 +41,23 @@ class IArchiveDependency(Interface):
title=_("Instant when the dependency was created."),
required=False, readonly=True))
+ # The object that has the dependency: exactly one of archive or
+ # snap_base is required (enforced by DB constraints).
+
archive = exported(
Reference(
- schema=IArchive, required=True, readonly=True,
+ schema=IArchive, required=False, readonly=True,
title=_('Target archive'),
- description=_("The archive affected by this dependecy.")))
+ description=_("The archive that has this dependency.")))
+
+ snap_base = exported(
+ Reference(
+ # Really ISnapBase, patched in _schema_circular_imports.py.
+ schema=Interface, required=False, readonly=True,
+ title=_('Target snap base'),
+ description=_("The snap base that has this dependency.")))
+
+ parent = Attribute("The object that has this dependency.")
dependency = exported(
Reference(
@@ -55,14 +70,14 @@ class IArchiveDependency(Interface):
vocabulary=PackagePublishingPocket))
component = Choice(
- title=_("Component"), required=True, readonly=True,
+ title=_("Component"), required=False, readonly=True,
vocabulary='Component')
# We don't want to export IComponent, so the name is exported specially.
component_name = exported(
TextLine(
title=_("Component name"),
- required=True, readonly=True))
+ required=False, readonly=True))
title = exported(
TextLine(title=_("Archive dependency title."), readonly=True))
diff --git a/lib/lp/soyuz/model/archive.py b/lib/lp/soyuz/model/archive.py
index 9696b8f..e51898b 100644
--- a/lib/lp/soyuz/model/archive.py
+++ b/lib/lp/soyuz/model/archive.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Database class for table Archive."""
@@ -1167,7 +1167,7 @@ class Archive(SQLBase):
"Non-primary archives only support the '%s' component." %
dependency.default_component.name)
return ArchiveDependency(
- archive=self, dependency=dependency, pocket=pocket,
+ parent=self, dependency=dependency, pocket=pocket,
component=component)
def _addArchiveDependency(self, dependency, pocket, component=None):
diff --git a/lib/lp/soyuz/model/archivedependency.py b/lib/lp/soyuz/model/archivedependency.py
index 8fcd899..49621bf 100644
--- a/lib/lp/soyuz/model/archivedependency.py
+++ b/lib/lp/soyuz/model/archivedependency.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2021 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Database class for ArchiveDependency."""
@@ -20,7 +20,9 @@ from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.services.database.constants import UTC_NOW
from lp.services.database.enumcol import DBEnum
from lp.services.database.stormbase import StormBase
+from lp.snappy.interfaces.snapbase import ISnapBase
from lp.soyuz.adapters.archivedependencies import get_components_for_context
+from lp.soyuz.interfaces.archive import IArchive
from lp.soyuz.interfaces.archivedependency import IArchiveDependency
@@ -37,9 +39,12 @@ class ArchiveDependency(StormBase):
name='date_created', tzinfo=pytz.UTC, allow_none=False,
default=UTC_NOW)
- archive_id = Int(name='archive', allow_none=False)
+ archive_id = Int(name='archive', allow_none=True)
archive = Reference(archive_id, 'Archive.id')
+ snap_base_id = Int(name='snap_base', allow_none=True)
+ snap_base = Reference(snap_base_id, 'SnapBase.id')
+
dependency_id = Int(name='dependency', allow_none=False)
dependency = Reference(dependency_id, 'Archive.id')
@@ -49,14 +54,33 @@ class ArchiveDependency(StormBase):
component_id = Int(name='component', allow_none=True)
component = Reference(component_id, 'Component.id')
- def __init__(self, archive, dependency, pocket, component=None):
+ def __init__(self, parent, dependency, pocket, component=None):
super(ArchiveDependency, self).__init__()
- self.archive = archive
+ self.parent = parent
self.dependency = dependency
self.pocket = pocket
self.component = component
@property
+ def parent(self):
+ if self.archive is not None:
+ return self.archive
+ else:
+ return self.snap_base
+
+ @parent.setter
+ def parent(self, value):
+ if IArchive.providedBy(value):
+ self.archive = value
+ self.snap_base = None
+ elif ISnapBase.providedBy(value):
+ self.archive = None
+ self.snap_base = value
+ else:
+ raise AssertionError(
+ "Unknown archive dependency parent %s" % value)
+
+ @property
def component_name(self):
"""See `IArchiveDependency`"""
if self.component:
@@ -76,11 +100,14 @@ class ArchiveDependency(StormBase):
if self.component is None:
return pocket_title
- # XXX cjwatson 2016-03-31: This may be inaccurate, but we can't do
- # much better since this ArchiveDependency applies to multiple
- # series which may each resolve component dependencies in different
- # ways.
- distroseries = self.archive.distribution.currentseries
+ if self.archive is not None:
+ # XXX cjwatson 2016-03-31: This may be inaccurate, but we can't
+ # do much better since this ArchiveDependency applies to
+ # multiple series which may each resolve component dependencies
+ # in different ways.
+ distroseries = self.archive.distribution.currentseries
+ else:
+ distroseries = self.snap_base.distro_series
component_part = ", ".join(get_components_for_context(
self.component, distroseries, self.pocket))