Merge lp:~cjwatson/launchpad/pocket-permissions into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: Benji York
Approved revision: no longer in the source branch.
Merged at revision: 15394
Proposed branch: lp:~cjwatson/launchpad/pocket-permissions
Merge into: lp:launchpad
Diff against target: 1874 lines (+637/-678)
14 files modified
lib/lp/_schema_circular_imports.py (+11/-0)
lib/lp/code/tests/test_branch.py (+1/-19)
lib/lp/soyuz/doc/archive.txt (+15/-546)
lib/lp/soyuz/doc/archivearch.txt (+0/-72)
lib/lp/soyuz/doc/archivepermission.txt (+2/-2)
lib/lp/soyuz/interfaces/archive.py (+77/-1)
lib/lp/soyuz/interfaces/archivepermission.py (+49/-1)
lib/lp/soyuz/model/archive.py (+40/-8)
lib/lp/soyuz/model/archivepermission.py (+47/-4)
lib/lp/soyuz/stories/webservice/xx-archive.txt (+4/-0)
lib/lp/soyuz/stories/webservice/xx-packageset.txt (+1/-0)
lib/lp/soyuz/tests/test_archive.py (+360/-15)
lib/lp/soyuz/tests/test_archivearch.py (+29/-7)
lib/lp/soyuz/tests/test_packageupload.py (+1/-3)
To merge this branch: bzr merge lp:~cjwatson/launchpad/pocket-permissions
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+109192@code.launchpad.net

Commit message

Allow creating upload permissions for pockets.

Description of the change

== Summary ==

Bug 914779: we'd like to have per-pocket upload permissions.

== Proposed fix ==

In https://code.launchpad.net/~cjwatson/launchpad/db-pocket-permissions/+merge/106091, I added a database column to support this work, which has been deployed. This adds the (I think) fairly obvious set of archive methods to create, delete, and query archive permissions for pockets.

== LOC Rationale ==

I hate doctests, so I went through archive.txt and archivearch.txt and variously deleted tests that duplicated existing unit tests and converted tests to unit tests when they weren't already covered there. This was enough to bring me down to -46.

== Tests ==

bin/test -vvct soyuz.tests.test_archive

== Demo and Q/A ==

On dogfood, create a pocket upload permission (say, for -backports) using Archive.newPocketUploader for a user who doesn't otherwise have upload permission, and try to upload something.

== Lint ==

Just two false positives due to pocketlint not understanding property setters:

./lib/lp/soyuz/model/archive.py
     380: redefinition of function 'suppress_subscription_notifications' from line 371
     396: redefinition of function 'private' from line 392

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

