1
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
# pylint: disable-msg=E0211,E0213
6
"""Archive interfaces."""
11
'ALLOW_RELEASE_BUILDS',
13
'ArchiveDependencyError',
17
'CannotSwitchPrivacy',
19
'CannotRestrictArchitectures',
20
'CannotUploadToArchive',
22
'CannotUploadToPocket',
23
'ForbiddenByFeatureFlag',
24
'FULL_COMPONENT_SUPPORT',
30
'IArchiveEditDependenciesForm',
33
'IDistributionArchive',
34
'InsufficientUploadRights',
36
'InvalidExternalDependencies',
37
'InvalidPocketForPartnerArchive',
38
'InvalidPocketForPPA',
40
'MAIN_ARCHIVE_PURPOSES',
42
'NoRightsForComponent',
46
'VersionRequiresName',
47
'default_name_by_purpose',
48
'validate_external_dependencies',
52
from urlparse import urlparse
54
from lazr.enum import DBEnumeratedType
55
from lazr.restful.declarations import (
58
export_as_webservice_entry,
59
export_factory_operation,
61
export_read_operation,
62
export_write_operation,
64
operation_for_version,
66
operation_returns_collection_of,
67
operation_returns_entry,
71
from lazr.restful.fields import (
75
from zope.interface import (
79
from zope.schema import (
90
from canonical.launchpad import _
91
from lp.app.interfaces.launchpad import IPrivacy
92
from lp.app.errors import NameLookupFailed
93
from lp.app.validators.name import name_validator
94
from lp.registry.interfaces.gpg import IGPGKey
95
from lp.registry.interfaces.person import IPerson
96
from lp.registry.interfaces.role import IHasOwner
97
from lp.services.fields import (
102
from lp.soyuz.enums import ArchivePurpose
103
from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
104
from lp.soyuz.interfaces.component import IComponent
107
@error_status(httplib.BAD_REQUEST)
108
class ArchiveDependencyError(Exception):
109
"""Raised when an `IArchiveDependency` does not fit the context archive.
111
A given dependency is considered inappropriate when:
113
* It is the archive itself,
115
* It is already recorded.
119
# Exceptions used in the webservice that need to be in this file to get
121
@error_status(httplib.BAD_REQUEST)
122
class CannotCopy(Exception):
123
"""Exception raised when a copy cannot be performed."""
126
@error_status(httplib.FORBIDDEN)
127
class ForbiddenByFeatureFlag(Exception):
128
"""Exception raised when using a method protected by a feature flag.
132
@error_status(httplib.BAD_REQUEST)
133
class CannotSwitchPrivacy(Exception):
134
"""Raised when switching the privacy of an archive that has
135
publishing records."""
138
class PocketNotFound(NameLookupFailed):
139
"""Invalid pocket."""
140
_message_prefix = "No such pocket"
143
@error_status(httplib.BAD_REQUEST)
144
class AlreadySubscribed(Exception):
145
"""Raised when creating a subscription for a subscribed person."""
148
@error_status(httplib.BAD_REQUEST)
149
class ArchiveNotPrivate(Exception):
150
"""Raised when creating an archive subscription for a public archive."""
153
@error_status(httplib.BAD_REQUEST)
154
class NoTokensForTeams(Exception):
155
"""Raised when creating a token for a team, rather than a person."""
158
class ComponentNotFound(NameLookupFailed):
159
"""Invalid source name."""
160
_message_prefix = 'No such component'
163
@error_status(httplib.BAD_REQUEST)
164
class InvalidComponent(Exception):
165
"""Invalid component name."""
168
class NoSuchPPA(NameLookupFailed):
169
"""Raised when we try to look up an PPA that doesn't exist."""
170
_message_prefix = "No such ppa"
173
@error_status(httplib.BAD_REQUEST)
174
class VersionRequiresName(Exception):
175
"""Raised on some queries when version is specified but name is not."""
178
class CannotRestrictArchitectures(Exception):
179
"""The architectures for this archive can not be restricted."""
182
@error_status(httplib.FORBIDDEN)
183
class CannotUploadToArchive(Exception):
184
"""A reason for not being able to upload to an archive."""
186
_fmt = '%(person)s has no upload rights to %(archive)s.'
188
def __init__(self, **args):
189
"""Construct a `CannotUploadToArchive`."""
190
Exception.__init__(self, self._fmt % args)
193
class InvalidPocketForPartnerArchive(CannotUploadToArchive):
194
"""Partner archives only support some pockets."""
196
_fmt = "Partner uploads must be for the RELEASE or PROPOSED pocket."
199
@error_status(httplib.FORBIDDEN)
200
class CannotUploadToPocket(Exception):
201
"""Returned when a pocket is closed for uploads."""
203
def __init__(self, distroseries, pocket):
204
Exception.__init__(self,
205
"Not permitted to upload to the %s pocket in a series in the "
206
"'%s' state." % (pocket.name, distroseries.status.name))
209
class CannotUploadToPPA(CannotUploadToArchive):
210
"""Raised when a person cannot upload to a PPA."""
212
_fmt = 'Signer has no upload rights to this PPA.'
215
class NoRightsForArchive(CannotUploadToArchive):
216
"""Raised when a person has absolutely no upload rights to an archive."""
219
"The signer of this package has no upload rights to this "
220
"distribution's primary archive. Did you mean to upload to "
224
class InsufficientUploadRights(CannotUploadToArchive):
225
"""Raised when a person has insufficient upload rights."""
227
"The signer of this package is lacking the upload rights for "
228
"the source package, component or package set in question.")
231
class NoRightsForComponent(CannotUploadToArchive):
232
"""Raised when a person tries to upload to a component without permission.
236
"Signer is not permitted to upload to the component '%(component)s'.")
238
def __init__(self, component):
239
CannotUploadToArchive.__init__(self, component=component.name)
242
class InvalidPocketForPPA(CannotUploadToArchive):
243
"""PPAs only support some pockets."""
245
_fmt = "PPA uploads must be for the RELEASE pocket."
248
class ArchiveDisabled(CannotUploadToArchive):
249
"""Uploading to a disabled archive is not allowed."""
251
_fmt = ("%(archive_name)s is disabled.")
253
def __init__(self, archive_name):
254
CannotUploadToArchive.__init__(self, archive_name=archive_name)
257
@error_status(httplib.BAD_REQUEST)
258
class InvalidExternalDependencies(Exception):
259
"""Tried to set external dependencies to an invalid value."""
261
def __init__(self, errors):
262
error_msg = 'Invalid external dependencies:\n%s\n' % '\n'.join(errors)
263
super(Exception, self).__init__(self, error_msg)
267
class IArchivePublic(IHasOwner, IPrivacy):
268
"""An Archive interface for publicly available operations."""
269
id = Attribute("The archive ID.")
273
title=_('Owner'), required=True, vocabulary='ValidOwner',
274
description=_("""The archive owner.""")))
278
title=_("Name"), required=True,
279
constraint=name_validator,
281
"At least one lowercase letter or number, followed by "
282
"letters, numbers, dots, hyphens or pluses. "
283
"Keep this name short; it is used in URLs.")))
285
displayname = exported(
287
title=_("Display name"), required=True,
288
description=_("A short title for the archive.")))
290
title = TextLine(title=_("Name"), required=False, readonly=True)
293
title=_("Enabled"), required=False,
295
"Accept and build packages uploaded to the archive."))
298
title=_("Publish"), required=False,
299
description=_("Whether or not to update the APT repository. If "
300
"disabled, nothing will be published. If the archive is "
301
"private then additionally no builds will be dispatched."))
303
# This is redefined from IPrivacy.private because the attribute is
304
# read-only. The value is guarded by a validator.
307
title=_("Private"), required=False,
309
"Restrict access to the archive to its owner and "
310
"subscribers. This can only be changed if the archive has "
311
"never had any sources published.")))
313
require_virtualized = exported(
315
title=_("Require virtualized builders"), required=False,
316
readonly=False, description=_(
317
"Only build the archive's packages on virtual builders.")))
319
build_debug_symbols = Bool(
320
title=_("Build debug symbols"), required=False,
322
"Create debug symbol packages for builds in the archive."))
324
authorized_size = Int(
325
title=_("Authorized size"), required=False,
327
description=_("Maximum size, in MiB, allowed for the archive."))
330
title=_("Purpose of archive."), required=True, readonly=True,
334
title=_("Status of archive."), required=True, readonly=True,
337
sources_cached = Int(
338
title=_("Number of sources cached"), required=False,
339
description=_("Number of source packages cached in this PPA."))
341
binaries_cached = Int(
342
title=_("Number of binaries cached"), required=False,
343
description=_("Number of binary packages cached in this PPA."))
345
package_description_cache = Attribute(
346
"Concatenation of the source and binary packages published in this "
347
"archive. Its content is used for indexed searches across archives.")
349
distribution = exported(
351
Interface, # Redefined to IDistribution later.
352
title=_("The distribution that uses or is used by this "
355
signing_key = Object(
356
title=_('Repository sigining key.'), required=False, schema=IGPGKey)
358
debug_archive = Attribute(
359
"The archive into which debug binaries should be uploaded.")
361
default_component = Reference(
364
"The default component for this archive. Publications without a "
365
"valid component will be assigned this one."))
367
archive_url = Attribute("External archive URL.")
369
is_ppa = Attribute("True if this archive is a PPA.")
371
is_partner = Attribute("True if this archive is a partner archive.")
373
is_copy = Attribute("True if this archive is a copy archive.")
376
title=_("True if archive is a main archive type"), required=False)
379
title=_("True if the archive is in the active state"),
380
required=False, readonly=True)
382
series_with_sources = Attribute(
383
"DistroSeries to which this archive has published sources")
384
number_of_sources = Attribute(
385
'The number of sources published in the context archive.')
386
number_of_binaries = Attribute(
387
'The number of binaries published in the context archive.')
388
sources_size = Attribute(
389
'The size of sources published in the context archive.')
390
binaries_size = Attribute(
391
'The size of binaries published in the context archive.')
392
estimated_size = Attribute('Estimated archive size.')
395
title=_("Total number of builds in archive"), required=True,
397
description=_("The total number of builds in this archive. "
398
"This counter does not include discontinued "
399
"(superseded, cancelled, obsoleted) builds"))
402
title=_("Number of pending builds in archive"), required=True,
404
description=_("The number of pending builds in this archive."))
406
succeeded_count = Int(
407
title=_("Number of successful builds in archive"), required=True,
409
description=_("The number of successful builds in this archive."))
411
building_count = Int(
412
title=_("Number of active builds in archive"), required=True,
414
description=_("The number of active builds in this archive."))
417
title=_("Number of failed builds in archive"), required=True,
419
description=_("The number of failed builds in this archive."))
421
date_created = Datetime(
422
title=_('Date created'), required=False, readonly=True,
423
description=_("The time when the archive was created."))
425
relative_build_score = Int(
426
title=_("Relative build score"), required=True, readonly=False,
428
"A delta to apply to all build scores for the archive. Builds "
429
"with a higher score will build sooner."))
431
external_dependencies = exported(
432
Text(title=_("External dependencies"), required=False,
433
readonly=False, description=_(
434
"Newline-separated list of repositories to be used to retrieve "
435
"any external build dependencies when building packages in the "
436
"archive, in the format:\n"
437
"deb http[s]://[user:pass@]<host>[/path] %(series)s[-pocket] "
439
"The series variable is replaced with the series name of the "
441
"NOTE: This is for migration of OEM PPAs only!")))
443
enabled_restricted_families = exported(
445
title=_("Enabled restricted families"),
447
"The restricted architecture families on which the archive "
449
value_type=Reference(schema=Interface),
450
# Really IProcessorFamily.
454
commercial = exported(
456
title=_("Commercial"),
459
"Display the archive in Software Center's commercial "
460
"listings. Only private archives can be commercial.")))
462
def getSourcesForDeletion(name=None, status=None, distroseries=None):
463
"""All `ISourcePackagePublishingHistory` available for deletion.
465
:param: name: optional source name filter (SQL LIKE)
466
:param: status: `PackagePublishingStatus` filter, can be a sequence.
467
:param: distroseries: `IDistroSeries` filter.
469
:return: SelectResults containing `ISourcePackagePublishingHistory`.
472
def getPublishedOnDiskBinaries(name=None, version=None, status=None,
473
distroarchseries=None, exact_match=False):
474
"""Unique `IBinaryPackagePublishingHistory` target to this archive.
476
In spite of getAllPublishedBinaries method, this method only returns
477
distinct binary publications inside this Archive, i.e, it excludes
478
architecture-independent publication for other architetures than the
479
nominatedarchindep. In few words it represents the binary files
480
published in the archive disk pool.
482
:param: name: binary name filter (exact match or SQL LIKE controlled
483
by 'exact_match' argument).
484
:param: version: binary version filter (always exact match).
485
:param: status: `PackagePublishingStatus` filter, can be a list.
486
:param: distroarchseries: `IDistroArchSeries` filter, can be a list.
487
:param: pocket: `PackagePublishingPocket` filter.
488
:param: exact_match: either or not filter source names by exact
491
:return: SelectResults containing `IBinaryPackagePublishingHistory`.
494
def allowUpdatesToReleasePocket():
495
"""Return whether the archive allows publishing to the release pocket.
497
If a distroseries is stable, normally release pocket publishings are
498
not allowed. However some archive types allow this.
500
:return: True or False
503
def getComponentsForSeries(distroseries):
504
"""Calculate the components available for use in this archive.
506
:return: An `IResultSet` of `IComponent` objects.
509
def updateArchiveCache():
510
"""Concentrate cached information about the archive contents.
512
Group the relevant package information (source name, binary names,
513
binary summaries and distroseries with binaries) strings in the
514
IArchive.package_description_cache search indexes (fti).
516
Updates 'sources_cached' and 'binaries_cached' counters.
518
Also include owner 'name' and 'displayname' to avoid inpecting the
519
Person table indexes while searching.
522
def findDepCandidates(distro_arch_series, pocket, component,
523
source_package_name, dep_name):
524
"""Return matching binaries in this archive and its dependencies.
526
Return all published `IBinaryPackagePublishingHistory` records with
527
the given name, in this archive and dependencies as specified by the
528
given build context, using the usual archive dependency rules.
530
We can't just use the first, since there may be other versions
531
published in other dependency archives.
533
:param distro_arch_series: the context `IDistroArchSeries`.
534
:param pocket: the context `PackagePublishingPocket`.
535
:param component: the context `IComponent`.
536
:param source_package_name: the context source package name (as text).
537
:param dep_name: the name of the binary package to look up.
538
:return: a sequence of matching `IBinaryPackagePublishingHistory`
542
def getPermissions(person, item, perm_type):
543
"""Get the `IArchivePermission` record with the supplied details.
545
:param person: An `IPerson`
546
:param item: An `IComponent`, `ISourcePackageName`
547
:param perm_type: An ArchivePermissionType enum,
548
:return: A list of `IArchivePermission` records.
551
def checkArchivePermission(person, component_or_package=None):
552
"""Check to see if person is allowed to upload to component.
554
:param person: An `IPerson` whom should be checked for authentication.
555
:param component_or_package: The context `IComponent` or an
556
`ISourcePackageName` for the check. This parameter is
557
not required if the archive is a PPA.
559
:return: True if 'person' is allowed to upload to the specified
560
component or package name.
561
:raise TypeError: If component_or_package is not one of
562
`IComponent` or `ISourcePackageName`.
566
def canUploadSuiteSourcePackage(person, suitesourcepackage):
567
"""Check if 'person' upload 'suitesourcepackage' to 'archive'.
569
:param person: An `IPerson` who might be uploading.
570
:param suitesourcepackage: An `ISuiteSourcePackage` to be uploaded.
571
:return: True if they can, False if they cannot.
574
def checkUploadToPocket(distroseries, pocket):
575
"""Check if an upload to a particular archive and pocket is possible.
577
:param distroseries: A `IDistroSeries`
578
:param pocket: A `PackagePublishingPocket`
579
:return: Reason why uploading is not possible or None
582
@operation_parameters(
583
person=Reference(schema=IPerson),
584
distroseries=Reference(
585
# Really IDistroSeries, avoiding a circular import here.
587
title=_("The distro series"), required=True),
588
sourcepackagename=TextLine(
589
title=_("Source package name"), required=True),
591
title=_("Component"), required=True),
594
description=_("The pocket into which this entry is published"),
595
# Really PackagePublishingPocket, circular import fixed below.
596
vocabulary=DBEnumeratedType,
598
strict_component=Bool(
599
title=_("Strict component"), required=False),
601
@export_operation_as("checkUpload")
602
@export_read_operation()
603
def _checkUpload(person, distroseries, sourcepackagename, component,
604
pocket, strict_component=True):
605
"""Wrapper around checkUpload for the web service API."""
607
def checkUpload(person, distroseries, sourcepackagename, component,
608
pocket, strict_component=True):
609
"""Check if 'person' upload 'suitesourcepackage' to 'archive'.
611
:param person: An `IPerson` who might be uploading.
612
:param distroseries: The `IDistroSeries` being uploaded to.
613
:param sourcepackagename: The `ISourcePackageName` being uploaded.
614
:param component: The `Component` being uploaded to.
615
:param pocket: The `PackagePublishingPocket` of 'distroseries' being
617
:param strict_component: True if access to the specific component for
618
the package is needed to upload to it. If False, then access to
619
any component will do.
620
:return: The reason for not being able to upload, None otherwise.
623
def verifyUpload(person, sourcepackagename, component,
624
distroseries, strict_component=True):
625
"""Can 'person' upload 'sourcepackagename' to this archive ?
627
:param person: The `IPerson` trying to upload to the package. Referred
628
to as 'the signer' in upload code.
629
:param sourcepackagename: The source package being uploaded. None if
631
:param archive: The `IArchive` being uploaded to.
632
:param component: The `IComponent` that the source package belongs to.
633
:param distroseries: The upload's target distro series.
634
:param strict_component: True if access to the specific component for
635
the package is needed to upload to it. If False, then access to
636
any component will do.
637
:return: CannotUploadToArchive if 'person' cannot upload to the
642
def canAdministerQueue(person, component):
643
"""Check to see if person is allowed to administer queue items.
645
:param person: An `IPerson` whom should be checked for authenticate.
646
:param component: The context `IComponent` for the check.
648
:return: True if 'person' is allowed to administer the package upload
649
queue for items with 'component'.
652
def getFileByName(filename):
653
"""Return the corresponding `ILibraryFileAlias` in this context.
655
The following file types (and extension) can be looked up in the
658
* Source files: '.orig.tar.gz', 'tar.gz', '.diff.gz' and '.dsc';
659
* Binary files: '.deb' and '.udeb';
660
* Source changesfile: '_source.changes';
661
* Package diffs: '.diff.gz';
663
:param filename: exactly filename to be looked up.
665
:raises AssertionError if the given filename contains a unsupported
666
filename and/or extension, see the list above.
667
:raises NotFoundError if no file could not be found.
669
:return the corresponding `ILibraryFileAlias` is the file was found.
672
def getBinaryPackageRelease(name, version, archtag):
673
"""Find the specified `IBinaryPackageRelease` in the archive.
675
:param name: The `IBinaryPackageName` of the package.
676
:param version: The version of the package.
677
:param archtag: The architecture tag of the package's build. 'all'
678
will not work here -- 'i386' (the build DAS) must be used instead.
680
:return The binary package release with the given name and version,
681
or None if one does not exist or there is more than one.
684
def getBinaryPackageReleaseByFileName(filename):
685
"""Return the corresponding `IBinaryPackageRelease` in this context.
687
:param filename: The filename to look up.
688
:return: The `IBinaryPackageRelease` with the specified filename,
689
or None if it was not found.
692
def requestPackageCopy(target_location, requestor, suite=None,
693
copy_binaries=False, reason=None):
694
"""Return a new `PackageCopyRequest` for this archive.
696
:param target_location: the archive location to which the packages
698
:param requestor: The `IPerson` who is requesting the package copy
700
:param suite: The `IDistroSeries` name with optional pocket, for
701
example, 'hoary-security'. If this is not provided it will
702
default to the current series' release pocket.
703
:param copy_binaries: Whether or not binary packages should be copied
705
:param reason: The reason for this package copy request.
707
:raises NotFoundError: if the provided suite is not found for this
708
archive's distribution.
710
:return The new `IPackageCopyRequest`
713
@operation_parameters(
714
# Really IPackageset, corrected in _schema_circular_imports to avoid
716
packageset=Reference(
717
Interface, title=_("Package set"), required=True),
718
direct_permissions=Bool(
719
title=_("Ignore package set hierarchy"), required=False))
720
# Really IArchivePermission, set in _schema_circular_imports to avoid
722
@operation_returns_collection_of(Interface)
723
@export_read_operation()
724
def getUploadersForPackageset(packageset, direct_permissions=True):
725
"""The `ArchivePermission` records for uploaders to the package set.
727
:param packageset: An `IPackageset`.
728
:param direct_permissions: If True, only consider permissions granted
729
directly for the package set at hand. Otherwise, include any
730
uploaders for package sets that include this one.
732
:return: `ArchivePermission` records for all the uploaders who are
733
authorized to upload to the named source package set.
736
@operation_parameters(
737
person=Reference(schema=IPerson))
738
# Really IArchivePermission, set in _schema_circular_imports to avoid
740
@operation_returns_collection_of(Interface)
741
@export_read_operation()
742
def getPackagesetsForUploader(person):
743
"""The `ArchivePermission` records for the person's package sets.
745
:param person: An `IPerson` for whom you want to find out which
746
package sets he has access to.
748
:return: `ArchivePermission` records for all the package sets that
749
'person' is allowed to upload to.
752
def getComponentsForUploader(person):
753
"""Return the components that 'person' can upload to this archive.
755
:param person: An `IPerson` wishing to upload to an archive.
756
:return: A `set` of `IComponent`s that 'person' can upload to.
759
@operation_parameters(
760
sourcepackagename=TextLine(
761
title=_("Source package name"), required=True),
762
person=Reference(schema=IPerson))
763
# Really IArchivePermission, set in _schema_circular_imports to avoid
765
@operation_returns_collection_of(Interface)
766
@export_read_operation()
767
def getPackagesetsForSourceUploader(sourcepackagename, person):
768
"""The package set based permissions for a given source and uploader.
770
Return the `IArchivePermission` records that
771
* apply to this archive
773
- package sets that include the given source package name
776
:param sourcepackagename: the source package name; can be
777
either a string or a `ISourcePackageName`.
778
:param person: An `IPerson` for whom you want to find out which
779
package sets he has access to.
781
:raises NoSuchSourcePackageName: if a source package with the
782
given name could not be found.
783
:return: `ArchivePermission` records for the package sets that
784
include the given source package name and to which the given
788
@operation_parameters(
789
sourcepackagename=TextLine(
790
title=_("Source package name"), required=True),
791
direct_permissions=Bool(
792
title=_("Ignore package set hierarchy"), required=False))
793
# Really IArchivePermission, set in _schema_circular_imports to avoid
795
@operation_returns_collection_of(Interface)
796
@export_read_operation()
797
def getPackagesetsForSource(
798
sourcepackagename, direct_permissions=True):
799
"""All package set based permissions for the given source.
801
This method is meant to aid the process of "debugging" package set
802
based archive permission since It allows the listing of permissions
803
for the given source package in this archive (irrespective of the
806
:param sourcepackagename: the source package name; can be
807
either a string or a `ISourcePackageName`.
808
:param direct_permissions: If set only package sets that directly
809
include the given source will be considered.
811
:raises NoSuchSourcePackageName: if a source package with the
812
given name could not be found.
813
:return: `ArchivePermission` records for the package sets that
814
include the given source package name and apply to the
818
@operation_parameters(
819
sourcepackagename=TextLine(
820
title=_("Source package name"), required=True),
821
person=Reference(schema=IPerson),
822
distroseries=Reference(
823
# Really IDistroSeries, avoiding a circular import here.
825
title=_("The distro series"), required=False))
826
@export_read_operation()
827
def isSourceUploadAllowed(sourcepackagename, person, distroseries=None):
828
"""True if the person is allowed to upload the given source package.
830
Return True if there exists a permission that combines
832
* a package set that includes the given source package name
833
* the given person or a team he is a member of
835
If the source package name is included by *any* package set with
836
an explicit permission then only such explicit permissions will
839
:param sourcepackagename: the source package name; can be
840
either a string or a `ISourcePackageName`.
841
:param person: An `IPerson` for whom you want to find out which
842
package sets he has access to.
843
:param distroseries: The `IDistroSeries` for which to check
844
permissions. If none is supplied then `currentseries` in
847
:raises NoSuchSourcePackageName: if a source package with the
848
given name could not be found.
849
:return: True if the person is allowed to upload the source package.
852
num_pkgs_building = Attribute(
853
"Tuple of packages building and waiting to build")
855
def getSourcePackageReleases(build_status=None):
856
"""Return the releases for this archive.
858
:param build_status: If specified, only the distinct releases with
859
builds in the specified build status will be returned.
860
:return: A `ResultSet` of distinct `SourcePackageReleases` for this
864
def updatePackageDownloadCount(bpr, day, country, count):
865
"""Update the daily download count for a given package.
867
:param bpr: The `IBinaryPackageRelease` to update the count for.
868
:param day: The date to update the count for.
869
:param country: The `ICountry` to update the count for.
870
:param count: The new download count.
872
If there's no matching `IBinaryPackageReleaseDownloadCount` entry,
873
we create one with the given count. Otherwise we just increase the
874
count of the existing one by the given amount.
877
def getPackageDownloadTotal(bpr):
878
"""Get the total download count for a given package."""
880
def validatePPA(person, proposed_name):
881
"""Check if a proposed name for a PPA is valid.
883
:param person: A Person identifying the requestor.
884
:param proposed_name: A String identifying the proposed PPA name.
888
"""Return iterable containing valid pocket names for this archive."""
890
def getOverridePolicy():
891
"""Returns an instantiated `IOverridePolicy` for the archive."""
894
class IArchiveView(IHasBuildRecords):
895
"""Archive interface for operations restricted by view privilege."""
897
buildd_secret = TextLine(
898
title=_("Build farm secret"), required=False,
900
"The password used by the build farm to access the archive."))
902
dependencies = exported(
904
title=_("Archive dependencies recorded for this archive."),
905
value_type=Reference(schema=Interface),
906
# Really IArchiveDependency
909
description = exported(
911
title=_("Description"), required=False,
913
"A short description of the archive. URLs are allowed and "
914
"will be rendered as links.")))
916
signing_key_fingerprint = exported(
918
title=_("Archive signing key fingerprint"), required=False,
919
description=_("A OpenPGP signing key fingerprint (40 chars) "
920
"for this PPA or None if there is no signing "
923
@rename_parameters_as(name="source_name", distroseries="distro_series")
924
@operation_parameters(
925
name=TextLine(title=_("Source package name"), required=False),
926
version=TextLine(title=_("Version"), required=False),
928
title=_('Package Publishing Status'),
929
description=_('The status of this publishing record'),
930
# Really PackagePublishingStatus, circular import fixed below.
931
vocabulary=DBEnumeratedType,
933
distroseries=Reference(
934
# Really IDistroSeries, fixed below to avoid circular import.
936
title=_("Distroseries name"), required=False),
939
description=_("The pocket into which this entry is published"),
940
# Really PackagePublishingPocket, circular import fixed below.
941
vocabulary=DBEnumeratedType,
942
required=False, readonly=True),
944
title=_("Exact Match"),
945
description=_("Whether or not to filter source names by exact"
948
created_since_date=Datetime(
949
title=_("Created Since Date"),
950
description=_("Return entries whose `date_created` is greater "
951
"than or equal to this date."),
953
component_name=TextLine(title=_("Component name"), required=False),
955
# Really returns ISourcePackagePublishingHistory, see below for
956
# patch to avoid circular import.
957
@call_with(eager_load=True)
958
@operation_returns_collection_of(Interface)
959
@export_read_operation()
960
def getPublishedSources(name=None, version=None, status=None,
961
distroseries=None, pocket=None,
962
exact_match=False, created_since_date=None,
963
eager_load=False, component_name=None):
964
"""All `ISourcePackagePublishingHistory` target to this archive.
966
:param name: source name filter (exact match or SQL LIKE controlled
967
by 'exact_match' argument).
968
Name can be a single string or a list of strings.
969
:param version: source version filter (always exact match).
970
:param status: `PackagePublishingStatus` filter, can be a sequence.
971
:param distroseries: `IDistroSeries` filter.
972
:param pocket: `PackagePublishingPocket` filter. This may be an
973
iterable of more than one pocket or a single pocket.
974
:param exact_match: either or not filter source names by exact
976
:param created_since_date: Only return results whose `date_created`
977
is greater than or equal to this date.
978
:param component_name: component filter. Only return source packages
979
that are in this component.
981
:return: SelectResults containing `ISourcePackagePublishingHistory`,
982
ordered by name. If there are multiple results for the same
983
name then they are sub-ordered newest first.
986
@rename_parameters_as(
987
name="binary_name", distroarchseries="distro_arch_series")
988
@operation_parameters(
989
name=TextLine(title=_("Binary Package Name"), required=False),
990
version=TextLine(title=_("Version"), required=False),
992
title=_("Package Publishing Status"),
993
description=_("The status of this publishing record"),
994
# Really PackagePublishingStatus, circular import fixed below.
995
vocabulary=DBEnumeratedType,
997
distroarchseries=Reference(
998
# Really IDistroArchSeries, circular import fixed below.
1000
title=_("Distro Arch Series"), required=False),
1003
description=_("The pocket into which this entry is published"),
1004
# Really PackagePublishingPocket, circular import fixed below.
1005
vocabulary=DBEnumeratedType,
1006
required=False, readonly=True),
1008
description=_("Whether or not to filter binary names by exact "
1011
created_since_date=Datetime(
1012
title=_("Created Since Date"),
1013
description=_("Return entries whose `date_created` is greater "
1014
"than or equal to this date."),
1018
description=_("Return ordered results by default, but specifying "
1019
"False will return results more quickly."),
1020
required=False, readonly=True),
1022
# Really returns ISourcePackagePublishingHistory, see below for
1023
# patch to avoid circular import.
1024
@operation_returns_collection_of(Interface)
1025
@export_operation_as("getPublishedBinaries")
1026
@export_read_operation()
1027
def getAllPublishedBinaries(name=None, version=None, status=None,
1028
distroarchseries=None, pocket=None,
1029
exact_match=False, created_since_date=None,
1031
"""All `IBinaryPackagePublishingHistory` target to this archive.
1033
:param name: binary name filter (exact match or SQL LIKE controlled
1034
by 'exact_match' argument).
1035
:param version: binary version filter (always exact match).
1036
:param status: `PackagePublishingStatus` filter, can be a list.
1037
:param distroarchseries: `IDistroArchSeries` filter, can be a list.
1038
:param pocket: `PackagePublishingPocket` filter.
1039
:param exact_match: either or not filter source names by exact
1041
:param created_since_date: Only return publications created on or
1043
:param ordered: Normally publications are ordered by binary package
1044
name and then ID order (creation order). If this parameter is
1045
False then the results will be unordered. This will make the
1046
operation much quicker to return results if you don't care about
1049
:return: A collection containing `BinaryPackagePublishingHistory`.
1052
@operation_parameters(
1053
include_needsbuild=Bool(
1054
title=_("Include builds with state NEEDSBUILD"), required=False))
1055
@export_read_operation()
1056
def getBuildCounters(include_needsbuild=True):
1057
"""Return a dictionary containing the build counters for an archive.
1059
This is necessary currently because the IArchive.failed_builds etc.
1060
counters are not in use.
1062
The returned dictionary contains the follwoing keys and values:
1064
* 'total': total number of builds (includes SUPERSEDED);
1065
* 'pending': number of builds in BUILDING or NEEDSBUILD state;
1066
* 'failed': number of builds in FAILEDTOBUILD, MANUALDEPWAIT,
1067
CHROOTWAIT and FAILEDTOUPLOAD state;
1068
* 'succeeded': number of SUCCESSFULLYBUILT builds.
1069
* 'superseded': number of SUPERSEDED builds.
1071
:param include_needsbuild: Indicates whether to include builds with
1072
the status NEEDSBUILD in the pending and total counts. This is
1073
useful in situations where a build that hasn't started isn't
1074
considered a build by the user.
1075
:type include_needsbuild: ``bool``
1076
:return: a dictionary with the 4 keys specified above.
1080
@operation_parameters(
1082
title=_("A list of source publishing history record ids."),
1084
@export_read_operation()
1085
def getBuildSummariesForSourceIds(source_ids):
1086
"""Return a dictionary containing a summary of the build statuses.
1088
Only information for sources belonging to the current archive will
1090
`IPublishingSet`.getBuildStatusSummariesForSourceIdsAndArchive() for
1093
:param source_ids: A list of source publishing history record ids.
1094
:type source_ids: ``list``
1095
:return: A dict consisting of the overall status summaries for the
1096
given ids that belong in the archive.
1099
@operation_parameters(
1100
dependency=Reference(schema=Interface)) # Really IArchive. See below.
1101
@operation_returns_entry(schema=Interface) # Really IArchiveDependency.
1102
@export_read_operation()
1103
def getArchiveDependency(dependency):
1104
"""Return the `IArchiveDependency` object for the given dependency.
1106
:param dependency: is an `IArchive` object.
1108
:return: `IArchiveDependency` or None if a corresponding object
1112
@operation_parameters(person=Reference(schema=IPerson))
1113
# Really IArchivePermission, set below to avoid circular import.
1114
@operation_returns_collection_of(Interface)
1115
@export_read_operation()
1116
def getPermissionsForPerson(person):
1117
"""Return the `IArchivePermission` records applicable to the person.
1119
:param person: An `IPerson`
1120
:return: A list of `IArchivePermission` records.
1123
@operation_parameters(
1124
source_package_name=TextLine(
1125
title=_("Source Package Name"), required=True))
1126
# Really IArchivePermission, set below to avoid circular import.
1127
@operation_returns_collection_of(Interface)
1128
@export_read_operation()
1129
def getUploadersForPackage(source_package_name):
1130
"""Return `IArchivePermission` records for the package's uploaders.
1132
:param source_package_name: An `ISourcePackageName` or textual name
1133
for the source package.
1134
:return: A list of `IArchivePermission` records.
1137
@operation_parameters(
1138
component_name=TextLine(title=_("Component Name"), required=False))
1139
# Really IArchivePermission, set below to avoid circular import.
1140
@operation_returns_collection_of(Interface)
1141
@export_read_operation()
1142
def getUploadersForComponent(component_name=None):
1143
"""Return `IArchivePermission` records for the component's uploaders.
1145
:param component_name: An `IComponent` or textual name for the
1147
:return: A list of `IArchivePermission` records.
1150
@operation_parameters(
1151
component_name=TextLine(title=_("Component Name"), required=True))
1152
# Really IArchivePermission, set below to avoid circular import.
1153
@operation_returns_collection_of(Interface)
1154
@export_read_operation()
1155
def getQueueAdminsForComponent(component_name):
1156
"""Return `IArchivePermission` records for authorised queue admins.
1158
:param component_name: An `IComponent` or textual name for the
1160
:return: A list of `IArchivePermission` records.
1163
@operation_parameters(person=Reference(schema=IPerson))
1164
# Really IArchivePermission, set below to avoid circular import.
1165
@operation_returns_collection_of(Interface)
1166
@export_read_operation()
1167
def getComponentsForQueueAdmin(person):
1168
"""Return `IArchivePermission` for the person's queue admin
1171
:param person: An `IPerson`.
1172
:return: A list of `IArchivePermission` records.
1175
def hasAnyPermission(person):
1176
"""Whether or not this person has any permission at all on this
1179
:param person: The `IPerson` for whom the check is performed.
1180
:return: A boolean indicating if the person has any permission on this
1184
def getPackageDownloadCount(bpr, day, country):
1185
"""Get the `IBinaryPackageDownloadCount` with the given key."""
1187
def getFilesAndSha1s(source_files):
1188
"""Return a dictionary with the filenames and the SHA1s for each
1191
:param source_files: A list of filenames to return SHA1s of
1192
:return: A dictionary of filenames and SHA1s.
1195
def getAuthToken(person):
1196
"""Returns an IArchiveAuthToken for the archive in question for
1199
:return: A IArchiveAuthToken, or None if the user has none.
1202
def newAuthToken(person, token=None, date_created=None):
1203
"""Create a new authorisation token.
1205
:param person: An IPerson whom this token is for
1206
:param token: Optional unicode text to use as the token. One will be
1207
generated if not given
1208
:param date_created: Optional, defaults to now
1210
:return: A new IArchiveAuthToken
1213
@call_with(person=REQUEST_USER)
1214
@operation_parameters(
1215
source_name=TextLine(title=_("Source package name")),
1216
version=TextLine(title=_("Version")),
1217
from_archive=Reference(schema=Interface),
1218
# Really IArchive, see below
1219
to_pocket=TextLine(title=_("Pocket name")),
1220
to_series=TextLine(title=_("Distroseries name"), required=False),
1221
include_binaries=Bool(
1222
title=_("Include Binaries"),
1223
description=_("Whether or not to copy binaries already built for"
1226
sponsored=Reference(
1228
title=_("Sponsored Person"),
1229
description=_("The person who is being sponsored for this copy."))
1231
@export_write_operation()
1232
@operation_for_version('devel')
1233
def copyPackage(source_name, version, from_archive, to_pocket,
1234
person, to_series=None, include_binaries=False,
1236
"""Copy a single named source into this archive.
1238
Asynchronously copy a specific version of a named source to the
1239
destination archive if necessary. Calls to this method will return
1240
immediately if the copy passes basic security checks and the copy
1241
will happen sometime later with full checking.
1243
:param source_name: a string name of the package to copy.
1244
:param version: the version of the package to copy.
1245
:param from_archive: the source archive from which to copy.
1246
:param to_pocket: the target pocket (as a string).
1247
:param to_series: the target distroseries (as a string).
1248
:param include_binaries: optional boolean, controls whether or not
1249
the published binaries for each given source should also be
1250
copied along with the source.
1251
:param person: the `IPerson` who requests the sync.
1252
:param sponsored: the `IPerson` who is being sponsored. Specifying
1253
this will ensure that the person's email address is used as the
1254
"From:" on the announcement email and will also be recorded as
1255
the creator of the new source publication.
1257
:raises NoSuchSourcePackageName: if the source name is invalid
1258
:raises PocketNotFound: if the pocket name is invalid
1259
:raises NoSuchDistroSeries: if the distro series name is invalid
1260
:raises CannotCopy: if there is a problem copying.
1263
@call_with(person=REQUEST_USER)
1264
@operation_parameters(
1266
title=_("Source package names"),
1267
value_type=TextLine()),
1268
from_archive=Reference(schema=Interface),
1269
#Really IArchive, see below
1270
to_pocket=TextLine(title=_("Pocket name")),
1271
to_series=TextLine(title=_("Distroseries name"), required=False),
1272
include_binaries=Bool(
1273
title=_("Include Binaries"),
1274
description=_("Whether or not to copy binaries already built for"
1277
sponsored=Reference(
1279
title=_("Sponsored Person"),
1280
description=_("The person who is being sponsored for this copy."))
1282
@export_write_operation()
1283
@operation_for_version('devel')
1284
def copyPackages(source_names, from_archive, to_pocket, person,
1285
to_series=None, include_binaries=False,
1287
"""Copy multiple named sources into this archive from another.
1289
Asynchronously copy the most recent PUBLISHED versions of the named
1290
sources to the destination archive if necessary. Calls to this
1291
method will return immediately if the copy passes basic security
1292
checks and the copy will happen sometime later with full checking.
1294
Partial changes of the destination archive can happen because each
1295
source is copied in its own transaction.
1297
:param source_names: a list of string names of packages to copy.
1298
:param from_archive: the source archive from which to copy.
1299
:param to_pocket: the target pocket (as a string).
1300
:param to_series: the target distroseries (as a string).
1301
:param include_binaries: optional boolean, controls whether or not
1302
the published binaries for each given source should also be
1303
copied along with the source.
1304
:param person: the `IPerson` who requests the sync.
1305
:param sponsored: the `IPerson` who is being sponsored. Specifying
1306
this will ensure that the person's email address is used as the
1307
"From:" on the announcement email and will also be recorded as
1308
the creator of the new source publication.
1310
:raises NoSuchSourcePackageName: if the source name is invalid
1311
:raises PocketNotFound: if the pocket name is invalid
1312
:raises NoSuchDistroSeries: if the distro series name is invalid
1313
:raises CannotCopy: if there is a problem copying.
1317
class IArchiveAppend(Interface):
1318
"""Archive interface for operations restricted by append privilege."""
1320
@call_with(person=REQUEST_USER)
1321
@operation_parameters(
1323
title=_("Source package names"),
1324
value_type=TextLine()),
1325
from_archive=Reference(schema=Interface),
1326
#Really IArchive, see below
1327
to_pocket=TextLine(title=_("Pocket name")),
1328
to_series=TextLine(title=_("Distroseries name"), required=False),
1329
include_binaries=Bool(
1330
title=_("Include Binaries"),
1331
description=_("Whether or not to copy binaries already built for"
1334
@export_write_operation()
1335
# Source_names is a string because exporting a SourcePackageName is
1336
# rather nonsensical as it only has id and name columns.
1337
def syncSources(source_names, from_archive, to_pocket, to_series=None,
1338
include_binaries=False, person=None):
1339
"""Synchronise (copy) named sources into this archive from another.
1341
It will copy the most recent PUBLISHED versions of the named
1342
sources to the destination archive if necessary.
1344
This operation will only succeeds when all requested packages
1345
are synchronised between the archives. If any of the requested
1346
copies cannot be performed, the whole operation will fail. There
1347
will be no partial changes of the destination archive.
1349
:param source_names: a list of string names of packages to copy.
1350
:param from_archive: the source archive from which to copy.
1351
:param to_pocket: the target pocket (as a string).
1352
:param to_series: the target distroseries (as a string).
1353
:param include_binaries: optional boolean, controls whether or not
1354
the published binaries for each given source should also be
1355
copied along with the source.
1356
:param person: the `IPerson` who requests the sync.
1358
:raises NoSuchSourcePackageName: if the source name is invalid
1359
:raises PocketNotFound: if the pocket name is invalid
1360
:raises NoSuchDistroSeries: if the distro series name is invalid
1361
:raises CannotCopy: if there is a problem copying.
1364
@call_with(person=REQUEST_USER)
1365
@operation_parameters(
1366
source_name=TextLine(title=_("Source package name")),
1367
version=TextLine(title=_("Version")),
1368
from_archive=Reference(schema=Interface),
1369
# Really IArchive, see below
1370
to_pocket=TextLine(title=_("Pocket name")),
1371
to_series=TextLine(title=_("Distroseries name"), required=False),
1372
include_binaries=Bool(
1373
title=_("Include Binaries"),
1374
description=_("Whether or not to copy binaries already built for"
1377
@export_write_operation()
1378
# XXX Julian 2008-11-05
1379
# This method takes source_name and version as strings because
1380
# SourcePackageRelease is not exported on the API yet. When it is,
1381
# we should consider either changing this method or adding a new one
1382
# that takes that object instead.
1383
def syncSource(source_name, version, from_archive, to_pocket,
1384
to_series=None, include_binaries=False, person=None):
1385
"""Synchronise (copy) a single named source into this archive.
1387
Copy a specific version of a named source to the destination
1388
archive if necessary.
1390
:param source_name: a string name of the package to copy.
1391
:param version: the version of the package to copy.
1392
:param from_archive: the source archive from which to copy.
1393
:param to_pocket: the target pocket (as a string).
1394
:param to_series: the target distroseries (as a string).
1395
:param include_binaries: optional boolean, controls whether or not
1396
the published binaries for each given source should also be
1397
copied along with the source.
1398
:param person: the `IPerson` who requests the sync.
1400
:raises NoSuchSourcePackageName: if the source name is invalid
1401
:raises PocketNotFound: if the pocket name is invalid
1402
:raises NoSuchDistroSeries: if the distro series name is invalid
1403
:raises CannotCopy: if there is a problem copying.
1406
@call_with(registrant=REQUEST_USER)
1407
@operation_parameters(
1408
subscriber=PublicPersonChoice(
1409
title=_("Subscriber"),
1411
vocabulary='ValidPersonOrTeam',
1412
description=_("The person who is subscribed.")),
1413
date_expires=Datetime(title=_("Date of Expiration"), required=False,
1414
description=_("The timestamp when the subscription will "
1416
description=Text(title=_("Description"), required=False,
1417
description=_("Free text describing this subscription.")))
1418
# Really IArchiveSubscriber, set below to avoid circular import.
1419
@export_factory_operation(Interface, [])
1420
def newSubscription(subscriber, registrant, date_expires=None,
1422
"""Create a new subscribtion to this archive.
1424
Create an `ArchiveSubscriber` record which allows an `IPerson` to
1425
access a private repository.
1427
:param subscriber: An `IPerson` who is allowed to access the
1428
repository for this archive.
1429
:param registrant: An `IPerson` who created this subscription.
1430
:param date_expires: When the subscription should expire; None if
1431
it should not expire (default).
1432
:param description: An option textual description of the subscription
1435
:return: The `IArchiveSubscriber` that was created.
1439
class IArchiveEdit(Interface):
1440
"""Archive interface for operations restricted by edit privilege."""
1442
@operation_parameters(
1443
person=Reference(schema=IPerson),
1444
source_package_name=TextLine(
1445
title=_("Source Package Name"), required=True))
1446
# Really IArchivePermission, set below to avoid circular import.
1447
@export_factory_operation(Interface, [])
1448
def newPackageUploader(person, source_package_name):
1449
"""Add permisson for a person to upload a package to this archive.
1451
:param person: An `IPerson` whom should be given permission.
1452
:param source_package_name: An `ISourcePackageName` or textual package
1454
:return: An `IArchivePermission` which is the newly-created
1458
@operation_parameters(
1459
person=Reference(schema=IPerson),
1460
component_name=TextLine(
1461
title=_("Component Name"), required=True))
1462
# Really IArchivePermission, set below to avoid circular import.
1463
@export_factory_operation(Interface, [])
1464
def newComponentUploader(person, component_name):
1465
"""Add permission for a person to upload to a component.
1467
:param person: An `IPerson` whom should be given permission.
1468
:param component: An `IComponent` or textual component name.
1469
:return: An `IArchivePermission` which is the newly-created
1471
:raises InvalidComponent: if this archive is a PPA and the component
1475
@operation_parameters(
1476
person=Reference(schema=IPerson),
1477
component_name=TextLine(
1478
title=_("Component Name"), required=True))
1479
# Really IArchivePermission, set below to avoid circular import.
1480
@export_factory_operation(Interface, [])
1481
def newQueueAdmin(person, component_name):
1482
"""Add permission for a person to administer a distroseries queue.
1484
The supplied person will gain permission to administer the
1485
distroseries queue for packages in the supplied component.
1487
:param person: An `IPerson` whom should be given permission.
1488
:param component: An `IComponent` or textual component name.
1489
:return: An `IArchivePermission` which is the newly-created
1493
@operation_parameters(
1494
person=Reference(schema=IPerson),
1495
# Really IPackageset, corrected in _schema_circular_imports to avoid
1497
packageset=Reference(
1498
Interface, title=_("Package set"), required=True),
1500
title=_("Explicit"), required=False))
1501
# Really IArchivePermission, set in _schema_circular_imports to avoid
1503
@export_factory_operation(Interface, [])
1504
def newPackagesetUploader(person, packageset, explicit=False):
1505
"""Add a package set based permission for a person.
1507
:param person: An `IPerson` for whom you want to add permission.
1508
:param packageset: An `IPackageset`.
1509
:param explicit: True if the package set in question requires
1510
specialist skills for proper handling.
1512
:return: The new `ArchivePermission`, or the existing one if it
1516
@operation_parameters(
1517
person=Reference(schema=IPerson),
1518
source_package_name=TextLine(
1519
title=_("Source Package Name"), required=True))
1520
@export_write_operation()
1521
def deletePackageUploader(person, source_package_name):
1522
"""Revoke permission for the person to upload the package.
1524
:param person: An `IPerson` whose permission should be revoked.
1525
:param source_package_name: An `ISourcePackageName` or textual package
1529
@operation_parameters(
1530
person=Reference(schema=IPerson),
1531
component_name=TextLine(
1532
title=_("Component Name"), required=True))
1533
@export_write_operation()
1534
def deleteComponentUploader(person, component_name):
1535
"""Revoke permission for the person to upload to the component.
1537
:param person: An `IPerson` whose permission should be revoked.
1538
:param component: An `IComponent` or textual component name.
1541
@operation_parameters(
1542
person=Reference(schema=IPerson),
1543
component_name=TextLine(
1544
title=_("Component Name"), required=True))
1545
@export_write_operation()
1546
def deleteQueueAdmin(person, component_name):
1547
"""Revoke permission for the person to administer distroseries queues.
1549
The supplied person will lose permission to administer the
1550
distroseries queue for packages in the supplied component.
1552
:param person: An `IPerson` whose permission should be revoked.
1553
:param component: An `IComponent` or textual component name.
1556
@operation_parameters(
1557
person=Reference(schema=IPerson),
1558
# Really IPackageset, corrected in _schema_circular_imports to avoid
1560
packageset=Reference(
1561
Interface, title=_("Package set"), required=True),
1563
title=_("Explicit"), required=False))
1564
@export_write_operation()
1565
def deletePackagesetUploader(person, packageset, explicit=False):
1566
"""Revoke upload permissions for a person.
1568
:param person: An `IPerson` for whom you want to revoke permission.
1569
:param packageset: An `IPackageset`.
1570
:param explicit: The value of the 'explicit' flag for the permission
1575
"""Enable the archive."""
1578
"""Disable the archive."""
1580
def delete(deleted_by):
1581
"""Delete this archive.
1583
:param deleted_by: The `IPerson` requesting the deletion.
1585
The ArchiveStatus will be set to DELETING and any published
1586
packages will be marked as DELETED by deleted_by.
1588
The publisher is responsible for deleting the repository area
1589
when it sees the status change and sets it to DELETED once
1593
def addArchiveDependency(dependency, pocket, component=None):
1594
"""Record an archive dependency record for the context archive.
1596
:param dependency: is an `IArchive` object.
1597
:param pocket: is an `PackagePublishingPocket` enum.
1598
:param component: is an optional `IComponent` object, if not given
1599
the archive dependency will be tied to the component used
1600
for a corresponding source in primary archive.
1602
:raise: `ArchiveDependencyError` if given 'dependency' does not fit
1603
the context archive.
1604
:return: a `IArchiveDependency` object targeted to the context
1605
`IArchive` requiring 'dependency' `IArchive`.
1608
@operation_parameters(
1609
dependency=Reference(schema=Interface, required=True),
1613
description=_("The pocket into which this entry is published"),
1614
# Really PackagePublishingPocket.
1615
vocabulary=DBEnumeratedType,
1617
component=TextLine(title=_("Component"), required=False),
1619
@export_operation_as('addArchiveDependency')
1620
@export_factory_operation(Interface, []) # Really IArchiveDependency
1621
@operation_for_version('devel')
1622
def _addArchiveDependency(dependency, pocket, component=None):
1623
"""Record an archive dependency record for the context archive.
1625
:param dependency: is an `IArchive` object.
1626
:param pocket: is an `PackagePublishingPocket` enum.
1627
:param component: is the name of a component. If not given,
1628
the archive dependency will be tied to the component used
1629
for a corresponding source in primary archive.
1631
:raise: `ArchiveDependencyError` if given 'dependency' does not fit
1632
the context archive.
1633
:return: a `IArchiveDependency` object targeted to the context
1634
`IArchive` requiring 'dependency' `IArchive`.
1636
@operation_parameters(
1637
dependency=Reference(schema=Interface, required=True),
1640
@export_write_operation()
1641
@operation_for_version('devel')
1642
def removeArchiveDependency(dependency):
1643
"""Remove the `IArchiveDependency` record for the given dependency.
1645
:param dependency: is an `IArchive` object.
1649
class IArchiveCommercial(Interface):
1650
"""Archive interface for operations restricted by commercial."""
1652
@operation_parameters(
1653
family=Reference(schema=Interface, required=True),
1654
# Really IProcessorFamily.
1656
@export_write_operation()
1657
@operation_for_version('devel')
1658
def enableRestrictedFamily(family):
1659
"""Add the processor family to the set of enabled restricted families.
1661
:param family: is an `IProcessorFamily` object.
1665
class IArchive(IArchivePublic, IArchiveAppend, IArchiveEdit, IArchiveView,
1666
IArchiveCommercial):
1667
"""Main Archive interface."""
1668
export_as_webservice_entry()
1671
class IPPA(IArchive):
1672
"""Marker interface so traversal works differently for PPAs."""
1675
class IDistributionArchive(IArchive):
1676
"""Marker interface so traversal works differently for distro archives."""
1679
class IArchiveEditDependenciesForm(Interface):
1680
"""Schema used to edit dependencies settings within a archive."""
1682
dependency_candidate = Choice(
1683
title=_('Add PPA dependency'), required=False, vocabulary='PPA')
1686
class IArchiveSet(Interface):
1687
"""Interface for ArchiveSet"""
1689
title = Attribute('Title')
1691
def getNumberOfPPASourcesForDistribution(distribution):
1692
"""Return the number of sources for PPAs in a given distribution.
1694
Only public and published sources are considered.
1697
def getNumberOfPPABinariesForDistribution(distribution):
1698
"""Return the number of binaries for PPAs in a given distribution.
1700
Only public and published sources are considered.
1703
def new(purpose, owner, name=None, displayname=None, distribution=None,
1704
description=None, enabled=True, require_virtualized=True,
1706
"""Create a new archive.
1708
On named-ppa creation, the signing key for the default PPA for the
1709
given owner will be used if it is present.
1711
:param purpose: `ArchivePurpose`;
1712
:param owner: `IPerson` owning the Archive;
1713
:param name: optional text to be used as the archive name, if not
1714
given it uses the names defined in
1715
`IArchiveSet._getDefaultArchiveNameForPurpose`;
1716
:param displayname: optional text that will be used as a reference
1717
to this archive in the UI. If not provided a default text
1718
(including the archive name and the owner displayname) will be
1720
:param distribution: optional `IDistribution` to which the archive
1722
:param description: optional text to be set as the archive
1724
:param enabled: whether the archive shall be enabled post creation
1725
:param require_virtualized: whether builds for the new archive shall
1726
be carried out on virtual builders
1727
:param private: whether or not to make the PPA private
1729
:return: an `IArchive` object.
1730
:raises AssertionError if name is already taken within distribution.
1733
def get(archive_id):
1734
"""Return the IArchive with the given archive_id."""
1736
def getPPAByDistributionAndOwnerName(distribution, person_name, ppa_name):
1737
"""Return a single PPA.
1739
:param distribution: The context IDistribution.
1740
:param person_name: The context IPerson.
1741
:param ppa_name: The name of the archive (PPA)
1744
def getByDistroPurpose(distribution, purpose, name=None):
1745
"""Return the IArchive with the given distribution and purpose.
1747
It uses the default names defined in
1748
`IArchiveSet._getDefaultArchiveNameForPurpose`.
1750
:raises AssertionError if used for with ArchivePurpose.PPA.
1753
def getByDistroAndName(distribution, name):
1754
"""Return the `IArchive` with the given distribution and name."""
1757
"""Iterates over existent archives, including the main_archives."""
1759
def getPPAOwnedByPerson(person, name=None, statuses=None,
1760
has_packages=False):
1761
"""Return the named PPA owned by person.
1763
:param person: An `IPerson`. Required.
1764
:param name: The PPA name. Optional.
1765
:param statuses: A list of statuses the PPAs must match. Optional.
1766
:param has_packages: If True will only select PPAs that have published
1769
If the name is not supplied it will default to the
1770
first PPA that the person created.
1772
:raises NoSuchPPA: if the named PPA does not exist.
1775
def getPPAsForUser(user):
1776
"""Return all PPAs the given user can participate.
1778
The result is ordered by PPA displayname.
1781
def getPPAsPendingSigningKey():
1782
"""Return all PPAs pending signing key generation.
1784
The result is ordered by archive creation date.
1787
def getLatestPPASourcePublicationsForDistribution(distribution):
1788
"""The latest 5 PPA source publications for a given distribution.
1790
Private PPAs are excluded from the result.
1793
def getMostActivePPAsForDistribution(distribution):
1794
"""Return the 5 most active PPAs.
1796
The activity is currently measured by number of uploaded (published)
1797
sources for each PPA during the last 7 days.
1799
Private PPAs are excluded from the result.
1801
:return A list with up to 5 dictionaries containing the ppa 'title'
1802
and the number of 'uploads' keys and corresponding values.
1805
def getBuildCountersForArchitecture(archive, distroarchseries):
1806
"""Return a dictionary containing the build counters per status.
1808
The result is restricted to the given archive and distroarchseries.
1810
The returned dictionary contains the follwoing keys and values:
1812
* 'total': total number of builds (includes SUPERSEDED);
1813
* 'pending': number of builds in NEEDSBUILD or BUILDING state;
1814
* 'failed': number of builds in FAILEDTOBUILD, MANUALDEPWAIT,
1815
CHROOTWAIT and FAILEDTOUPLOAD state;
1816
* 'succeeded': number of SUCCESSFULLYBUILT builds.
1818
:param archive: target `IArchive`;
1819
:param distroarchseries: target `IDistroArchSeries`.
1821
:return a dictionary with the 4 keys specified above.
1824
def getArchivesForDistribution(distribution, name=None, purposes=None,
1825
user=None, exclude_disabled=True):
1826
"""Return a list of all the archives for a distribution.
1828
This will return all the archives for the given distribution, with
1829
the following parameters:
1831
:param distribution: target `IDistribution`
1832
:param name: An optional archive name which will further restrict
1833
the results to only those archives with this name.
1834
:param purposes: An optional archive purpose or list of purposes with
1835
which to filter the results.
1836
:param user: An optional `IPerson` who is requesting the archives,
1837
which is used to include private archives for which the user
1838
has permission. If it is not supplied, only public archives
1840
:param exclude_disabled: Whether to exclude disabled archives.
1842
:return: A queryset of all the archives for the given
1843
distribution matching the given params.
1846
def getPrivatePPAs():
1847
"""Return a result set containing all private PPAs."""
1849
def getCommercialPPAs():
1850
"""Return a result set containing all commercial PPAs.
1852
Commercial PPAs are private, but explicitly flagged up as commercial
1853
so that they are discoverable by people who wish to buy items
1857
def getPublicationsInArchives(source_package_name, archive_list,
1859
"""Return a result set of publishing records for the source package.
1861
:param source_package_name: an `ISourcePackageName` identifying the
1862
source package for which the publishings will be returned.
1863
:param archive_list: a list of at least one archive with which to
1864
restrict the search.
1865
:param distribution: the distribution by which the results will
1867
:return: a resultset of the `ISourcePackagePublishingHistory` objects
1868
that are currently published in the given archives.
1872
default_name_by_purpose = {
1873
ArchivePurpose.PRIMARY: 'primary',
1874
ArchivePurpose.PPA: 'ppa',
1875
ArchivePurpose.PARTNER: 'partner',
1876
ArchivePurpose.DEBUG: 'debug',
1880
MAIN_ARCHIVE_PURPOSES = (
1881
ArchivePurpose.PRIMARY,
1882
ArchivePurpose.PARTNER,
1883
ArchivePurpose.DEBUG,
1886
ALLOW_RELEASE_BUILDS = (
1887
ArchivePurpose.PARTNER,
1889
ArchivePurpose.COPY,
1892
FULL_COMPONENT_SUPPORT = (
1893
ArchivePurpose.PRIMARY,
1894
ArchivePurpose.DEBUG,
1895
ArchivePurpose.COPY,
1898
# Circular dependency issues fixed in _schema_circular_imports.py
1901
def validate_external_dependencies(ext_deps):
1902
"""Validate the external_dependencies field.
1904
:param ext_deps: The dependencies form field to check.
1907
# The field can consist of multiple entries separated by
1908
# newlines, so process each in turn.
1909
for dep in ext_deps.splitlines():
1911
deb, url, suite, components = dep.split(" ", 3)
1914
"'%s' is not a complete and valid sources.list entry"
1919
errors.append("%s: Must start with 'deb'" % dep)
1920
url_components = urlparse(url)
1921
if not url_components[0] or not url_components[1]:
1922
errors.append("%s: Invalid URL" % dep)