launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #23477
[Merge] lp:~cjwatson/launchpad/snap-build-record-code into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/snap-build-record-code into lp:launchpad.
Commit message:
Record the code object from which a SnapBuild was created, for later use by security adapters.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1639975 in Launchpad itself: "support for building private snaps"
https://bugs.launchpad.net/launchpad/+bug/1639975
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/snap-build-record-code/+merge/365356
This is annoying, but I don't see any other way to cope with privacy changes of code objects that have snaps attached to them without leaking information about old builds.
We shouldn't land this until https://code.launchpad.net/~cjwatson/launchpad/branch-delete-job/+merge/364907 and https://code.launchpad.net/~cjwatson/launchpad/git-repository-delete-job/+merge/364910 have landed, as it'll make deletions slower; and, assuming all of these are approved, we'll need to add SELECT and DELETE permissions for the snap-build-job user on public.snapbuild before landing this.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/snap-build-record-code into lp:launchpad.
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2019-03-29 17:06:15 +0000
+++ lib/lp/code/model/branch.py 2019-04-01 15:03:40 +0000
@@ -196,6 +196,7 @@
from lp.services.webhooks.interfaces import IWebhookSet
from lp.services.webhooks.model import WebhookTargetMixin
from lp.snappy.interfaces.snap import ISnapSet
+from lp.snappy.interfaces.snapbuild import ISnapBuildSet
@implementer(IBranch, IPrivacy, IInformationType)
@@ -922,10 +923,9 @@
DeletionCallable(
recipe, _('This recipe uses this branch.'), recipe.destroySelf)
for recipe in self.recipes)
- if not getUtility(ISnapSet).findByBranch(self).is_empty():
- alteration_operations.append(DeletionCallable(
- None, _('Some snap packages build from this branch.'),
- getUtility(ISnapSet).detachFromBranch, self))
+ if (not getUtility(ISnapSet).findByBranch(self).is_empty() or
+ not getUtility(ISnapBuildSet).findByBranch(self).is_empty()):
+ alteration_operations.append(DetachSnaps(self))
return (alteration_operations, deletion_operations)
def deletionRequirements(self):
@@ -1639,6 +1639,19 @@
getUtility(ICodeImportSet).delete(self.affected_object)
+class DetachSnaps(DeletionOperation):
+ """Deletion operation that detaches snaps from a branch."""
+
+ def __init__(self, branch):
+ super(DetachSnaps, self).__init__(
+ None, _('Some snap packages build from this branch.'))
+ self.branch = branch
+
+ def __call__(self):
+ getUtility(ISnapSet).detachFromBranch(self.branch)
+ getUtility(ISnapBuildSet).detachFromBranch(self.branch)
+
+
@implementer(IBranchSet)
class BranchSet:
"""The set of all branches."""
=== modified file 'lib/lp/code/model/gitrepository.py'
--- lib/lp/code/model/gitrepository.py 2019-03-29 17:06:15 +0000
+++ lib/lp/code/model/gitrepository.py 2019-04-01 15:03:40 +0000
@@ -194,6 +194,7 @@
from lp.services.webhooks.interfaces import IWebhookSet
from lp.services.webhooks.model import WebhookTargetMixin
from lp.snappy.interfaces.snap import ISnapSet
+from lp.snappy.interfaces.snapbuild import ISnapBuildSet
object_type_map = {
@@ -1467,9 +1468,7 @@
recipe.destroySelf)
for recipe in self.recipes)
if not getUtility(ISnapSet).findByGitRepository(self).is_empty():
- alteration_operations.append(DeletionCallable(
- None, msg("Some snap packages build from this repository."),
- getUtility(ISnapSet).detachFromGitRepository, self))
+ alteration_operations.append(DetachSnaps(self))
return (alteration_operations, deletion_operations)
@@ -1621,6 +1620,19 @@
getUtility(ICodeImportSet).delete(self.affected_object)
+class DetachSnaps(DeletionOperation):
+ """Deletion operation that detaches snaps from a repository."""
+
+ def __init__(self, repository):
+ super(DetachSnaps, self).__init__(
+ None, msg("Some snap packages build from this repository."))
+ self.repository = repository
+
+ def __call__(self):
+ getUtility(ISnapSet).detachFromGitRepository(self.repository)
+ getUtility(ISnapBuildSet).detachFromGitRepository(self.repository)
+
+
@implementer(IGitRepositorySet)
class GitRepositorySet:
"""See `IGitRepositorySet`."""
=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py 2019-01-28 18:09:21 +0000
+++ lib/lp/code/model/tests/test_branch.py 2019-04-01 15:03:40 +0000
@@ -1697,7 +1697,7 @@
def test_snap_requirements(self):
# If a branch is used by a snap package, the deletion requirements
# indicate this.
- self.factory.makeSnap(branch=self.branch)
+ self.factory.makeSnapBuild(branch=self.branch)
self.assertEqual(
{None: ('alter', _('Some snap packages build from this branch.'))},
self.branch.deletionRequirements())
@@ -1705,7 +1705,9 @@
def test_snap_deletion(self):
# break_references allows deleting a branch used by a snap package.
snap1 = self.factory.makeSnap(branch=self.branch)
+ self.factory.makeSnapBuild(snap=snap1)
snap2 = self.factory.makeSnap(branch=self.branch)
+ self.factory.makeSnapBuild(snap=snap2)
self.branch.destroySelf(break_references=True)
transaction.commit()
self.assertIsNone(snap1.branch)
=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
--- lib/lp/code/model/tests/test_gitrepository.py 2019-02-11 12:31:06 +0000
+++ lib/lp/code/model/tests/test_gitrepository.py 2019-04-01 15:03:40 +0000
@@ -975,7 +975,7 @@
# If a repository is used by a snap package, the deletion
# requirements indicate this.
[ref] = self.factory.makeGitRefs()
- self.factory.makeSnap(git_ref=ref)
+ self.factory.makeSnapBuild(git_ref=ref)
self.assertEqual(
{None:
("alter", _("Some snap packages build from this repository."))},
@@ -987,7 +987,9 @@
[ref1, ref2] = self.factory.makeGitRefs(
repository=repository, paths=["refs/heads/1", "refs/heads/2"])
snap1 = self.factory.makeSnap(git_ref=ref1)
+ self.factory.makeSnapBuild(snap=snap1)
snap2 = self.factory.makeSnap(git_ref=ref2)
+ self.factory.makeSnapBuild(snap=snap2)
repository.destroySelf(break_references=True)
transaction.commit()
self.assertIsNone(snap1.git_repository)
=== modified file 'lib/lp/snappy/interfaces/snapbuild.py'
--- lib/lp/snappy/interfaces/snapbuild.py 2018-12-12 10:31:40 +0000
+++ lib/lp/snappy/interfaces/snapbuild.py 2019-04-01 15:03:40 +0000
@@ -51,6 +51,8 @@
from lp import _
from lp.buildmaster.interfaces.buildfarmjob import ISpecificBuildFarmJobSource
from lp.buildmaster.interfaces.packagebuild import IPackageBuild
+from lp.code.interfaces.branch import IBranch
+from lp.code.interfaces.gitrepository import IGitRepository
from lp.registry.interfaces.person import IPerson
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.services.database.constants import DEFAULT
@@ -248,6 +250,20 @@
store_upload_metadata = Attribute(
_("A dict of data about store upload progress."))
+ branch = Reference(
+ IBranch,
+ title=_("Bazaar branch"), required=False, readonly=True)
+
+ git_repository = Reference(
+ IGitRepository,
+ title=_("Git repository"), required=False, readonly=True)
+
+ git_repository_url = TextLine(
+ title=_("Git repository URL"), required=False, readonly=True)
+
+ git_path = TextLine(
+ title=_("Git branch path"), required=False, readonly=True)
+
def getFiles():
"""Retrieve the build's `ISnapFile` records.
@@ -343,3 +359,15 @@
def preloadBuildsData(builds):
"""Load the data related to a list of snap builds."""
+
+ def findByBranch(branch):
+ """Return all snap builds for the given Bazaar branch."""
+
+ def findByGitRepository(repository, paths=None):
+ """Return all snap builds for the given Git repository."""
+
+ def detachFromBranch(branch):
+ """Detach all snap builds from the given Bazaar branch."""
+
+ def detachFromGitRepository(repository):
+ """Detach all snap builds from the given Git repository."""
=== modified file 'lib/lp/snappy/model/snapbuild.py'
--- lib/lp/snappy/model/snapbuild.py 2018-12-18 18:14:37 +0000
+++ lib/lp/snappy/model/snapbuild.py 2019-04-01 15:03:40 +0000
@@ -177,6 +177,16 @@
store_upload_metadata = JSON('store_upload_json_data', allow_none=True)
+ branch_id = Int(name='branch', allow_none=True)
+ branch = Reference(branch_id, 'Branch.id')
+
+ git_repository_id = Int(name='git_repository', allow_none=True)
+ git_repository = Reference(git_repository_id, 'GitRepository.id')
+
+ git_repository_url = Unicode(name='git_repository_url', allow_none=True)
+
+ git_path = Unicode(name='git_path', allow_none=True)
+
def __init__(self, build_farm_job, requester, snap, archive,
distro_arch_series, pocket, channels, processor, virtualized,
date_created, store_upload_metadata=None, build_request=None):
@@ -196,6 +206,12 @@
if build_request is not None:
self.build_request_id = build_request.id
self.status = BuildStatus.NEEDSBUILD
+ # Copy branch/repository information from the snap, for future
+ # privacy checks on this build.
+ self.branch = snap.branch
+ self.git_repository = snap.git_repository
+ self.git_repository_url = snap.git_repository_url
+ self.git_path = snap.git_path
@property
def build_request(self):
@@ -584,3 +600,21 @@
SnapBuild, SnapBuild.build_farm_job_id.is_in(
bfj.id for bfj in build_farm_jobs))
return DecoratedResultSet(rows, pre_iter_hook=self.preloadBuildsData)
+
+ def findByBranch(self, branch):
+ """See `ISnapBuildSet`."""
+ return IStore(SnapBuild).find(SnapBuild, SnapBuild.branch == branch)
+
+ def findByGitRepository(self, repository):
+ """See `ISnapBuildSet`."""
+ return IStore(SnapBuild).find(
+ SnapBuild, SnapBuild.git_repository == repository)
+
+ def detachFromBranch(self, branch):
+ """See `ISnapBuildSet`."""
+ self.findByBranch(branch).set(branch_id=None)
+
+ def detachFromGitRepository(self, repository):
+ """See `ISnapBuildSet`."""
+ self.findByGitRepository(repository).set(
+ git_repository_id=None, git_path=None)
=== modified file 'lib/lp/snappy/model/snapbuildbehaviour.py'
--- lib/lp/snappy/model/snapbuildbehaviour.py 2019-03-06 23:00:49 +0000
+++ lib/lp/snappy/model/snapbuildbehaviour.py 2019-04-01 15:03:40 +0000
@@ -123,6 +123,8 @@
# dict, since otherwise we'll be unable to serialise it to
# XML-RPC.
args["channels"] = removeSecurityProxy(channels)
+ # XXX cjwatson 2019-04-01: Once new SnapBuilds are being created on
+ # production with code object columns, we should use them here.
if build.snap.branch is not None:
args["branch"] = build.snap.branch.bzr_identity
elif build.snap.git_ref is not None:
=== modified file 'lib/lp/snappy/tests/test_snapbuild.py'
--- lib/lp/snappy/tests/test_snapbuild.py 2018-12-18 18:23:52 +0000
+++ lib/lp/snappy/tests/test_snapbuild.py 2019-04-01 15:03:40 +0000
@@ -163,6 +163,30 @@
build = self.factory.makeSnapBuild(archive=private_archive)
self.assertTrue(build.is_private)
+ def test_copies_code_information(self):
+ # Creating a SnapBuild copies code information from its parent Snap.
+ for args in (
+ {"branch": self.factory.makeAnyBranch()},
+ {"git_ref": self.factory.makeGitRefs()[0]},
+ {"git_ref": self.factory.makeGitRefRemote()},
+ ):
+ snap = self.factory.makeSnap(**args)
+ build = self.factory.makeSnapBuild(snap=snap)
+ snap.branch = self.factory.makeAnyBranch()
+ snap.git_ref = None
+ if "branch" in args:
+ self.assertThat(build, MatchesStructure(
+ branch=Equals(args["branch"]),
+ git_repository=Is(None),
+ git_repository_url=Is(None),
+ git_path=Is(None)))
+ else:
+ self.assertThat(build, MatchesStructure(
+ branch=Is(None),
+ git_repository=Equals(args["git_ref"].repository),
+ git_repository_url=Equals(args["git_ref"].repository_url),
+ git_path=Equals(args["git_ref"].path)))
+
def test_can_be_cancelled(self):
# For all states that can be cancelled, can_be_cancelled returns True.
ok_cases = [
Follow ups