This branch looks good. I can't see anything worth changing.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/_schema_circular_imports.py'
--- lib/lp/_schema_circular_imports.py 2012-06-10 09:06:08 +0000
+++ lib/lp/_schema_circular_imports.py 2012-06-12 00:00:37 +0000
@@ -397,9 +397,14 @@
397 IArchive, 'getQueueAdminsForComponent', IArchivePermission)397 IArchive, 'getQueueAdminsForComponent', IArchivePermission)
398patch_collection_return_type(398patch_collection_return_type(
399 IArchive, 'getComponentsForQueueAdmin', IArchivePermission)399 IArchive, 'getComponentsForQueueAdmin', IArchivePermission)
400patch_collection_return_type(
401 IArchive, 'getPocketsForUploader', IArchivePermission)
402patch_collection_return_type(
403 IArchive, 'getUploadersForPocket', IArchivePermission)
400patch_entry_return_type(IArchive, 'newPackageUploader', IArchivePermission)404patch_entry_return_type(IArchive, 'newPackageUploader', IArchivePermission)
401patch_entry_return_type(IArchive, 'newPackagesetUploader', IArchivePermission)405patch_entry_return_type(IArchive, 'newPackagesetUploader', IArchivePermission)
402patch_entry_return_type(IArchive, 'newComponentUploader', IArchivePermission)406patch_entry_return_type(IArchive, 'newComponentUploader', IArchivePermission)
407patch_entry_return_type(IArchive, 'newPocketUploader', IArchivePermission)
403patch_entry_return_type(IArchive, 'newQueueAdmin', IArchivePermission)408patch_entry_return_type(IArchive, 'newQueueAdmin', IArchivePermission)
404patch_plain_parameter_type(IArchive, 'syncSources', 'from_archive', IArchive)409patch_plain_parameter_type(IArchive, 'syncSources', 'from_archive', IArchive)
405patch_plain_parameter_type(IArchive, 'syncSource', 'from_archive', IArchive)410patch_plain_parameter_type(IArchive, 'syncSource', 'from_archive', IArchive)
@@ -433,6 +438,12 @@
433 IArchive, '_checkUpload', 'distroseries', IDistroSeries)438 IArchive, '_checkUpload', 'distroseries', IDistroSeries)
434patch_choice_parameter_type(439patch_choice_parameter_type(
435 IArchive, '_checkUpload', 'pocket', PackagePublishingPocket)440 IArchive, '_checkUpload', 'pocket', PackagePublishingPocket)
441patch_choice_parameter_type(
442 IArchive, 'getUploadersForPocket', 'pocket', PackagePublishingPocket)
443patch_choice_parameter_type(
444 IArchive, 'newPocketUploader', 'pocket', PackagePublishingPocket)
445patch_choice_parameter_type(
446 IArchive, 'deletePocketUploader', 'pocket', PackagePublishingPocket)
436patch_plain_parameter_type(447patch_plain_parameter_type(
437 IArchive, 'newPackagesetUploader', 'packageset', IPackageset)448 IArchive, 'newPackagesetUploader', 'packageset', IPackageset)
438patch_plain_parameter_type(449patch_plain_parameter_type(
439450
=== modified file 'lib/lp/code/tests/test_branch.py'
--- lib/lp/code/tests/test_branch.py 2012-05-31 03:54:13 +0000
+++ lib/lp/code/tests/test_branch.py 2012-06-12 00:00:37 +0000
@@ -274,25 +274,7 @@
274 None,274 None,
275 archive.verifyUpload(275 archive.verifyUpload(
276 person, spn, component, distroseries,276 person, spn, component, distroseries,
277 strict_component))277 strict_component=strict_component))
278
279 def assertCannotUpload(
280 self, reason, person, spn, archive, component, distroseries=None):
281 """Assert that 'person' cannot upload to the archive.
282
283 :param reason: The expected reason for not being able to upload. A
284 string.
285 :param person: The person trying to upload.
286 :param spn: The `ISourcePackageName` being uploaded to. None if the
287 package does not yet exist.
288 :param archive: The `IArchive` being uploaded to.
289 :param component: The IComponent to which the package belongs.
290 """
291 if distroseries is None:
292 distroseries = archive.distribution.currentseries
293 exception = archive.verifyUpload(
294 person, spn, component, distroseries)
295 self.assertEqual(reason, str(exception))
296278
297 def test_package_upload_permissions_grant_branch_edit(self):279 def test_package_upload_permissions_grant_branch_edit(self):
298 # If you can upload to the package, then you are also allowed to write280 # If you can upload to the package, then you are also allowed to write
299281
=== modified file 'lib/lp/soyuz/doc/archive.txt'
--- lib/lp/soyuz/doc/archive.txt 2012-05-23 05:42:24 +0000
+++ lib/lp/soyuz/doc/archive.txt 2012-06-12 00:00:37 +0000
@@ -64,23 +64,16 @@
64 >>> cprov_archive.failed_count64 >>> cprov_archive.failed_count
65 165 1
6666
67relative_build_score and external_dependencies are properties that can be set67 >>> cprov_private_ppa = factory.makeArchive(
68only by LP admins and read by anyone.68 ... owner=cprov, name='myprivateppa',
6969 ... distribution=cprov_archive.distribution)
70relative_build_score is a signed integer that represents a delta to all the70 >>> login("foo.bar@canonical.com")
71build scores for builds done in the archive. The default value is zero:71 >>> cprov_private_ppa.buildd_secret = 'really secret'
7272 >>> cprov_private_ppa.private = True
73 >>> cprov_archive.relative_build_score73 >>> login(ANONYMOUS)
74 074
7575external_dependencies is a property that can be set only by LP admins and
76Amending it as an unprivileged user results in failure:76read by anyone. It is a text field that should contain a comma-separated
77
78 >>> cprov_archive.relative_build_score = 100
79 Traceback (most recent call last):
80 ...
81 Unauthorized: (..., 'relative_build_score', 'launchpad.Moderate')
82
83external_dependencies is a text field that should contain a comma-separated
84list of sources.list entries in the format:77list of sources.list entries in the format:
85deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] [components]78deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] [components]
86where the series variable is replaced with the series name of the context79where the series variable is replaced with the series name of the context
@@ -97,69 +90,11 @@
97 ...90 ...
98 Unauthorized: (..., 'external_dependencies', 'launchpad.Commercial')91 Unauthorized: (..., 'external_dependencies', 'launchpad.Commercial')
9992
100As a Launchpad admin, setting these properties will work.93As a Launchpad admin, setting this property will work.
10194
102 >>> login("admin@canonical.com")95 >>> login("admin@canonical.com")
103 >>> cprov_archive.relative_build_score = 100
104 >>> cprov_archive.external_dependencies = "deb http://foo hardy bar"96 >>> cprov_archive.external_dependencies = "deb http://foo hardy bar"
10597
106The buildd_secret is used by the slave scanner when generating a
107sources.list entry for the builder to access a private archive. It is
108essentially the password to the archive for the builder.
109
110It can only be set by users with launchpad.Commercial permission in the
111archive, i.e. an admin or a commercial admin. We create a new PPA for
112cprov here as changing privacy of a PPA is not allowed when sources have
113already been published.
114
115 >>> cprov_private_ppa = factory.makeArchive(
116 ... owner=cprov, name='myprivateppa',
117 ... distribution=cprov_archive.distribution)
118 >>> login(ANONYMOUS)
119 >>> cprov_private_ppa.buildd_secret = 'boing'
120 Traceback (most recent call last):
121 ...
122 Unauthorized: (..., 'buildd_secret', 'launchpad.Commercial')
123
124Commercial Member, a commercial admin but not an admin, can set
125'buildd_secret'.
126
127 >>> login("commercial-member@canonical.com")
128 >>> cprov_private_ppa.buildd_secret = 'not so secret at all'
129
130Foo Bar, an admin, can set 'buildd_secret'.
131
132 >>> login("foo.bar@canonical.com")
133 >>> cprov_archive.buildd_secret = 'not so secret'
134
135In a public PPA, 'buildd_secret' still visible to anyone.
136
137 >>> login(ANONYMOUS)
138 >>> print cprov_archive.private
139 False
140
141 >>> print cprov_archive.buildd_secret
142 not so secret
143
144Once made private, 'buildd_secret' content can only be read by users with
145'launchpad.View' in the archive.
146
147 >>> login("foo.bar@canonical.com")
148 >>> cprov_private_ppa.buildd_secret = 'really secret'
149 >>> cprov_private_ppa.private = True
150
151 >>> login(ANONYMOUS)
152 >>> print cprov_private_ppa.buildd_secret
153 Traceback (most recent call last):
154 ...
155 Unauthorized: (..., 'buildd_secret', 'launchpad.View')
156
157Celso can read 'buildd_secret' contents for his PPA.
158
159 >>> login('celso.providelo@canonical.com')
160 >>> print cprov_private_ppa.buildd_secret
161 really secret
162
163Useful properties:98Useful properties:
16499
165 >>> print cprov_archive.archive_url100 >>> print cprov_archive.archive_url
@@ -543,160 +478,6 @@
543 >>> status_lookup.count()478 >>> status_lookup.count()
544 2479 2
545480
546getBuildCounters
547----------------
548
549IArchive.getBuildCounters() allows callsites to quickly present
550the number of builds in a pre-defined status for a given IArchive.
551
552 >>> def print_build_counters(build_counters):
553 ... sorted_keys = sorted(build_counters)
554 ... for key in sorted_keys:
555 ... print key, build_counters[key]
556
557Build counters for Celso's PPA.
558
559 >>> print_build_counters(cprov_archive.getBuildCounters())
560 failed 1
561 pending 0
562 succeeded 3
563 superseded 0
564 total 4
565
566Build counters for ubuntu primary archive.
567
568 >>> print_build_counters(ubuntu.main_archive.getBuildCounters())
569 failed 5
570 pending 2
571 succeeded 8
572 superseded 3
573 total 18
574
575An option argument can be used to exclude any builds that have the status
576`NEEDSBUILD`:
577
578 >>> print_build_counters(
579 ... ubuntu.main_archive.getBuildCounters(include_needsbuild=False))
580 failed 5
581 pending 1
582 succeeded 8
583 superseded 3
584 total 17
585
586
587getBuildSummariesForSourceIds()
588-------------------------------
589
590IArchive.getBuildSummariesForSourceIds() allows callsites to get an update
591on the build statuses for a set of source publishing record ids. This
592is useful for dynamically updating a page which displays a small batch of
593source packages, such as the PPA/Archive pages.
594
595Create a small function for displaying the results:
596
597 >>> def print_build_summary(summary):
598 ... print "%s\n%s\nRelevant builds:\n%s" % (
599 ... summary['status'].title,
600 ... summary['status'].description,
601 ... "\n".join(
602 ... " - %s" % build.title for build in summary['builds'])
603 ... )
604
605 >>> def print_build_summaries(summaries):
606 ... for source_id, summary in sorted(summaries.items()):
607 ... print "Source ID: %s" % source_id
608 ... print_build_summary(summary)
609
610Now print the build summaries for firefox and foo_bar:
611
612 >>> firefox_source = ubuntu.getSourcePackage('mozilla-firefox')
613 >>> firefox_source_pub = firefox_source.publishing_history[0]
614 >>> foobar = ubuntu.getSourcePackage('foobar')
615 >>> foo_pub = foobar.publishing_history[0]
616
617 >>> build_summaries = \
618 ... ubuntu.main_archive.getBuildSummariesForSourceIds(
619 ... [firefox_source_pub.id, foo_pub.id])
620 >>> print_build_summaries(build_summaries)
621 Source ID: 18
622 FULLYBUILT
623 All builds were built successfully.
624 Relevant builds:
625 - hppa build of mozilla-firefox 0.9 in ubuntu warty RELEASE
626 - i386 build of mozilla-firefox 0.9 in ubuntu warty RELEASE
627 Source ID: 22
628 FAILEDTOBUILD
629 There were build failures.
630 Relevant builds:
631 - i386 build of foobar 1.0 in ubuntu warty RELEASE
632
633
634No public access to IArchiveView methods
635----------------------------------------
636
637Both the getBuildCounters() and getBuildSummariesForSourceIds() methods are
638not publically available, but available only to users who have permission to
639view the archive:
640
641 # First we create some source/binary packages in cprov's private
642 # PPA so that we'll have some results to view.
643 >>> login('admin@canonical.com')
644 >>> from lp.soyuz.tests.test_publishing import (
645 ... SoyuzTestPublisher)
646 >>> test_publisher = SoyuzTestPublisher()
647 >>> ignore = test_publisher.setUpDefaultDistroSeries(warty)
648 >>> test_publisher.addFakeChroots(warty)
649 >>> ignore = test_publisher.getPubBinaries(archive=cprov_private_ppa)
650
651 # Grab some source IDs from the archive that we can use for calls to
652 # getBuildSummariesForSourceIds():
653 >>> source_ids = [cprov_private_ppa.getPublishedSources()[0].id]
654
655Then verify that an admin can see the counters and build summaries:
656
657 >>> print_build_counters(cprov_private_ppa.getBuildCounters())
658 failed 0
659 ...
660 succeeded 1
661 ...
662 total 1
663 >>> print_build_summaries(cprov_private_ppa.getBuildSummariesForSourceIds(
664 ... source_ids))
665 Source ID:...
666 FULLYBUILT_PENDING
667 All builds were built successfully but have not yet been published.
668 Relevant builds:
669 - i386 build of foo 666 in ubuntu warty RELEASE
670
671Next veryify that cprov can still access the build counters:
672
673 >>> login('celso.providelo@canonical.com')
674 >>> print_build_counters(cprov_private_ppa.getBuildCounters())
675 failed 0
676 ...
677 succeeded 1
678 ...
679 total 1
680 >>> print_build_summaries(cprov_private_ppa.getBuildSummariesForSourceIds(
681 ... source_ids))
682 Source ID:...
683 ...
684 - i386 build of foo 666 in ubuntu warty RELEASE
685
686But the public cannot:
687
688 >>> login('no-priv@canonical.com')
689 >>> print_build_counters(cprov_private_ppa.getBuildCounters())
690 Traceback (most recent call last):
691 ...
692 Unauthorized: (..., 'getBuildCounters', 'launchpad.View')
693 >>> print_build_summaries(cprov_private_ppa.getBuildSummariesForSourceIds(
694 ... source_ids))
695 Traceback (most recent call last):
696 ...
697 Unauthorized: (..., 'getBuildSummariesForSourceIds', 'launchpad.View')
698
699
700Package Counters481Package Counters
701----------------482----------------
702483
@@ -806,26 +587,6 @@
806 2587 2
807588
808589
809Getting an Archive's source-package releases
810--------------------------------------------
811
812The method getSourcePackageReleases() is provided to return the unique
813source-package releases for the archive. By default, all releases will
814be returned, but you can also ask for releases with builds in a certain
815state.
816
817 >>> from lp.buildmaster.enums import BuildStatus
818 >>> releases = cprov_archive.getSourcePackageReleases(
819 ... build_status=BuildStatus.FULLYBUILT)
820 >>> for release in releases:
821 ... print release.title
822 mozilla-firefox - 0.9
823 pmount - 0.1-1
824 iceweasel - 1.0
825
826For further details see the `TestGetSourcePackageReleases` unit-test.
827
828
829Sources available for deletions590Sources available for deletions
830-------------------------------591-------------------------------
831592
@@ -932,6 +693,7 @@
932693
933Or return build records in a specific status:694Or return build records in a specific status:
934695
696 >>> from lp.buildmaster.enums import BuildStatus
935 >>> cprov_archive.getBuildRecords(697 >>> cprov_archive.getBuildRecords(
936 ... build_state=BuildStatus.FULLYBUILT).count()698 ... build_state=BuildStatus.FULLYBUILT).count()
937 3699 3
@@ -1963,24 +1725,6 @@
1963 ultimate-copy copy-owner1 False True1725 ultimate-copy copy-owner1 False True
19641726
19651727
1966Getting publishing records across a set of Archives
1967---------------------------------------------------
1968
1969The IArchiveSet utility provides a getPublicationsInArchives() method
1970that can be used to find the current publishing records for a source
1971package in the provided list of archives for a specific distribution.
1972
1973 >>> pubs = archive_set.getPublicationsInArchives(
1974 ... firefox_source_pub.sourcepackagerelease.sourcepackagename,
1975 ... [ubuntu.main_archive],
1976 ... firefox_source_pub.distroseries.distribution)
1977 >>> for pub in pubs:
1978 ... print "%s - %s in %s" % (
1979 ... pub.source_package_name,
1980 ... pub.source_package_version,
1981 ... pub.archive.displayname)
1982 mozilla-firefox - 0.9 in Primary Archive for Ubuntu Linux
1983
1984Archive Permission Checking1728Archive Permission Checking
1985---------------------------1729---------------------------
19861730
@@ -2257,6 +2001,9 @@
2257First we use the SoyuzTestPublisher to make some test publications in2001First we use the SoyuzTestPublisher to make some test publications in
2258hoary:2002hoary:
22592003
2004 >>> from lp.soyuz.tests.test_publishing import (
2005 ... SoyuzTestPublisher)
2006 >>> test_publisher = SoyuzTestPublisher()
2260 >>> test_publisher.addFakeChroots(hoary)2007 >>> test_publisher.addFakeChroots(hoary)
2261 >>> unused = test_publisher.setUpDefaultDistroSeries(hoary)2008 >>> unused = test_publisher.setUpDefaultDistroSeries(hoary)
2262 >>> discard = test_publisher.getPubSource(2009 >>> discard = test_publisher.getPubSource(
@@ -2482,281 +2229,3 @@
2482 >>> copy = mark.archive.getPublishedSources(name="overridden").one()2229 >>> copy = mark.archive.getPublishedSources(name="overridden").one()
2483 >>> print copy.section.name2230 >>> print copy.section.name
2484 python2231 python
2485
2486
2487Publish flag
2488------------
2489
2490Every archive has a "publish" flag that governs whether it should be
2491published or not. Upon creation that flag is false for copy archives but
2492true for all other archive types.
2493
2494 >>> uber = getUtility(IDistributionSet).new(
2495 ... 'uberdistro', 'The uberdistro', 'The mother of all distros',
2496 ... 'All you would want from a distro', 'zero', 'uberdistro.org',
2497 ... mark, cprov, cprov)
2498
2499The primary archive for the Ãœberdistro was created by the
2500IDistributionSet.new() method. Let's check its publish flag.
2501
2502 >>> uber_primary = getUtility(IArchiveSet).getByDistroPurpose(
2503 ... uber, ArchivePurpose.PRIMARY)
2504 >>> uber_primary.publish
2505 True
2506
2507 >>> uber_partner = getUtility(IArchiveSet).new(
2508 ... owner=cprov, purpose=ArchivePurpose.PARTNER,
2509 ... distribution=uber, name='uber-partner')
2510 >>> uber_partner.publish
2511 True
2512
2513The 'sandbox archive' is a PPA that was newly created above.
2514
2515 >>> sandbox_archive.is_ppa
2516 True
2517 >>> sandbox_archive.publish
2518 True
2519
2520 >>> uber_copy = getUtility(IArchiveSet).new(
2521 ... owner=cprov, purpose=ArchivePurpose.COPY,
2522 ... distribution=uber, name='uber-copy')
2523 >>> uber_copy.publish
2524 False
2525
2526
2527The name uniqueness constraints for archives
2528--------------------------------------------
2529
2530The names of archives other than PPAs must be unique for a given
2531distribution. Trying to create an archive with the same name and
2532distribution but with a different owner will fail.
2533
2534 >>> copycat_archive = getUtility(IArchiveSet).new(
2535 ... owner=mark, purpose=ArchivePurpose.COPY,
2536 ... distribution=uber, name='uber-copy')
2537 Traceback (most recent call last):
2538 ...
2539 AssertionError: archive 'uber-copy' exists ... in 'uberdistro'.
2540
2541The same constraint is enforced for other archive types e.g. for partner
2542archives.
2543
2544 >>> copycat_archive = getUtility(IArchiveSet).new(
2545 ... owner=mark, purpose=ArchivePurpose.PARTNER,
2546 ... distribution=uber, name='uber-partner')
2547 Traceback (most recent call last):
2548 ...
2549 AssertionError: archive 'uber-partner' exists ... in 'uberdistro'.
2550
2551The names of PPAs must be unique per owner and distribution.
2552
2553 >>> print mark.archive.displayname
2554 PPA for Mark Shuttleworth
2555
2556 >>> print mark.archive.name
2557 ppa
2558
2559 >>> dup_ppa = getUtility(IArchiveSet).new(
2560 ... owner=mark, purpose=ArchivePurpose.PPA,
2561 ... distribution=ubuntu, name='ppa')
2562 Traceback (most recent call last):
2563 ...
2564 AssertionError: Person 'mark' already has a PPA named 'ppa'.
2565
2566While multiple PPAs per user isn't yet fully suported we may create
2567other PPAs, but they won't affect the existing traversal from IPerson
2568to a single IArchive.
2569
2570 >>> another_ppa = getUtility(IArchiveSet).new(
2571 ... owner=mark, purpose=ArchivePurpose.PPA,
2572 ... distribution=ubuntu, name='nightly')
2573
2574 >>> print another_ppa.owner.displayname
2575 Mark Shuttleworth
2576
2577 >>> print another_ppa.name
2578 nightly
2579
2580`IPerson.archive` is still pointing to the PPA named 'ppa'.
2581
2582 >>> print mark.archive.displayname
2583 PPA for Mark Shuttleworth
2584
2585 >>> print mark.archive.name
2586 ppa
2587
2588The ppas named differently than the default ('ppa') have a slightly
2589different displayname format, including their names.
2590
2591 >>> print another_ppa.displayname
2592 PPA named nightly for Mark Shuttleworth
2593
2594Additionally, archives, despite of their purpose, cannot have the same
2595name as their distribution.
2596
2597 >>> boingolinux = factory.makeDistribution(name='boingolinux')
2598
2599 >>> getUtility(IArchiveSet).new(
2600 ... owner=mark, purpose=ArchivePurpose.PRIMARY,
2601 ... distribution=boingolinux, name=boingolinux.name)
2602 Traceback (most recent call last):
2603 ...
2604 AssertionError: Archives cannot have the same name as their
2605 distribution.
2606
2607
2608Looking up named PPAs
2609---------------------
2610
2611Additionally to the locked 'archive' property, `IPerson` also offers
2612`ppas` property and `getPPAByName` method.
2613
2614`IPerson.ppas` returns a list with all PPA owned by the context person
2615or team ordered by name.
2616
2617 >>> for ppa in mark.ppas:
2618 ... print ppa.name
2619 nightly
2620 ppa
2621
2622`IPerson.getPPAByName` allows call sites to look up PPAs owned by the
2623context person with a given name (exact match).
2624
2625 >>> default_ppa = mark.getPPAByName('ppa')
2626 >>> default_ppa == mark.archive
2627 True
2628
2629 >>> nightly_ppa = mark.getPPAByName('nightly')
2630 >>> another_ppa == nightly_ppa
2631 True
2632
2633When a suitable PPA couldn't be found, NoSuchPPA is raised.
2634
2635 >>> print mark.getPPAByName('not-found')
2636 Traceback (most recent call last):
2637 ...
2638 NoSuchPPA: No such ppa: 'not-found'.
2639
2640
2641Editable displayname
2642--------------------
2643
2644If 'displayname' is omitted on archive created, a default form is
2645automatically used.
2646 >>> new_ppa = getUtility(IArchiveSet).new(
2647 ... owner=cprov, purpose=ArchivePurpose.PPA,
2648 ... distribution=ubuntu, name='test-ppa')
2649 >>> print new_ppa.displayname
2650 PPA named test-ppa for Celso Providelo
2651
2652When provided 'displayname' is used as given.
2653
2654 >>> new_copy = getUtility(IArchiveSet).new(
2655 ... owner=cprov, purpose=ArchivePurpose.COPY,
2656 ... displayname='Rock and roll with rebuilds!',
2657 ... distribution=ubuntu, name='test-rebuild')
2658 >>> print new_copy.displayname
2659 Rock and roll with rebuilds!
2660
2661After archive creation, the 'displayname' can be edited by the archive
2662anyone with 'edit' permissions on the archive.
2663
2664 >>> login("no-priv@canonical.com")
2665 >>> new_ppa.displayname = 'No-way!'
2666 Traceback (most recent call last):
2667 ...
2668 Unauthorized: (<Archive at ...>, 'displayname', 'launchpad.Edit')
2669
2670 >>> login('celso.providelo@canonical.com')
2671 >>> new_ppa.displayname = 'My testing packages for jaunty'
2672
2673
2674Signing-key propagation
2675-----------------------
2676
2677Signing keys are, by default, shared between PPAs owned by the same
2678user/team.
2679
2680Celso's default PPA currently has no signing-key.
2681
2682 >>> print cprov.archive.signing_key
2683 None
2684
2685When a named-ppa is created there is no key to be shared, this case
2686is worked out when generating new signing key. See archive-signing.txt
2687for more information.
2688
2689 >>> no_key_ppa = getUtility(IArchiveSet).new(
2690 ... owner=cprov, purpose=ArchivePurpose.PPA,
2691 ... distribution=ubuntu, name='no-key')
2692
2693 >>> print no_key_ppa.signing_key
2694 None
2695
2696We will select the only available IGPGKey from the sampledata.
2697
2698 >>> foo_bar = getUtility(IPersonSet).getByName('name16')
2699 >>> [a_key] = foo_bar.gpg_keys
2700 >>> print a_key.displayname
2701 1024D/12345678
2702
2703And use it as the Celso's default PPA signing key.
2704
2705 >>> login('foo.bar@canonical.com')
2706 >>> cprov.archive.signing_key = a_key
2707 >>> login('celso.providelo@canonical.com')
2708
2709Now there is a signing-key to be propagated and a new named-ppa is
2710already created accordingly.
2711
2712 >>> ppa_with_key = getUtility(IArchiveSet).new(
2713 ... owner=cprov, purpose=ArchivePurpose.PPA,
2714 ... distribution=ubuntu, name='has-key')
2715
2716 >>> ppa_with_key.signing_key == cprov.archive.signing_key
2717 True
2718
2719
2720Download counts
2721---------------
2722
2723Counts of downloads per binary package release, day and country are kept
2724up to date by a log-processing script. Archives have a method to get the
2725total number of downloads for a particular binary package release.
2726
2727 >>> login('mark@example.com')
2728 >>> binaries = test_publisher.getPubBinaries(
2729 ... architecturespecific=True)
2730 >>> archive = binaries[0].archive
2731 >>> binary0, binary1 = (b.binarypackagerelease for b in binaries)
2732
2733The new packages have no downloads yet.
2734
2735 >>> print archive.getPackageDownloadTotal(binary0)
2736 0
2737 >>> print archive.getPackageDownloadTotal(binary1)
2738 0
2739
2740We will fake some package downloads.
2741
2742 >>> from datetime import date
2743 >>> from lp.services.worlddata.interfaces.country import ICountrySet
2744 >>> australia = getUtility(ICountrySet)['AU']
2745 >>> uk = getUtility(ICountrySet)['GB']
2746
2747 >>> archive.updatePackageDownloadCount(
2748 ... binary0, date(2010, 2, 21), None, 10)
2749 >>> archive.updatePackageDownloadCount(
2750 ... binary0, date(2010, 2, 22), uk, 5)
2751 >>> archive.updatePackageDownloadCount(
2752 ... binary0, date(2010, 2, 22), australia, 4)
2753
2754 >>> archive.updatePackageDownloadCount(
2755 ... binary1, date(2010, 2, 21), australia, 2)
2756 >>> archive.updatePackageDownloadCount(
2757 ... binary1, date(2010, 2, 21), uk, 1)
2758
2759 >>> print archive.getPackageDownloadTotal(binary0)
2760 19
2761 >>> print archive.getPackageDownloadTotal(binary1)
2762 3
27632232
=== removed file 'lib/lp/soyuz/doc/archivearch.txt'
--- lib/lp/soyuz/doc/archivearch.txt 2010-08-23 16:51:11 +0000
+++ lib/lp/soyuz/doc/archivearch.txt 1970-01-01 00:00:00 +0000
@@ -1,72 +0,0 @@
1The `ArchiveArch` table facilitates the association of archives and
2processor families. This allows a user to specify (or limit) what
3processors the source packages in a certain archives will be built
4for.
5
6 >>> from lp.soyuz.enums import ArchivePurpose
7 >>> rebuild_archive = factory.makeArchive(
8 ... purpose=ArchivePurpose.COPY, name='archivearch-test')
9
10The rebuild archive has no associated processor families yet.
11
12 >>> from lp.soyuz.interfaces.archivearch import IArchiveArchSet
13 >>> aa_set = getUtility(IArchiveArchSet)
14 >>> rset = aa_set.getByArchive(rebuild_archive)
15 >>> print rset.count()
16 0
17
18The utility allows us to associate archives with processor families
19and we'll tie the rebuild archive to the 'amd64' processor family.
20
21 # Retrieve the 'amd64' and 'x86' processor families available
22 # in the sampledata.
23 >>> from lp.soyuz.interfaces.processor import IProcessorFamilySet
24 >>> amd64 = getUtility(IProcessorFamilySet).getByName('amd64')
25 >>> x86 = getUtility(IProcessorFamilySet).getByName('x86')
26
27 >>> ignore = aa_set.new(rebuild_archive, amd64)
28
29Now we have an association between the rebuild archive to the 'amd64'
30processor family.
31
32 >>> archive_arches = aa_set.getByArchive(rebuild_archive)
33 >>> archive_arches.count()
34 1
35
36 >>> [archive_arch] = list(archive_arches)
37 >>> print archive_arch.archive.name
38 archivearch-test
39
40 >>> print archive_arch.processorfamily.name
41 amd64
42
43Let's add another association for 'x86' processor family.
44
45 >>> ignore = aa_set.new(rebuild_archive, x86)
46
47 >>> archive_arches = aa_set.getByArchive(rebuild_archive)
48 >>> print archive_arches.count()
49 2
50
51The result follows the creation order, so the just-created
52`ArchiveArch` comes last.
53
54 >>> [old, x86_archive_arch] = list(archive_arches)
55 >>> print x86_archive_arch.archive.name
56 archivearch-test
57
58 >>> print x86_archive_arch.processorfamily.name
59 x86
60
61Last but not least, we query for a specific association.
62
63 >>> archive_arches = aa_set.getByArchive(rebuild_archive, amd64)
64 >>> archive_arches.count()
65 1
66
67 >>> [amd64_archive_arch] = list(archive_arches)
68 >>> print amd64_archive_arch.archive.name
69 archivearch-test
70
71 >>> print amd64_archive_arch.processorfamily.name
72 amd64
730
=== modified file 'lib/lp/soyuz/doc/archivepermission.txt'
--- lib/lp/soyuz/doc/archivepermission.txt 2012-04-24 13:35:22 +0000
+++ lib/lp/soyuz/doc/archivepermission.txt 2012-06-12 00:00:37 +0000
@@ -112,8 +112,8 @@
112 ... ubuntu)112 ... ubuntu)
113 Traceback (most recent call last):113 Traceback (most recent call last):
114 ...114 ...
115 AssertionError: 'item' ... is not an IComponent, IPackageset or an115 AssertionError: 'item' ... is not an IComponent, IPackageset,
116 ISourcePackageName116 ISourcePackageName or PackagePublishingPocket
117117
118IArchivePermissionSet also has some helpers to make it very easy to118IArchivePermissionSet also has some helpers to make it very easy to
119check permissions.119check permissions.
120120
=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py 2012-06-09 16:26:32 +0000
+++ lib/lp/soyuz/interfaces/archive.py 2012-06-12 00:00:37 +0000
@@ -626,7 +626,7 @@
626 """626 """
627627
628 def verifyUpload(person, sourcepackagename, component,628 def verifyUpload(person, sourcepackagename, component,
629 distroseries, strict_component=True):629 distroseries, strict_component=True, pocket=None):
630 """Can 'person' upload 'sourcepackagename' to this archive ?630 """Can 'person' upload 'sourcepackagename' to this archive ?
631631
632 :param person: The `IPerson` trying to upload to the package. Referred632 :param person: The `IPerson` trying to upload to the package. Referred
@@ -639,6 +639,8 @@
639 :param strict_component: True if access to the specific component for639 :param strict_component: True if access to the specific component for
640 the package is needed to upload to it. If False, then access to640 the package is needed to upload to it. If False, then access to
641 any component will do.641 any component will do.
642 :param pocket: The `PackagePublishingPocket` being uploaded to. If
643 None, then pocket permissions are not checked.
642 :return: CannotUploadToArchive if 'person' cannot upload to the644 :return: CannotUploadToArchive if 'person' cannot upload to the
643 archive,645 archive,
644 None otherwise.646 None otherwise.
@@ -762,6 +764,21 @@
762 """764 """
763765
764 @operation_parameters(766 @operation_parameters(
767 person=Reference(schema=IPerson))
768 # Really IArchivePermission, set in _schema_circular_imports to avoid
769 # circular import.
770 @operation_returns_collection_of(Interface)
771 @export_read_operation()
772 @operation_for_version("devel")
773 def getPocketsForUploader(person):
774 """Return the pockets that 'person' can upload to this archive.
775
776 :param person: An `IPerson` wishing to upload to an archive.
777 :return: A `set` of `PackagePublishingPocket` items that 'person'
778 can upload to.
779 """
780
781 @operation_parameters(
765 sourcepackagename=TextLine(782 sourcepackagename=TextLine(
766 title=_("Source package name"), required=True),783 title=_("Source package name"), required=True),
767 person=Reference(schema=IPerson))784 person=Reference(schema=IPerson))
@@ -1166,6 +1183,24 @@
1166 :return: A list of `IArchivePermission` records.1183 :return: A list of `IArchivePermission` records.
1167 """1184 """
11681185
1186 @operation_parameters(
1187 pocket=Choice(
1188 title=_("Pocket"),
1189 # Really PackagePublishingPocket, circular import fixed below.
1190 vocabulary=DBEnumeratedType,
1191 required=True),
1192 )
1193 # Really IArchivePermission, set below to avoid circular import.
1194 @operation_returns_collection_of(Interface)
1195 @export_read_operation()
1196 @operation_for_version("devel")
1197 def getUploadersForPocket(pocket):
1198 """Return `IArchivePermission` records for the pocket's uploaders.
1199
1200 :param pocket: A `PackagePublishingPocket`.
1201 :return: A list of `IArchivePermission` records.
1202 """
1203
1169 def hasAnyPermission(person):1204 def hasAnyPermission(person):
1170 """Whether or not this person has any permission at all on this1205 """Whether or not this person has any permission at all on this
1171 archive.1206 archive.
@@ -1500,6 +1535,30 @@
15001535
1501 @operation_parameters(1536 @operation_parameters(
1502 person=Reference(schema=IPerson),1537 person=Reference(schema=IPerson),
1538 pocket=Choice(
1539 title=_("Pocket"),
1540 # Really PackagePublishingPocket, circular import fixed below.
1541 vocabulary=DBEnumeratedType,
1542 required=True),
1543 )
1544 # Really IArchivePermission, set below to avoid circular import.
1545 @export_factory_operation(Interface, [])
1546 @operation_for_version("devel")
1547 def newPocketUploader(person, pocket):
1548 """Add permission for a person to upload to a pocket.
1549
1550 :param person: An `IPerson` whom should be given permission.
1551 :param component: A `PackagePublishingPocket`.
1552 :return: An `IArchivePermission` which is the newly-created
1553 permission.
1554 :raises InvalidPocketForPartnerArchive: if this archive is a partner
1555 archive and the pocket is not RELEASE or PROPOSED.
1556 :raises InvalidPocketForPPA: if this archive is a PPA and the pocket
1557 is not RELEASE.
1558 """
1559
1560 @operation_parameters(
1561 person=Reference(schema=IPerson),
1503 component_name=TextLine(1562 component_name=TextLine(
1504 title=_("Component Name"), required=True))1563 title=_("Component Name"), required=True))
1505 # Really IArchivePermission, set below to avoid circular import.1564 # Really IArchivePermission, set below to avoid circular import.
@@ -1566,6 +1625,23 @@
15661625
1567 @operation_parameters(1626 @operation_parameters(
1568 person=Reference(schema=IPerson),1627 person=Reference(schema=IPerson),
1628 pocket=Choice(
1629 title=_("Pocket"),
1630 # Really PackagePublishingPocket, circular import fixed below.
1631 vocabulary=DBEnumeratedType,
1632 required=True),
1633 )
1634 @export_write_operation()
1635 @operation_for_version("devel")
1636 def deletePocketUploader(person, pocket):
1637 """Revoke permission for the person to upload to the pocket.
1638
1639 :param person: An `IPerson` whose permission should be revoked.
1640 :param pocket: A `PackagePublishingPocket`.
1641 """
1642
1643 @operation_parameters(
1644 person=Reference(schema=IPerson),
1569 component_name=TextLine(1645 component_name=TextLine(
1570 title=_("Component Name"), required=True))1646 title=_("Component Name"), required=True))
1571 @export_write_operation()1647 @export_write_operation()
15721648
=== modified file 'lib/lp/soyuz/interfaces/archivepermission.py'
--- lib/lp/soyuz/interfaces/archivepermission.py 2011-12-24 16:54:44 +0000
+++ lib/lp/soyuz/interfaces/archivepermission.py 2012-06-12 00:00:37 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2011 Canonical Ltd. This software is licensed under the1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# pylint: disable-msg=E02134# pylint: disable-msg=E0213
@@ -31,6 +31,7 @@
31 )31 )
3232
33from lp import _33from lp import _
34from lp.registry.interfaces.pocket import PackagePublishingPocket
34from lp.registry.interfaces.sourcepackagename import ISourcePackageName35from lp.registry.interfaces.sourcepackagename import ISourcePackageName
35from lp.services.fields import PublicPersonChoice36from lp.services.fields import PublicPersonChoice
36from lp.soyuz.enums import ArchivePermissionType37from lp.soyuz.enums import ArchivePermissionType
@@ -117,6 +118,13 @@
117 "package set."),118 "package set."),
118 required=True))119 required=True))
119120
121 pocket = exported(
122 Choice(
123 title=_("Pocket"),
124 description=_("The pocket that this permission is for."),
125 vocabulary=PackagePublishingPocket,
126 required=True))
127
120128
121class IArchiveUploader(IArchivePermission):129class IArchiveUploader(IArchivePermission):
122 """Marker interface for URL traversal of uploader permissions."""130 """Marker interface for URL traversal of uploader permissions."""
@@ -305,6 +313,27 @@
305 authorized to upload to the named source package set.313 authorized to upload to the named source package set.
306 """314 """
307315
316 def uploadersForPocket(archive, pocket):
317 """The `ArchivePermission` records for authorised pocket uploaders.
318
319 :param archive: The context `IArchive` for the permission check.
320 :param pocket: A `PackagePublishingPocket`.
321
322 :return: `ArchivePermission` records for all the uploaders who
323 are authorised for the supplied pocket.
324 """
325
326 def pocketsForUploader(archive, person):
327 """The `ArchivePermission` records for the person's upload pockets.
328
329 :param archive: The context `IArchive` for the permission check.
330 :param person: An `IPerson` for whom you want to find out which
331 pockets he has access to.
332
333 :return: `ArchivePermission` records for all the pockets that
334 'person' is allowed to upload to.
335 """
336
308 def queueAdminsForComponent(archive, component):337 def queueAdminsForComponent(archive, component):
309 """The `ArchivePermission` records for authorised queue admins.338 """The `ArchivePermission` records for authorised queue admins.
310339
@@ -370,6 +399,17 @@
370 already exists.399 already exists.
371 """400 """
372401
402 def newPocketUploader(archive, person, pocket):
403 """Create and return a new `ArchivePermission` for an uploader.
404
405 :param archive: The context `IArchive` for the permission check.
406 :param person: An `IPerson` for whom you want to add permission.
407 :param component: A `PackagePublishingPocket`.
408
409 :return: The new `ArchivePermission`, or the existing one if it
410 already exists.
411 """
412
373 def newQueueAdmin(archive, person, component):413 def newQueueAdmin(archive, person, component):
374 """Create and return a new `ArchivePermission` for a queue admin.414 """Create and return a new `ArchivePermission` for a queue admin.
375415
@@ -411,6 +451,14 @@
411 :param component: An `IComponent` or a string package name.451 :param component: An `IComponent` or a string package name.
412 """452 """
413453
454 def deletePocketUploader(archive, person, pocket):
455 """Revoke upload permissions for a person.
456
457 :param archive: The context `IArchive` for the permission check.
458 :param person: An `IPerson` for whom you want to revoke permission.
459 :param pocket: A `PackagePublishingPocket`.
460 """
461
414 def deleteQueueAdmin(archive, person, component):462 def deleteQueueAdmin(archive, person, component):
415 """Revoke queue admin permissions for a person.463 """Revoke queue admin permissions for a person.
416464
417465
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2012-06-10 13:15:28 +0000
+++ lib/lp/soyuz/model/archive.py 2012-06-12 00:00:37 +0000
@@ -1124,6 +1124,11 @@
1124 permission_set = getUtility(IArchivePermissionSet)1124 permission_set = getUtility(IArchivePermissionSet)
1125 return permission_set.uploadersForComponent(self, component_name)1125 return permission_set.uploadersForComponent(self, component_name)
11261126
1127 def getUploadersForPocket(self, pocket):
1128 """See `IArchive`."""
1129 permission_set = getUtility(IArchivePermissionSet)
1130 return permission_set.uploadersForPocket(self, pocket)
1131
1127 def getQueueAdminsForComponent(self, component_name):1132 def getQueueAdminsForComponent(self, component_name):
1128 """See `IArchive`."""1133 """See `IArchive`."""
1129 permission_set = getUtility(IArchivePermissionSet)1134 permission_set = getUtility(IArchivePermissionSet)
@@ -1232,7 +1237,7 @@
1232 source_ids,1237 source_ids,
1233 archive=self)1238 archive=self)
12341239
1235 def checkArchivePermission(self, user, component_or_package=None):1240 def checkArchivePermission(self, user, item=None):
1236 """See `IArchive`."""1241 """See `IArchive`."""
1237 # PPA access is immediately granted if the user is in the PPA1242 # PPA access is immediately granted if the user is in the PPA
1238 # team.1243 # team.
@@ -1247,7 +1252,7 @@
1247 # interface will no longer require them because we can1252 # interface will no longer require them because we can
1248 # then relax the database constraint on1253 # then relax the database constraint on
1249 # ArchivePermission.1254 # ArchivePermission.
1250 component_or_package = self.default_component1255 item = self.default_component
12511256
1252 # Flatly refuse uploads to copy archives, at least for now.1257 # Flatly refuse uploads to copy archives, at least for now.
1253 if self.is_copy:1258 if self.is_copy:
@@ -1255,8 +1260,7 @@
12551260
1256 # Otherwise any archive, including PPAs, uses the standard1261 # Otherwise any archive, including PPAs, uses the standard
1257 # ArchivePermission entries.1262 # ArchivePermission entries.
1258 return self._authenticate(1263 return self._authenticate(user, item, ArchivePermissionType.UPLOAD)
1259 user, component_or_package, ArchivePermissionType.UPLOAD)
12601264
1261 def canUploadSuiteSourcePackage(self, person, suitesourcepackage):1265 def canUploadSuiteSourcePackage(self, person, suitesourcepackage):
1262 """See `IArchive`."""1266 """See `IArchive`."""
@@ -1319,10 +1323,10 @@
1319 return reason1323 return reason
1320 return self.verifyUpload(1324 return self.verifyUpload(
1321 person, sourcepackagename, component, distroseries,1325 person, sourcepackagename, component, distroseries,
1322 strict_component)1326 strict_component=strict_component, pocket=pocket)
13231327
1324 def verifyUpload(self, person, sourcepackagename, component,1328 def verifyUpload(self, person, sourcepackagename, component,
1325 distroseries, strict_component=True):1329 distroseries, strict_component=True, pocket=None):
1326 """See `IArchive`."""1330 """See `IArchive`."""
1327 if not self.enabled:1331 if not self.enabled:
1328 return ArchiveDisabled(self.displayname)1332 return ArchiveDisabled(self.displayname)
@@ -1334,6 +1338,11 @@
1334 else:1338 else:
1335 return None1339 return None
13361340
1341 # Users with pocket upload permissions may upload to anything in the
1342 # given pocket.
1343 if pocket is not None and self.checkArchivePermission(person, pocket):
1344 return None
1345
1337 if sourcepackagename is not None:1346 if sourcepackagename is not None:
1338 # Check whether user may upload because they hold a permission for1347 # Check whether user may upload because they hold a permission for
1339 # - the given source package directly1348 # - the given source package directly
@@ -1364,9 +1373,9 @@
1364 return self._authenticate(1373 return self._authenticate(
1365 user, component, ArchivePermissionType.QUEUE_ADMIN)1374 user, component, ArchivePermissionType.QUEUE_ADMIN)
13661375
1367 def _authenticate(self, user, component, permission):1376 def _authenticate(self, user, item, permission):
1368 """Private helper method to check permissions."""1377 """Private helper method to check permissions."""
1369 permissions = self.getPermissions(user, component, permission)1378 permissions = self.getPermissions(user, item, permission)
1370 return bool(permissions)1379 return bool(permissions)
13711380
1372 def newPackageUploader(self, person, source_package_name):1381 def newPackageUploader(self, person, source_package_name):
@@ -1400,6 +1409,19 @@
1400 return permission_set.newComponentUploader(1409 return permission_set.newComponentUploader(
1401 self, person, component_name)1410 self, person, component_name)
14021411
1412 def newPocketUploader(self, person, pocket):
1413 if self.is_partner:
1414 if pocket not in (
1415 PackagePublishingPocket.RELEASE,
1416 PackagePublishingPocket.PROPOSED):
1417 raise InvalidPocketForPartnerArchive()
1418 elif self.is_ppa:
1419 if pocket != PackagePublishingPocket.RELEASE:
1420 raise InvalidPocketForPPA()
1421
1422 permission_set = getUtility(IArchivePermissionSet)
1423 return permission_set.newPocketUploader(self, person, pocket)
1424
1403 def newQueueAdmin(self, person, component_name):1425 def newQueueAdmin(self, person, component_name):
1404 """See `IArchive`."""1426 """See `IArchive`."""
1405 permission_set = getUtility(IArchivePermissionSet)1427 permission_set = getUtility(IArchivePermissionSet)
@@ -1417,6 +1439,11 @@
1417 return permission_set.deleteComponentUploader(1439 return permission_set.deleteComponentUploader(
1418 self, person, component_name)1440 self, person, component_name)
14191441
1442 def deletePocketUploader(self, person, pocket):
1443 """See `IArchive`."""
1444 permission_set = getUtility(IArchivePermissionSet)
1445 return permission_set.deletePocketUploader(self, person, pocket)
1446
1420 def deleteQueueAdmin(self, person, component_name):1447 def deleteQueueAdmin(self, person, component_name):
1421 """See `IArchive`."""1448 """See `IArchive`."""
1422 permission_set = getUtility(IArchivePermissionSet)1449 permission_set = getUtility(IArchivePermissionSet)
@@ -1439,6 +1466,11 @@
1439 permission_set = getUtility(IArchivePermissionSet)1466 permission_set = getUtility(IArchivePermissionSet)
1440 return permission_set.componentsForUploader(self, person)1467 return permission_set.componentsForUploader(self, person)
14411468
1469 def getPocketsForUploader(self, person):
1470 """See `IArchive`."""
1471 permission_set = getUtility(IArchivePermissionSet)
1472 return permission_set.pocketsForUploader(self, person)
1473
1442 def getPackagesetsForUploader(self, person):1474 def getPackagesetsForUploader(self, person):
1443 """See `IArchive`."""1475 """See `IArchive`."""
1444 permission_set = getUtility(IArchivePermissionSet)1476 permission_set = getUtility(IArchivePermissionSet)
14451477
=== modified file 'lib/lp/soyuz/model/archivepermission.py'
--- lib/lp/soyuz/model/archivepermission.py 2011-12-30 06:14:56 +0000
+++ lib/lp/soyuz/model/archivepermission.py 2012-06-12 00:00:37 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Database class for table ArchivePermission."""4"""Database class for table ArchivePermission."""
@@ -10,6 +10,7 @@
10 'ArchivePermissionSet',10 'ArchivePermissionSet',
11 ]11 ]
1212
13from lazr.enum import DBItem
13from sqlobject import (14from sqlobject import (
14 BoolCol,15 BoolCol,
15 ForeignKey,16 ForeignKey,
@@ -25,9 +26,11 @@
25 alsoProvides,26 alsoProvides,
26 implements,27 implements,
27 )28 )
29from zope.security.proxy import isinstance as zope_isinstance
2830
29from lp.app.errors import NotFoundError31from lp.app.errors import NotFoundError
30from lp.registry.interfaces.distribution import IDistributionSet32from lp.registry.interfaces.distribution import IDistributionSet
33from lp.registry.interfaces.pocket import PackagePublishingPocket
31from lp.registry.interfaces.sourcepackagename import (34from lp.registry.interfaces.sourcepackagename import (
32 ISourcePackageName,35 ISourcePackageName,
33 ISourcePackageNameSet,36 ISourcePackageNameSet,
@@ -101,6 +104,8 @@
101104
102 explicit = BoolCol(dbName='explicit', notNull=True, default=False)105 explicit = BoolCol(dbName='explicit', notNull=True, default=False)
103106
107 pocket = EnumCol(dbName="pocket", schema=PackagePublishingPocket)
108
104 def _init(self, *args, **kw):109 def _init(self, *args, **kw):
105 """Provide the right interface for URL traversal."""110 """Provide the right interface for URL traversal."""
106 SQLBase._init(self, *args, **kw)111 SQLBase._init(self, *args, **kw)
@@ -176,10 +181,13 @@
176 clauses.append(181 clauses.append(
177 "ArchivePermission.packageset = %s" % sqlvalues(item.id))182 "ArchivePermission.packageset = %s" % sqlvalues(item.id))
178 prejoins.append("packageset")183 prejoins.append("packageset")
184 elif (zope_isinstance(item, DBItem) and
185 item.enum.name == "PackagePublishingPocket"):
186 clauses.append("ArchivePermission.pocket = %s" % sqlvalues(item))
179 else:187 else:
180 raise AssertionError(188 raise AssertionError(
181 "'item' %r is not an IComponent, IPackageset or an "189 "'item' %r is not an IComponent, IPackageset, "
182 "ISourcePackageName" % item)190 "ISourcePackageName or PackagePublishingPocket" % item)
183191
184 query = " AND ".join(clauses)192 query = " AND ".join(clauses)
185 auth = ArchivePermission.select(193 auth = ArchivePermission.select(
@@ -235,7 +243,7 @@
235 prejoins=["component"])243 prejoins=["component"])
236244
237 def componentsForUploader(self, archive, person):245 def componentsForUploader(self, archive, person):
238 """See `IArchivePermissionSet`,"""246 """See `IArchivePermissionSet`."""
239 return self._componentsFor(247 return self._componentsFor(
240 archive, person, ArchivePermissionType.UPLOAD)248 archive, person, ArchivePermissionType.UPLOAD)
241249
@@ -277,6 +285,24 @@
277 sourcepackagename=sourcepackagename)285 sourcepackagename=sourcepackagename)
278 return results.prejoin(["sourcepackagename"])286 return results.prejoin(["sourcepackagename"])
279287
288 def pocketsForUploader(self, archive, person):
289 """See `IArchivePermissionSet`."""
290 return ArchivePermission.select("""
291 ArchivePermission.archive = %s AND
292 ArchivePermission.permission = %s AND
293 ArchivePermission.pocket IS NOT NULL AND
294 EXISTS (SELECT TeamParticipation.person
295 FROM TeamParticipation
296 WHERE TeamParticipation.person = %s AND
297 TeamParticipation.team = ArchivePermission.person)
298 """ % sqlvalues(archive, ArchivePermissionType.UPLOAD, person))
299
300 def uploadersForPocket(self, archive, pocket):
301 "See `IArchivePermissionSet`."""
302 return ArchivePermission.selectBy(
303 archive=archive, permission=ArchivePermissionType.UPLOAD,
304 pocket=pocket)
305
280 def queueAdminsForComponent(self, archive, component):306 def queueAdminsForComponent(self, archive, component):
281 "See `IArchivePermissionSet`."""307 "See `IArchivePermissionSet`."""
282 component = self._nameToComponent(component)308 component = self._nameToComponent(component)
@@ -315,6 +341,17 @@
315 archive=archive, person=person, component=component,341 archive=archive, person=person, component=component,
316 permission=ArchivePermissionType.UPLOAD)342 permission=ArchivePermissionType.UPLOAD)
317343
344 def newPocketUploader(self, archive, person, pocket):
345 """See `IArchivePermissionSet`."""
346 existing = self.checkAuthenticated(
347 person, archive, ArchivePermissionType.UPLOAD, pocket)
348 try:
349 return existing[0]
350 except IndexError:
351 return ArchivePermission(
352 archive=archive, person=person, pocket=pocket,
353 permission=ArchivePermissionType.UPLOAD)
354
318 def newQueueAdmin(self, archive, person, component):355 def newQueueAdmin(self, archive, person, component):
319 """See `IArchivePermissionSet`."""356 """See `IArchivePermissionSet`."""
320 component = self._nameToComponent(component)357 component = self._nameToComponent(component)
@@ -353,6 +390,12 @@
353 permission=ArchivePermissionType.UPLOAD)390 permission=ArchivePermissionType.UPLOAD)
354 self._remove_permission(permission)391 self._remove_permission(permission)
355392
393 def deletePocketUploader(self, archive, person, pocket):
394 permission = ArchivePermission.selectOneBy(
395 archive=archive, person=person, pocket=pocket,
396 permission=ArchivePermissionType.UPLOAD)
397 self._remove_permission(permission)
398
356 def deleteQueueAdmin(self, archive, person, component):399 def deleteQueueAdmin(self, archive, person, component):
357 """See `IArchivePermissionSet`."""400 """See `IArchivePermissionSet`."""
358 component = self._nameToComponent(component)401 component = self._nameToComponent(component)
359402
=== modified file 'lib/lp/soyuz/stories/webservice/xx-archive.txt'
--- lib/lp/soyuz/stories/webservice/xx-archive.txt 2012-05-27 18:53:35 +0000
+++ lib/lp/soyuz/stories/webservice/xx-archive.txt 2012-06-12 00:00:37 +0000
@@ -168,6 +168,7 @@
168 date_created: ...168 date_created: ...
169 permission: u'Archive Upload Rights'169 permission: u'Archive Upload Rights'
170 person_link: u'http://.../~ubuntu-team'170 person_link: u'http://.../~ubuntu-team'
171 pocket: None
171 resource_type_link: ...172 resource_type_link: ...
172 self_link: u'http://.../ubuntu/+archive/primary/+upload/ubuntu-team?type=component&item=main'173 self_link: u'http://.../ubuntu/+archive/primary/+upload/ubuntu-team?type=component&item=main'
173 source_package_name: None174 source_package_name: None
@@ -183,6 +184,7 @@
183 date_created: ...184 date_created: ...
184 permission: u'Archive Upload Rights'185 permission: u'Archive Upload Rights'
185 person_link: u'http://.../~carlos'186 person_link: u'http://.../~carlos'
187 pocket: None
186 resource_type_link: ...188 resource_type_link: ...
187 self_link:189 self_link:
188 u'http://.../ubuntu/+archive/primary/+upload/carlos?type=packagename&item=mozilla-firefox'190 u'http://.../ubuntu/+archive/primary/+upload/carlos?type=packagename&item=mozilla-firefox'
@@ -199,6 +201,7 @@
199 date_created: ...201 date_created: ...
200 permission: u'Queue Administration Rights'202 permission: u'Queue Administration Rights'
201 person_link: u'http://.../~ubuntu-team'203 person_link: u'http://.../~ubuntu-team'
204 pocket: None
202 resource_type_link: ...205 resource_type_link: ...
203 self_link:206 self_link:
204 u'http://.../ubuntu/+archive/primary/+queue-admin/ubuntu-team?type=component&item=main'207 u'http://.../ubuntu/+archive/primary/+queue-admin/ubuntu-team?type=component&item=main'
@@ -215,6 +218,7 @@
215 date_created: ...218 date_created: ...
216 permission: u'Queue Administration Rights'219 permission: u'Queue Administration Rights'
217 person_link: u'http://.../~name12'220 person_link: u'http://.../~name12'
221 pocket: None
218 resource_type_link: ...222 resource_type_link: ...
219 self_link:223 self_link:
220 u'http://.../ubuntu/+archive/primary/+queue-admin/name12?type=component&item=universe'224 u'http://.../ubuntu/+archive/primary/+queue-admin/name12?type=component&item=universe'
221225
=== modified file 'lib/lp/soyuz/stories/webservice/xx-packageset.txt'
--- lib/lp/soyuz/stories/webservice/xx-packageset.txt 2011-12-24 17:49:30 +0000
+++ lib/lp/soyuz/stories/webservice/xx-packageset.txt 2012-06-12 00:00:37 +0000
@@ -573,6 +573,7 @@
573 package_set_name: u'firefox'573 package_set_name: u'firefox'
574 permission: u'Archive Upload Rights'574 permission: u'Archive Upload Rights'
575 person_link: u'http://.../~name12'575 person_link: u'http://.../~name12'
576 pocket: None
576 resource_type_link: ...577 resource_type_link: ...
577 self_link: u'http://.../+archive/primary/+upload/name12?type=packageset&item=firefox&series=hoary'578 self_link: u'http://.../+archive/primary/+upload/name12?type=packageset&item=firefox&series=hoary'
578 source_package_name: None579 source_package_name: None
579580
=== modified file 'lib/lp/soyuz/tests/test_archive.py'
--- lib/lp/soyuz/tests/test_archive.py 2012-06-10 09:06:08 +0000
+++ lib/lp/soyuz/tests/test_archive.py 2012-06-12 00:00:37 +0000
@@ -24,6 +24,7 @@
24from lp.app.errors import NotFoundError24from lp.app.errors import NotFoundError
25from lp.app.interfaces.launchpad import ILaunchpadCelebrities25from lp.app.interfaces.launchpad import ILaunchpadCelebrities
26from lp.buildmaster.enums import BuildStatus26from lp.buildmaster.enums import BuildStatus
27from lp.registry.interfaces.distribution import IDistributionSet
27from lp.registry.interfaces.person import (28from lp.registry.interfaces.person import (
28 IPersonSet,29 IPersonSet,
29 TeamSubscriptionPolicy,30 TeamSubscriptionPolicy,
@@ -64,10 +65,12 @@
64 InvalidPocketForPPA,65 InvalidPocketForPPA,
65 NoRightsForArchive,66 NoRightsForArchive,
66 NoRightsForComponent,67 NoRightsForComponent,
68 NoSuchPPA,
67 VersionRequiresName,69 VersionRequiresName,
68 )70 )
69from lp.soyuz.interfaces.archivearch import IArchiveArchSet71from lp.soyuz.interfaces.archivearch import IArchiveArchSet
70from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet72from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
73from lp.soyuz.interfaces.binarypackagebuild import BuildSetStatus
71from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet74from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet
72from lp.soyuz.interfaces.component import IComponentSet75from lp.soyuz.interfaces.component import IComponentSet
73from lp.soyuz.interfaces.packagecopyjob import IPlainPackageCopyJobSource76from lp.soyuz.interfaces.packagecopyjob import IPlainPackageCopyJobSource
@@ -92,6 +95,7 @@
92 )95 )
93from lp.testing.layers import (96from lp.testing.layers import (
94 DatabaseFunctionalLayer,97 DatabaseFunctionalLayer,
98 LaunchpadFunctionalLayer,
95 LaunchpadZopelessLayer,99 LaunchpadZopelessLayer,
96 )100 )
97from lp.testing.sampledata import COMMERCIAL_ADMIN_EMAIL101from lp.testing.sampledata import COMMERCIAL_ADMIN_EMAIL
@@ -523,11 +527,11 @@
523 # Somebody unrelated does not527 # Somebody unrelated does not
524 self.assertFalse(archive.checkArchivePermission(somebody))528 self.assertFalse(archive.checkArchivePermission(somebody))
525529
526 def makeArchiveAndActiveDistroSeries(self, purpose=ArchivePurpose.PRIMARY):530 def makeArchiveAndActiveDistroSeries(self, purpose=ArchivePurpose.PRIMARY,
531 status=SeriesStatus.DEVELOPMENT):
527 archive = self.factory.makeArchive(purpose=purpose)532 archive = self.factory.makeArchive(purpose=purpose)
528 distroseries = self.factory.makeDistroSeries(533 distroseries = self.factory.makeDistroSeries(
529 distribution=archive.distribution,534 distribution=archive.distribution, status=status)
530 status=SeriesStatus.DEVELOPMENT)
531 return archive, distroseries535 return archive, distroseries
532536
533 def makePersonWithComponentPermission(self, archive):537 def makePersonWithComponentPermission(self, archive):
@@ -706,6 +710,21 @@
706 self.assertCanUpload(710 self.assertCanUpload(
707 archive, person, sourcepackagename, distroseries=distroseries)711 archive, person, sourcepackagename, distroseries=distroseries)
708712
713 def makePersonWithPocketPermission(self, archive, pocket):
714 person = self.factory.makePerson()
715 removeSecurityProxy(archive).newPocketUploader(person, pocket)
716 return person
717
718 def test_checkUpload_pocket_permission(self):
719 archive, distroseries = self.makeArchiveAndActiveDistroSeries(
720 purpose=ArchivePurpose.PRIMARY, status=SeriesStatus.CURRENT)
721 sourcepackagename = self.factory.makeSourcePackageName()
722 pocket = PackagePublishingPocket.SECURITY
723 person = self.makePersonWithPocketPermission(archive, pocket)
724 self.assertCanUpload(
725 archive, person, sourcepackagename, distroseries=distroseries,
726 pocket=pocket)
727
709 def make_person_with_packageset_permission(self, archive, distroseries,728 def make_person_with_packageset_permission(self, archive, distroseries,
710 packages=()):729 packages=()):
711 packageset = self.factory.makePackageset(730 packageset = self.factory.makePackageset(
@@ -801,11 +820,10 @@
801820
802 def makePackageToUpload(self, distroseries):821 def makePackageToUpload(self, distroseries):
803 sourcepackagename = self.factory.makeSourcePackageName()822 sourcepackagename = self.factory.makeSourcePackageName()
804 suitesourcepackage = self.factory.makeSuiteSourcePackage(823 return self.factory.makeSuiteSourcePackage(
805 pocket=PackagePublishingPocket.RELEASE,824 pocket=PackagePublishingPocket.RELEASE,
806 sourcepackagename=sourcepackagename,825 sourcepackagename=sourcepackagename,
807 distroseries=distroseries)826 distroseries=distroseries)
808 return suitesourcepackage
809827
810 def test_canUploadSuiteSourcePackage_invalid_pocket(self):828 def test_canUploadSuiteSourcePackage_invalid_pocket(self):
811 # Test that canUploadSuiteSourcePackage calls checkUpload for829 # Test that canUploadSuiteSourcePackage calls checkUpload for
@@ -927,6 +945,7 @@
927 self.archive.updatePackageDownloadCount(945 self.archive.updatePackageDownloadCount(
928 self.bpr_1, day, self.australia, 10)946 self.bpr_1, day, self.australia, 10)
929 self.assertCount(10, self.archive, self.bpr_1, day, self.australia)947 self.assertCount(10, self.archive, self.bpr_1, day, self.australia)
948 self.assertEqual(10, self.archive.getPackageDownloadTotal(self.bpr_1))
930949
931 def test_reuses_existing_entry(self):950 def test_reuses_existing_entry(self):
932 # A second update will simply add to the count on the existing951 # A second update will simply add to the count on the existing
@@ -937,6 +956,7 @@
937 self.archive.updatePackageDownloadCount(956 self.archive.updatePackageDownloadCount(
938 self.bpr_1, day, self.australia, 3)957 self.bpr_1, day, self.australia, 3)
939 self.assertCount(13, self.archive, self.bpr_1, day, self.australia)958 self.assertCount(13, self.archive, self.bpr_1, day, self.australia)
959 self.assertEqual(13, self.archive.getPackageDownloadTotal(self.bpr_1))
940960
941 def test_differentiates_between_countries(self):961 def test_differentiates_between_countries(self):
942 # A different country will cause a new entry to be created.962 # A different country will cause a new entry to be created.
@@ -948,6 +968,7 @@
948968
949 self.assertCount(10, self.archive, self.bpr_1, day, self.australia)969 self.assertCount(10, self.archive, self.bpr_1, day, self.australia)
950 self.assertCount(3, self.archive, self.bpr_1, day, self.new_zealand)970 self.assertCount(3, self.archive, self.bpr_1, day, self.new_zealand)
971 self.assertEqual(13, self.archive.getPackageDownloadTotal(self.bpr_1))
951972
952 def test_country_can_be_none(self):973 def test_country_can_be_none(self):
953 # The country can be None, indicating that it is unknown.974 # The country can be None, indicating that it is unknown.
@@ -959,6 +980,7 @@
959980
960 self.assertCount(10, self.archive, self.bpr_1, day, self.australia)981 self.assertCount(10, self.archive, self.bpr_1, day, self.australia)
961 self.assertCount(3, self.archive, self.bpr_1, day, None)982 self.assertCount(3, self.archive, self.bpr_1, day, None)
983 self.assertEqual(13, self.archive.getPackageDownloadTotal(self.bpr_1))
962984
963 def test_differentiates_between_days(self):985 def test_differentiates_between_days(self):
964 # A different date will also cause a new entry to be created.986 # A different date will also cause a new entry to be created.
@@ -972,6 +994,7 @@
972 self.assertCount(10, self.archive, self.bpr_1, day, self.australia)994 self.assertCount(10, self.archive, self.bpr_1, day, self.australia)
973 self.assertCount(995 self.assertCount(
974 3, self.archive, self.bpr_1, another_day, self.australia)996 3, self.archive, self.bpr_1, another_day, self.australia)
997 self.assertEqual(13, self.archive.getPackageDownloadTotal(self.bpr_1))
975998
976 def test_differentiates_between_bprs(self):999 def test_differentiates_between_bprs(self):
977 # And even a different package will create a new entry.1000 # And even a different package will create a new entry.
@@ -983,6 +1006,8 @@
9831006
984 self.assertCount(10, self.archive, self.bpr_1, day, self.australia)1007 self.assertCount(10, self.archive, self.bpr_1, day, self.australia)
985 self.assertCount(3, self.archive, self.bpr_2, day, self.australia)1008 self.assertCount(3, self.archive, self.bpr_2, day, self.australia)
1009 self.assertEqual(10, self.archive.getPackageDownloadTotal(self.bpr_1))
1010 self.assertEqual(3, self.archive.getPackageDownloadTotal(self.bpr_2))
9861011
9871012
988class TestEnabledRestrictedBuilds(TestCaseWithFactory):1013class TestEnabledRestrictedBuilds(TestCaseWithFactory):
@@ -1015,11 +1040,9 @@
1015 distro.main_archive.require_virtualized = False1040 distro.main_archive.require_virtualized = False
1016 # Restricting to all restricted architectures is fine1041 # Restricting to all restricted architectures is fine
1017 distro.main_archive.enabled_restricted_families = [self.arm]1042 distro.main_archive.enabled_restricted_families = [self.arm]
10181043 self.assertRaises(
1019 def restrict():1044 CannotRestrictArchitectures, setattr, distro.main_archive,
1020 distro.main_archive.enabled_restricted_families = []1045 "enabled_restricted_families", [])
1021
1022 self.assertRaises(CannotRestrictArchitectures, restrict)
10231046
1024 def test_main_virtualized_archive_can_be_restricted(self):1047 def test_main_virtualized_archive_can_be_restricted(self):
1025 # A main archive can be restricted to certain architectures1048 # A main archive can be restricted to certain architectures
@@ -1071,15 +1094,63 @@
1071 self.assertContentEqual([], self.archive.enabled_restricted_families)1094 self.assertContentEqual([], self.archive.enabled_restricted_families)
10721095
10731096
1097class TestBuilddSecret(TestCaseWithFactory):
1098 """Test buildd_secret security.
1099
1100 The buildd_secret is used by the slave scanner when generating a
1101 sources.list entry for the builder to access a private archive. It is
1102 essentially the password to the archive for the builder.
1103 """
1104
1105 layer = DatabaseFunctionalLayer
1106
1107 def setUp(self):
1108 super(TestBuilddSecret, self).setUp()
1109 self.archive = self.factory.makeArchive()
1110
1111 def test_anonymous_cannot_set_buildd_secret(self):
1112 login(ANONYMOUS)
1113 e = self.assertRaises(
1114 Unauthorized, setattr, self.archive, "buildd_secret", "boing")
1115 self.assertEqual("launchpad.Commercial", e.args[2])
1116
1117 def test_commercial_admin_can_set_buildd_secret(self):
1118 with celebrity_logged_in("commercial_admin"):
1119 self.archive.buildd_secret = "not so secret at all"
1120
1121 def test_admin_can_set_buildd_secret(self):
1122 with celebrity_logged_in("admin"):
1123 self.archive.buildd_secret = "not so secret"
1124
1125 def test_public_archive_has_public_buildd_secret(self):
1126 # In a public PPA, the buildd "secret" is visible to anyone.
1127 with celebrity_logged_in("admin"):
1128 self.archive.buildd_secret = "not so secret"
1129 login(ANONYMOUS)
1130 self.assertFalse(self.archive.private)
1131 self.assertEqual("not so secret", self.archive.buildd_secret)
1132
1133 def test_private_archive_has_private_buildd_secret(self):
1134 # In a private PPA, the buildd secret can only be read by users with
1135 # launchpad.View on the archive.
1136 with celebrity_logged_in("admin"):
1137 self.archive.buildd_secret = "really secret"
1138 self.archive.private = True
1139 login(ANONYMOUS)
1140 e = self.assertRaises(
1141 Unauthorized, getattr, self.archive, "buildd_secret")
1142 self.assertEqual("launchpad.View", e.args[2])
1143 with person_logged_in(self.archive.owner):
1144 self.assertEqual("really secret", self.archive.buildd_secret)
1145
1146
1074class TestArchiveTokens(TestCaseWithFactory):1147class TestArchiveTokens(TestCaseWithFactory):
1075 layer = LaunchpadZopelessLayer1148 layer = LaunchpadZopelessLayer
10761149
1077 def setUp(self):1150 def setUp(self):
1078 super(TestArchiveTokens, self).setUp()1151 super(TestArchiveTokens, self).setUp()
1079 owner = self.factory.makePerson()1152 owner = self.factory.makePerson()
1080 self.private_ppa = self.factory.makeArchive(owner=owner)1153 self.private_ppa = self.factory.makeArchive(owner=owner, private=True)
1081 self.private_ppa.buildd_secret = 'blah'
1082 self.private_ppa.private = True
1083 self.joe = self.factory.makePerson(name='joe')1154 self.joe = self.factory.makePerson(name='joe')
1084 self.private_ppa.newSubscription(self.joe, owner)1155 self.private_ppa.newSubscription(self.joe, owner)
10851156
@@ -1631,8 +1702,7 @@
1631 # By default, a person cannot upload to any component of an archive.1702 # By default, a person cannot upload to any component of an archive.
1632 archive = self.factory.makeArchive()1703 archive = self.factory.makeArchive()
1633 person = self.factory.makePerson()1704 person = self.factory.makePerson()
1634 self.assertEqual(set(),1705 self.assertFalse(set(archive.getComponentsForUploader(person)))
1635 set(archive.getComponentsForUploader(person)))
16361706
1637 def test_components_for_person_with_permissions(self):1707 def test_components_for_person_with_permissions(self):
1638 # If a person has been explicitly granted upload permissions to a1708 # If a person has been explicitly granted upload permissions to a
@@ -1649,6 +1719,30 @@
1649 set(archive.getComponentsForUploader(person)))1719 set(archive.getComponentsForUploader(person)))
16501720
16511721
1722class TestPockets(TestCaseWithFactory):
1723
1724 layer = DatabaseFunctionalLayer
1725
1726 def test_no_pockets_for_arbitrary_person(self):
1727 # By default, a person cannot upload to any pocket of an archive.
1728 archive = self.factory.makeArchive()
1729 person = self.factory.makePerson()
1730 self.assertEqual(set(), set(archive.getPocketsForUploader(person)))
1731
1732 def test_pockets_for_person_with_permissions(self):
1733 # If a person has been explicitly granted upload permissions to a
1734 # particular pocket, then those pockets are included in
1735 # IArchive.getPocketsForUploader.
1736 archive = self.factory.makeArchive()
1737 person = self.factory.makePerson()
1738 # Only admins or techboard members can add permissions normally. That
1739 # restriction isn't relevant to this test.
1740 ap_set = removeSecurityProxy(getUtility(IArchivePermissionSet))
1741 ap = ap_set.newPocketUploader(
1742 archive, person, PackagePublishingPocket.SECURITY)
1743 self.assertEqual(set([ap]), set(archive.getPocketsForUploader(person)))
1744
1745
1652class TestValidatePPA(TestCaseWithFactory):1746class TestValidatePPA(TestCaseWithFactory):
16531747
1654 layer = DatabaseFunctionalLayer1748 layer = DatabaseFunctionalLayer
@@ -2489,3 +2583,254 @@
2489 with person_logged_in(archive2.owner):2583 with person_logged_in(archive2.owner):
2490 self.assertRaises(2584 self.assertRaises(
2491 AssertionError, archive2.removeCopyNotification, job2.id)2585 AssertionError, archive2.removeCopyNotification, job2.id)
2586
2587
2588class TestPublishFlag(TestCaseWithFactory):
2589
2590 layer = DatabaseFunctionalLayer
2591
2592 def test_primary_archive_published_by_default(self):
2593 distribution = self.factory.makeDistribution()
2594 self.assertTrue(distribution.main_archive.publish)
2595
2596 def test_partner_archive_published_by_default(self):
2597 partner = self.factory.makeArchive(purpose=ArchivePurpose.PARTNER)
2598 self.assertTrue(partner.publish)
2599
2600 def test_ppa_published_by_default(self):
2601 ppa = self.factory.makeArchive(purpose=ArchivePurpose.PPA)
2602 self.assertTrue(ppa.publish)
2603
2604 def test_copy_archive_not_published_by_default(self):
2605 copy = self.factory.makeArchive(purpose=ArchivePurpose.COPY)
2606 self.assertFalse(copy.publish)
2607
2608
2609class TestPPANaming(TestCaseWithFactory):
2610
2611 layer = DatabaseFunctionalLayer
2612
2613 def test_unique_copy_archive_name(self):
2614 # Non-PPA archive names must be unique for a given distribution.
2615 uber = self.factory.makeDistribution()
2616 self.factory.makeArchive(
2617 purpose=ArchivePurpose.COPY, distribution=uber, name="uber-copy")
2618 self.assertRaises(
2619 AssertionError, self.factory.makeArchive,
2620 purpose=ArchivePurpose.COPY, distribution=uber, name="uber-copy")
2621
2622 def test_unique_partner_archive_name(self):
2623 # Partner archive names must be unique for a given distribution.
2624 uber = self.factory.makeDistribution()
2625 self.factory.makeArchive(
2626 purpose=ArchivePurpose.PARTNER, distribution=uber,
2627 name="uber-partner")
2628 self.assertRaises(
2629 AssertionError, self.factory.makeArchive,
2630 purpose=ArchivePurpose.PARTNER, distribution=uber,
2631 name="uber-partner")
2632
2633 def test_unique_ppa_name_per_owner_and_distribution(self):
2634 person = self.factory.makePerson()
2635 self.factory.makeArchive(owner=person, name="ppa")
2636 self.assertEqual(
2637 "PPA for %s" % person.displayname, person.archive.displayname)
2638 self.assertEqual("ppa", person.archive.name)
2639 self.assertRaises(
2640 AssertionError, self.factory.makeArchive, owner=person, name="ppa")
2641
2642 def test_default_archive(self):
2643 # Creating multiple PPAs does not affect the existing traversal from
2644 # IPerson to a single IArchive.
2645 person = self.factory.makePerson()
2646 ppa = self.factory.makeArchive(owner=person, name="ppa")
2647 self.factory.makeArchive(owner=person, name="nightly")
2648 self.assertEqual(ppa, person.archive)
2649
2650 def test_non_default_ppas_have_different_displayname(self):
2651 person = self.factory.makePerson()
2652 another_ppa = self.factory.makeArchive(owner=person, name="nightly")
2653 self.assertEqual(
2654 "PPA named nightly for %s" % person.displayname,
2655 another_ppa.displayname)
2656
2657 def test_archives_cannot_have_same_name_as_distribution(self):
2658 boingolinux = self.factory.makeDistribution(name="boingolinux")
2659 self.assertRaises(
2660 AssertionError, getUtility(IArchiveSet).new,
2661 owner=self.factory.makePerson(), purpose=ArchivePurpose.PRIMARY,
2662 distribution=boingolinux, name=boingolinux.name)
2663
2664
2665class TestPPALookup(TestCaseWithFactory):
2666
2667 layer = DatabaseFunctionalLayer
2668
2669 def setUp(self):
2670 super(TestPPALookup, self).setUp()
2671 self.person = self.factory.makePerson()
2672 self.factory.makeArchive(owner=self.person, name="ppa")
2673 self.nightly = self.factory.makeArchive(
2674 owner=self.person, name="nightly")
2675
2676 def test_ppas(self):
2677 # IPerson.ppas returns all owned PPAs ordered by name.
2678 self.assertEqual(
2679 ["nightly", "ppa"], [ppa.name for ppa in self.person.ppas])
2680
2681 def test_getPPAByName(self):
2682 default_ppa = self.person.getPPAByName("ppa")
2683 self.assertEqual(self.person.archive, default_ppa)
2684 nightly_ppa = self.person.getPPAByName("nightly")
2685 self.assertEqual(self.nightly, nightly_ppa)
2686
2687 def test_NoSuchPPA(self):
2688 self.assertRaises(NoSuchPPA, self.person.getPPAByName, "not-found")
2689
2690
2691class TestDisplayName(TestCaseWithFactory):
2692
2693 layer = DatabaseFunctionalLayer
2694
2695 def test_default(self):
2696 # If 'displayname' is omitted when creating the archive, there is a
2697 # sensible default.
2698 archive = self.factory.makeArchive(name="test-ppa")
2699 self.assertEqual(
2700 "PPA named test-ppa for %s" % archive.owner.displayname,
2701 archive.displayname)
2702
2703 def test_provided(self):
2704 # If 'displayname' is provided, it is used.
2705 archive = self.factory.makeArchive(
2706 purpose=ArchivePurpose.COPY,
2707 displayname="Rock and roll with rebuilds!", name="test-rebuild")
2708 self.assertEqual("Rock and roll with rebuilds!", archive.displayname)
2709
2710 def test_editable(self):
2711 # Anyone with edit permission on the archive can change displayname.
2712 archive = self.factory.makeArchive(name="test-ppa")
2713 login("no-priv@canonical.com")
2714 e = self.assertRaises(
2715 Unauthorized, setattr, archive, "displayname", "No-way!")
2716 self.assertEqual("launchpad.Edit", e.args[2])
2717 with person_logged_in(archive.owner):
2718 archive.displayname = "My testing packages"
2719
2720
2721class TestSigningKeyPropagation(TestCaseWithFactory):
2722 """Signing keys are shared between PPAs owned by the same person/team."""
2723
2724 layer = DatabaseFunctionalLayer
2725
2726 def test_ppa_created_with_no_signing_key(self):
2727 ppa = self.factory.makeArchive(purpose=ArchivePurpose.PPA)
2728 self.assertIsNone(ppa.signing_key)
2729
2730 def test_default_signing_key_propagated_to_new_ppa(self):
2731 person = self.factory.makePerson()
2732 ppa = self.factory.makeArchive(
2733 owner=person, purpose=ArchivePurpose.PPA, name="ppa")
2734 self.assertEqual(ppa, person.archive)
2735 self.factory.makeGPGKey(person)
2736 with celebrity_logged_in("admin"):
2737 person.archive.signing_key = person.gpg_keys[0]
2738 ppa_with_key = self.factory.makeArchive(
2739 owner=person, purpose=ArchivePurpose.PPA)
2740 self.assertEqual(person.gpg_keys[0], ppa_with_key.signing_key)
2741
2742
2743class TestCountersAndSummaries(TestCaseWithFactory):
2744
2745 layer = LaunchpadFunctionalLayer
2746
2747 def assertDictEqual(self, one, two):
2748 self.assertContentEqual(one.items(), two.items())
2749
2750 def test_cprov_build_counters_in_sampledata(self):
2751 cprov_archive = getUtility(IPersonSet).getByName("cprov").archive
2752 expected_counters = {
2753 "failed": 1,
2754 "pending": 0,
2755 "succeeded": 3,
2756 "superseded": 0,
2757 "total": 4,
2758 }
2759 self.assertDictEqual(
2760 expected_counters, cprov_archive.getBuildCounters())
2761
2762 def test_ubuntu_build_counters_in_sampledata(self):
2763 ubuntu_archive = getUtility(IDistributionSet)["ubuntu"].main_archive
2764 expected_counters = {
2765 "failed": 5,
2766 "pending": 2,
2767 "succeeded": 8,
2768 "superseded": 3,
2769 "total": 18,
2770 }
2771 self.assertDictEqual(
2772 expected_counters, ubuntu_archive.getBuildCounters())
2773 # include_needsbuild=False excludes builds in status NEEDSBUILD.
2774 expected_counters["pending"] -= 1
2775 expected_counters["total"] -= 1
2776 self.assertDictEqual(
2777 expected_counters,
2778 ubuntu_archive.getBuildCounters(include_needsbuild=False))
2779
2780 def assertBuildSummaryMatches(self, status, builds, summary):
2781 self.assertEqual(status, summary["status"])
2782 self.assertContentEqual(
2783 builds, [build.title for build in summary["builds"]])
2784
2785 def test_build_summaries_in_sampledata(self):
2786 ubuntu = getUtility(IDistributionSet)["ubuntu"]
2787 firefox_source = ubuntu.getSourcePackage("mozilla-firefox")
2788 firefox_source_pub = firefox_source.publishing_history[0]
2789 foobar = ubuntu.getSourcePackage("foobar")
2790 foobar_pub = foobar.publishing_history[0]
2791 build_summaries = ubuntu.main_archive.getBuildSummariesForSourceIds(
2792 [firefox_source_pub.id, foobar_pub.id])
2793 self.assertEqual(2, len(build_summaries))
2794 expected_firefox_builds = [
2795 "hppa build of mozilla-firefox 0.9 in ubuntu warty RELEASE",
2796 "i386 build of mozilla-firefox 0.9 in ubuntu warty RELEASE",
2797 ]
2798 self.assertBuildSummaryMatches(
2799 BuildSetStatus.FULLYBUILT, expected_firefox_builds,
2800 build_summaries[firefox_source_pub.id])
2801 expected_foobar_builds = [
2802 "i386 build of foobar 1.0 in ubuntu warty RELEASE",
2803 ]
2804 self.assertBuildSummaryMatches(
2805 BuildSetStatus.FAILEDTOBUILD, expected_foobar_builds,
2806 build_summaries[foobar_pub.id])
2807
2808 def test_private_archives_have_private_counters_and_summaries(self):
2809 archive = self.factory.makeArchive()
2810 distroseries = self.factory.makeDistroSeries(
2811 distribution=archive.distribution)
2812 with celebrity_logged_in("admin"):
2813 archive.private = True
2814 publisher = SoyuzTestPublisher()
2815 publisher.setUpDefaultDistroSeries(distroseries)
2816 publisher.addFakeChroots(distroseries)
2817 publisher.getPubBinaries(archive=archive)
2818 source_id = archive.getPublishedSources()[0].id
2819
2820 # An admin can see the counters and build summaries.
2821 archive.getBuildCounters()["total"]
2822 archive.getBuildSummariesForSourceIds([source_id])
2823
2824 # The archive owner can see the counters and build summaries.
2825 with person_logged_in(archive.owner):
2826 archive.getBuildCounters()["total"]
2827 archive.getBuildSummariesForSourceIds([source_id])
2828
2829 # The public cannot.
2830 login("no-priv@canonical.com")
2831 e = self.assertRaises(
2832 Unauthorized, getattr, archive, "getBuildCounters")
2833 self.assertEqual("launchpad.View", e.args[2])
2834 e = self.assertRaises(
2835 Unauthorized, getattr, archive, "getBuildSummariesForSourceIds")
2836 self.assertEqual("launchpad.View", e.args[2])
24922837
=== modified file 'lib/lp/soyuz/tests/test_archivearch.py'
--- lib/lp/soyuz/tests/test_archivearch.py 2012-01-01 02:58:52 +0000
+++ lib/lp/soyuz/tests/test_archivearch.py 2012-06-12 00:00:37 +0000
@@ -1,4 +1,4 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the1# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Test ArchiveArch features."""4"""Test ArchiveArch features."""
@@ -49,7 +49,7 @@
49 self.archive_arch_set.getRestrictedFamilies(self.ppa))49 self.archive_arch_set.getRestrictedFamilies(self.ppa))
50 results = dict(50 results = dict(
51 (row[0].name, row[1] is not None) for row in result_set)51 (row[0].name, row[1] is not None) for row in result_set)
52 self.assertEquals(52 self.assertEqual(
53 {'arm': False, 'cell-proc': True, 'omap': False},53 {'arm': False, 'cell-proc': True, 'omap': False},
54 results)54 results)
5555
@@ -62,7 +62,7 @@
62 self.archive_arch_set.getRestrictedFamilies(self.ppa))62 self.archive_arch_set.getRestrictedFamilies(self.ppa))
63 results = dict(63 results = dict(
64 (row[0].name, row[1] is not None) for row in result_set)64 (row[0].name, row[1] is not None) for row in result_set)
65 self.assertEquals(65 self.assertEqual(
66 {'arm': False, 'cell-proc': True, 'omap': False},66 {'arm': False, 'cell-proc': True, 'omap': False},
67 results)67 results)
6868
@@ -70,8 +70,30 @@
70 # Test ArchiveArchSet.getByArchive returns no other archives.70 # Test ArchiveArchSet.getByArchive returns no other archives.
71 self.archive_arch_set.new(self.ppa, self.cell_proc)71 self.archive_arch_set.new(self.ppa, self.cell_proc)
72 self.archive_arch_set.new(self.ubuntu_archive, self.omap)72 self.archive_arch_set.new(self.ubuntu_archive, self.omap)
73 result_set = list(self.archive_arch_set.getByArchive(self.ppa))
74 self.assertEqual(1, len(result_set))
75 self.assertEqual(self.ppa, result_set[0].archive)
76 self.assertEqual(self.cell_proc, result_set[0].processorfamily)
77
78 def test_getByArchive_follows_creation_order(self):
79 # The result of ArchiveArchSet.getByArchive follows the order in
80 # which architecture associations were added.
81 self.archive_arch_set.new(self.ppa, self.cell_proc)
82 self.archive_arch_set.new(self.ppa, self.omap)
83 result_set = list(self.archive_arch_set.getByArchive(self.ppa))
84 self.assertEqual(2, len(result_set))
85 self.assertEqual(self.ppa, result_set[0].archive)
86 self.assertEqual(self.cell_proc, result_set[0].processorfamily)
87 self.assertEqual(self.ppa, result_set[1].archive)
88 self.assertEqual(self.omap, result_set[1].processorfamily)
89
90 def test_getByArchive_specific_architecture(self):
91 # ArchiveArchSet.getByArchive can query for a specific architecture
92 # association.
93 self.archive_arch_set.new(self.ppa, self.cell_proc)
94 self.archive_arch_set.new(self.ppa, self.omap)
73 result_set = list(95 result_set = list(
74 self.archive_arch_set.getByArchive(self.ppa))96 self.archive_arch_set.getByArchive(self.ppa, self.cell_proc))
75 self.assertEquals(1, len(result_set))97 self.assertEqual(1, len(result_set))
76 self.assertEquals(self.ppa, result_set[0].archive)98 self.assertEqual(self.ppa, result_set[0].archive)
77 self.assertEquals(self.cell_proc, result_set[0].processorfamily)99 self.assertEqual(self.cell_proc, result_set[0].processorfamily)
78100
=== modified file 'lib/lp/soyuz/tests/test_packageupload.py'
--- lib/lp/soyuz/tests/test_packageupload.py 2012-05-30 08:50:50 +0000
+++ lib/lp/soyuz/tests/test_packageupload.py 2012-06-12 00:00:37 +0000
@@ -115,9 +115,7 @@
115 self.test_publisher.prepareBreezyAutotest()115 self.test_publisher.prepareBreezyAutotest()
116 ppa = self.factory.makeArchive(116 ppa = self.factory.makeArchive(
117 distribution=self.test_publisher.ubuntutest,117 distribution=self.test_publisher.ubuntutest,
118 purpose=ArchivePurpose.PPA)118 purpose=ArchivePurpose.PPA, private=True)
119 ppa.buildd_secret = 'x'
120 ppa.private = True
121119
122 changesfile_path = (120 changesfile_path = (
123 'lib/lp/archiveuploader/tests/data/suite/'121 'lib/lp/archiveuploader/tests/data/suite/'