~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/soyuz/interfaces/archive.py

[r=sinzui][bug=855670] Add additional checks to the private team
        launchpad.LimitedView security adaptor so more users in defined
        roles can see the team.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
# pylint: disable-msg=E0211,E0213
 
5
 
 
6
"""Archive interfaces."""
 
7
 
 
8
__metaclass__ = type
 
9
 
 
10
__all__ = [
 
11
    'ALLOW_RELEASE_BUILDS',
 
12
    'AlreadySubscribed',
 
13
    'ArchiveDependencyError',
 
14
    'ArchiveDisabled',
 
15
    'ArchiveNotPrivate',
 
16
    'CannotCopy',
 
17
    'CannotSwitchPrivacy',
 
18
    'ComponentNotFound',
 
19
    'CannotRestrictArchitectures',
 
20
    'CannotUploadToArchive',
 
21
    'CannotUploadToPPA',
 
22
    'CannotUploadToPocket',
 
23
    'ForbiddenByFeatureFlag',
 
24
    'FULL_COMPONENT_SUPPORT',
 
25
    'IArchive',
 
26
    'IArchiveAppend',
 
27
    'IArchiveCommercial',
 
28
    'IArchiveEdit',
 
29
    'IArchiveView',
 
30
    'IArchiveEditDependenciesForm',
 
31
    'IArchivePublic',
 
32
    'IArchiveSet',
 
33
    'IDistributionArchive',
 
34
    'InsufficientUploadRights',
 
35
    'InvalidComponent',
 
36
    'InvalidExternalDependencies',
 
37
    'InvalidPocketForPartnerArchive',
 
38
    'InvalidPocketForPPA',
 
39
    'IPPA',
 
40
    'MAIN_ARCHIVE_PURPOSES',
 
41
    'NoRightsForArchive',
 
42
    'NoRightsForComponent',
 
43
    'NoSuchPPA',
 
44
    'NoTokensForTeams',
 
45
    'PocketNotFound',
 
46
    'VersionRequiresName',
 
47
    'default_name_by_purpose',
 
48
    'validate_external_dependencies',
 
49
    ]
 
50
 
 
51
import httplib
 
52
from urlparse import urlparse
 
53
 
 
54
from lazr.enum import DBEnumeratedType
 
55
from lazr.restful.declarations import (
 
56
    call_with,
 
57
    error_status,
 
58
    export_as_webservice_entry,
 
59
    export_factory_operation,
 
60
    export_operation_as,
 
61
    export_read_operation,
 
62
    export_write_operation,
 
63
    exported,
 
64
    operation_for_version,
 
65
    operation_parameters,
 
66
    operation_returns_collection_of,
 
67
    operation_returns_entry,
 
68
    rename_parameters_as,
 
69
    REQUEST_USER,
 
70
    )
 
71
from lazr.restful.fields import (
 
72
    CollectionField,
 
73
    Reference,
 
74
    )
 
75
from zope.interface import (
 
76
    Attribute,
 
77
    Interface,
 
78
    )
 
79
from zope.schema import (
 
80
    Bool,
 
81
    Choice,
 
82
    Datetime,
 
83
    Int,
 
84
    List,
 
85
    Object,
 
86
    Text,
 
87
    TextLine,
 
88
    )
 
89
 
 
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 (
 
98
    PersonChoice,
 
99
    PublicPersonChoice,
 
100
    StrippedTextLine,
 
101
    )
 
102
from lp.soyuz.enums import ArchivePurpose
 
103
from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
 
104
from lp.soyuz.interfaces.component import IComponent
 
105
 
 
106
 
 
107
@error_status(httplib.BAD_REQUEST)
 
108
class ArchiveDependencyError(Exception):
 
109
    """Raised when an `IArchiveDependency` does not fit the context archive.
 
110
 
 
111
    A given dependency is considered inappropriate when:
 
112
 
 
113
     * It is the archive itself,
 
114
     * It is not a PPA,
 
115
     * It is already recorded.
 
116
    """
 
117
 
 
118
 
 
119
# Exceptions used in the webservice that need to be in this file to get
 
120
# picked up therein.
 
121
@error_status(httplib.BAD_REQUEST)
 
122
class CannotCopy(Exception):
 
123
    """Exception raised when a copy cannot be performed."""
 
124
 
 
125
 
 
126
@error_status(httplib.FORBIDDEN)
 
127
class ForbiddenByFeatureFlag(Exception):
 
128
    """Exception raised when using a method protected by a feature flag.
 
129
    """
 
130
 
 
131
 
 
132
@error_status(httplib.BAD_REQUEST)
 
133
class CannotSwitchPrivacy(Exception):
 
134
    """Raised when switching the privacy of an archive that has
 
135
    publishing records."""
 
136
 
 
137
 
 
138
class PocketNotFound(NameLookupFailed):
 
139
    """Invalid pocket."""
 
140
    _message_prefix = "No such pocket"
 
141
 
 
142
 
 
143
@error_status(httplib.BAD_REQUEST)
 
144
class AlreadySubscribed(Exception):
 
145
    """Raised when creating a subscription for a subscribed person."""
 
146
 
 
147
 
 
148
@error_status(httplib.BAD_REQUEST)
 
149
class ArchiveNotPrivate(Exception):
 
150
    """Raised when creating an archive subscription for a public archive."""
 
151
 
 
152
 
 
153
@error_status(httplib.BAD_REQUEST)
 
154
class NoTokensForTeams(Exception):
 
155
    """Raised when creating a token for a team, rather than a person."""
 
156
 
 
157
 
 
158
class ComponentNotFound(NameLookupFailed):
 
159
    """Invalid source name."""
 
160
    _message_prefix = 'No such component'
 
161
 
 
162
 
 
163
@error_status(httplib.BAD_REQUEST)
 
164
class InvalidComponent(Exception):
 
165
    """Invalid component name."""
 
166
 
 
167
 
 
168
class NoSuchPPA(NameLookupFailed):
 
169
    """Raised when we try to look up an PPA that doesn't exist."""
 
170
    _message_prefix = "No such ppa"
 
171
 
 
172
 
 
173
@error_status(httplib.BAD_REQUEST)
 
174
class VersionRequiresName(Exception):
 
175
    """Raised on some queries when version is specified but name is not."""
 
176
 
 
177
 
 
178
class CannotRestrictArchitectures(Exception):
 
179
    """The architectures for this archive can not be restricted."""
 
180
 
 
181
 
 
182
@error_status(httplib.FORBIDDEN)
 
183
class CannotUploadToArchive(Exception):
 
184
    """A reason for not being able to upload to an archive."""
 
185
 
 
186
    _fmt = '%(person)s has no upload rights to %(archive)s.'
 
187
 
 
188
    def __init__(self, **args):
 
189
        """Construct a `CannotUploadToArchive`."""
 
190
        Exception.__init__(self, self._fmt % args)
 
191
 
 
192
 
 
193
class InvalidPocketForPartnerArchive(CannotUploadToArchive):
 
194
    """Partner archives only support some pockets."""
 
195
 
 
196
    _fmt = "Partner uploads must be for the RELEASE or PROPOSED pocket."
 
197
 
 
198
 
 
199
@error_status(httplib.FORBIDDEN)
 
200
class CannotUploadToPocket(Exception):
 
201
    """Returned when a pocket is closed for uploads."""
 
202
 
 
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))
 
207
 
 
208
 
 
209
class CannotUploadToPPA(CannotUploadToArchive):
 
210
    """Raised when a person cannot upload to a PPA."""
 
211
 
 
212
    _fmt = 'Signer has no upload rights to this PPA.'
 
213
 
 
214
 
 
215
class NoRightsForArchive(CannotUploadToArchive):
 
216
    """Raised when a person has absolutely no upload rights to an archive."""
 
217
 
 
218
    _fmt = (
 
219
        "The signer of this package has no upload rights to this "
 
220
        "distribution's primary archive.  Did you mean to upload to "
 
221
        "a PPA?")
 
222
 
 
223
 
 
224
class InsufficientUploadRights(CannotUploadToArchive):
 
225
    """Raised when a person has insufficient upload rights."""
 
226
    _fmt = (
 
227
        "The signer of this package is lacking the upload rights for "
 
228
        "the source package, component or package set in question.")
 
229
 
 
230
 
 
231
class NoRightsForComponent(CannotUploadToArchive):
 
232
    """Raised when a person tries to upload to a component without permission.
 
233
    """
 
234
 
 
235
    _fmt = (
 
236
        "Signer is not permitted to upload to the component '%(component)s'.")
 
237
 
 
238
    def __init__(self, component):
 
239
        CannotUploadToArchive.__init__(self, component=component.name)
 
240
 
 
241
 
 
242
class InvalidPocketForPPA(CannotUploadToArchive):
 
243
    """PPAs only support some pockets."""
 
244
 
 
245
    _fmt = "PPA uploads must be for the RELEASE pocket."
 
246
 
 
247
 
 
248
class ArchiveDisabled(CannotUploadToArchive):
 
249
    """Uploading to a disabled archive is not allowed."""
 
250
 
 
251
    _fmt = ("%(archive_name)s is disabled.")
 
252
 
 
253
    def __init__(self, archive_name):
 
254
        CannotUploadToArchive.__init__(self, archive_name=archive_name)
 
255
 
 
256
 
 
257
@error_status(httplib.BAD_REQUEST)
 
258
class InvalidExternalDependencies(Exception):
 
259
    """Tried to set external dependencies to an invalid value."""
 
260
 
 
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)
 
264
        self.errors = errors
 
265
 
 
266
 
 
267
class IArchivePublic(IHasOwner, IPrivacy):
 
268
    """An Archive interface for publicly available operations."""
 
269
    id = Attribute("The archive ID.")
 
270
 
 
271
    owner = exported(
 
272
        PersonChoice(
 
273
            title=_('Owner'), required=True, vocabulary='ValidOwner',
 
274
            description=_("""The archive owner.""")))
 
275
 
 
276
    name = exported(
 
277
        TextLine(
 
278
            title=_("Name"), required=True,
 
279
            constraint=name_validator,
 
280
            description=_(
 
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.")))
 
284
 
 
285
    displayname = exported(
 
286
        StrippedTextLine(
 
287
            title=_("Display name"), required=True,
 
288
            description=_("A short title for the archive.")))
 
289
 
 
290
    title = TextLine(title=_("Name"), required=False, readonly=True)
 
291
 
 
292
    enabled = Bool(
 
293
        title=_("Enabled"), required=False,
 
294
        description=_(
 
295
            "Accept and build packages uploaded to the archive."))
 
296
 
 
297
    publish = Bool(
 
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."))
 
302
 
 
303
    # This is redefined from IPrivacy.private because the attribute is
 
304
    # read-only. The value is guarded by a validator.
 
305
    private = exported(
 
306
        Bool(
 
307
            title=_("Private"), required=False,
 
308
            description=_(
 
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.")))
 
312
 
 
313
    require_virtualized = exported(
 
314
        Bool(
 
315
            title=_("Require virtualized builders"), required=False,
 
316
            readonly=False, description=_(
 
317
                "Only build the archive's packages on virtual builders.")))
 
318
 
 
319
    build_debug_symbols = Bool(
 
320
        title=_("Build debug symbols"), required=False,
 
321
        description=_(
 
322
            "Create debug symbol packages for builds in the archive."))
 
323
 
 
324
    authorized_size = Int(
 
325
        title=_("Authorized size"), required=False,
 
326
        max=2 ** 31 - 1,
 
327
        description=_("Maximum size, in MiB, allowed for the archive."))
 
328
 
 
329
    purpose = Int(
 
330
        title=_("Purpose of archive."), required=True, readonly=True,
 
331
        )
 
332
 
 
333
    status = Int(
 
334
        title=_("Status of archive."), required=True, readonly=True,
 
335
        )
 
336
 
 
337
    sources_cached = Int(
 
338
        title=_("Number of sources cached"), required=False,
 
339
        description=_("Number of source packages cached in this PPA."))
 
340
 
 
341
    binaries_cached = Int(
 
342
        title=_("Number of binaries cached"), required=False,
 
343
        description=_("Number of binary packages cached in this PPA."))
 
344
 
 
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.")
 
348
 
 
349
    distribution = exported(
 
350
        Reference(
 
351
            Interface,  # Redefined to IDistribution later.
 
352
            title=_("The distribution that uses or is used by this "
 
353
                    "archive.")))
 
354
 
 
355
    signing_key = Object(
 
356
        title=_('Repository sigining key.'), required=False, schema=IGPGKey)
 
357
 
 
358
    debug_archive = Attribute(
 
359
        "The archive into which debug binaries should be uploaded.")
 
360
 
 
361
    default_component = Reference(
 
362
        IComponent,
 
363
        title=_(
 
364
            "The default component for this archive. Publications without a "
 
365
            "valid component will be assigned this one."))
 
366
 
 
367
    archive_url = Attribute("External archive URL.")
 
368
 
 
369
    is_ppa = Attribute("True if this archive is a PPA.")
 
370
 
 
371
    is_partner = Attribute("True if this archive is a partner archive.")
 
372
 
 
373
    is_copy = Attribute("True if this archive is a copy archive.")
 
374
 
 
375
    is_main = Bool(
 
376
        title=_("True if archive is a main archive type"), required=False)
 
377
 
 
378
    is_active = Bool(
 
379
        title=_("True if the archive is in the active state"),
 
380
        required=False, readonly=True)
 
381
 
 
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.')
 
393
 
 
394
    total_count = Int(
 
395
        title=_("Total number of builds in archive"), required=True,
 
396
        default=0,
 
397
        description=_("The total number of builds in this archive. "
 
398
                      "This counter does not include discontinued "
 
399
                      "(superseded, cancelled, obsoleted) builds"))
 
400
 
 
401
    pending_count = Int(
 
402
        title=_("Number of pending builds in archive"), required=True,
 
403
        default=0,
 
404
        description=_("The number of pending builds in this archive."))
 
405
 
 
406
    succeeded_count = Int(
 
407
        title=_("Number of successful builds in archive"), required=True,
 
408
        default=0,
 
409
        description=_("The number of successful builds in this archive."))
 
410
 
 
411
    building_count = Int(
 
412
        title=_("Number of active builds in archive"), required=True,
 
413
        default=0,
 
414
        description=_("The number of active builds in this archive."))
 
415
 
 
416
    failed_count = Int(
 
417
        title=_("Number of failed builds in archive"), required=True,
 
418
        default=0,
 
419
        description=_("The number of failed builds in this archive."))
 
420
 
 
421
    date_created = Datetime(
 
422
        title=_('Date created'), required=False, readonly=True,
 
423
        description=_("The time when the archive was created."))
 
424
 
 
425
    relative_build_score = Int(
 
426
        title=_("Relative build score"), required=True, readonly=False,
 
427
        description=_(
 
428
            "A delta to apply to all build scores for the archive. Builds "
 
429
            "with a higher score will build sooner."))
 
430
 
 
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] "
 
438
                "[components]\n"
 
439
            "The series variable is replaced with the series name of the "
 
440
            "context build.\n"
 
441
            "NOTE: This is for migration of OEM PPAs only!")))
 
442
 
 
443
    enabled_restricted_families = exported(
 
444
        CollectionField(
 
445
            title=_("Enabled restricted families"),
 
446
            description=_(
 
447
                "The restricted architecture families on which the archive "
 
448
                "can build."),
 
449
            value_type=Reference(schema=Interface),
 
450
            # Really IProcessorFamily.
 
451
            readonly=True),
 
452
        as_of='devel')
 
453
 
 
454
    commercial = exported(
 
455
        Bool(
 
456
            title=_("Commercial"),
 
457
            required=True,
 
458
            description=_(
 
459
                "Display the archive in Software Center's commercial "
 
460
                "listings. Only private archives can be commercial.")))
 
461
 
 
462
    def getSourcesForDeletion(name=None, status=None, distroseries=None):
 
463
        """All `ISourcePackagePublishingHistory` available for deletion.
 
464
 
 
465
        :param: name: optional source name filter (SQL LIKE)
 
466
        :param: status: `PackagePublishingStatus` filter, can be a sequence.
 
467
        :param: distroseries: `IDistroSeries` filter.
 
468
 
 
469
        :return: SelectResults containing `ISourcePackagePublishingHistory`.
 
470
        """
 
471
 
 
472
    def getPublishedOnDiskBinaries(name=None, version=None, status=None,
 
473
                                   distroarchseries=None, exact_match=False):
 
474
        """Unique `IBinaryPackagePublishingHistory` target to this archive.
 
475
 
 
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.
 
481
 
 
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
 
489
                             matching.
 
490
 
 
491
        :return: SelectResults containing `IBinaryPackagePublishingHistory`.
 
492
        """
 
493
 
 
494
    def allowUpdatesToReleasePocket():
 
495
        """Return whether the archive allows publishing to the release pocket.
 
496
 
 
497
        If a distroseries is stable, normally release pocket publishings are
 
498
        not allowed.  However some archive types allow this.
 
499
 
 
500
        :return: True or False
 
501
        """
 
502
 
 
503
    def getComponentsForSeries(distroseries):
 
504
        """Calculate the components available for use in this archive.
 
505
 
 
506
        :return: An `IResultSet` of `IComponent` objects.
 
507
        """
 
508
 
 
509
    def updateArchiveCache():
 
510
        """Concentrate cached information about the archive contents.
 
511
 
 
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).
 
515
 
 
516
        Updates 'sources_cached' and 'binaries_cached' counters.
 
517
 
 
518
        Also include owner 'name' and 'displayname' to avoid inpecting the
 
519
        Person table indexes while searching.
 
520
        """
 
521
 
 
522
    def findDepCandidates(distro_arch_series, pocket, component,
 
523
                          source_package_name, dep_name):
 
524
        """Return matching binaries in this archive and its dependencies.
 
525
 
 
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.
 
529
 
 
530
        We can't just use the first, since there may be other versions
 
531
        published in other dependency archives.
 
532
 
 
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`
 
539
            records.
 
540
        """
 
541
 
 
542
    def getPermissions(person, item, perm_type):
 
543
        """Get the `IArchivePermission` record with the supplied details.
 
544
 
 
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.
 
549
        """
 
550
 
 
551
    def checkArchivePermission(person, component_or_package=None):
 
552
        """Check to see if person is allowed to upload to component.
 
553
 
 
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.
 
558
 
 
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`.
 
563
 
 
564
        """
 
565
 
 
566
    def canUploadSuiteSourcePackage(person, suitesourcepackage):
 
567
        """Check if 'person' upload 'suitesourcepackage' to 'archive'.
 
568
 
 
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.
 
572
        """
 
573
 
 
574
    def checkUploadToPocket(distroseries, pocket):
 
575
        """Check if an upload to a particular archive and pocket is possible.
 
576
 
 
577
        :param distroseries: A `IDistroSeries`
 
578
        :param pocket: A `PackagePublishingPocket`
 
579
        :return: Reason why uploading is not possible or None
 
580
        """
 
581
 
 
582
    @operation_parameters(
 
583
        person=Reference(schema=IPerson),
 
584
        distroseries=Reference(
 
585
            # Really IDistroSeries, avoiding a circular import here.
 
586
            Interface,
 
587
            title=_("The distro series"), required=True),
 
588
        sourcepackagename=TextLine(
 
589
            title=_("Source package name"), required=True),
 
590
        component=TextLine(
 
591
            title=_("Component"), required=True),
 
592
        pocket=Choice(
 
593
            title=_("Pocket"),
 
594
            description=_("The pocket into which this entry is published"),
 
595
            # Really PackagePublishingPocket, circular import fixed below.
 
596
            vocabulary=DBEnumeratedType,
 
597
            required=True),
 
598
        strict_component=Bool(
 
599
            title=_("Strict component"), required=False),
 
600
        )
 
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."""
 
606
 
 
607
    def checkUpload(person, distroseries, sourcepackagename, component,
 
608
                    pocket, strict_component=True):
 
609
        """Check if 'person' upload 'suitesourcepackage' to 'archive'.
 
610
 
 
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
 
616
            uploaded to.
 
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.
 
621
        """
 
622
 
 
623
    def verifyUpload(person, sourcepackagename, component,
 
624
                      distroseries, strict_component=True):
 
625
        """Can 'person' upload 'sourcepackagename' to this archive ?
 
626
 
 
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
 
630
            the package is new.
 
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
 
638
            archive,
 
639
            None otherwise.
 
640
        """
 
641
 
 
642
    def canAdministerQueue(person, component):
 
643
        """Check to see if person is allowed to administer queue items.
 
644
 
 
645
        :param person: An `IPerson` whom should be checked for authenticate.
 
646
        :param component: The context `IComponent` for the check.
 
647
 
 
648
        :return: True if 'person' is allowed to administer the package upload
 
649
        queue for items with 'component'.
 
650
        """
 
651
 
 
652
    def getFileByName(filename):
 
653
        """Return the corresponding `ILibraryFileAlias` in this context.
 
654
 
 
655
        The following file types (and extension) can be looked up in the
 
656
        archive context:
 
657
 
 
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';
 
662
 
 
663
        :param filename: exactly filename to be looked up.
 
664
 
 
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.
 
668
 
 
669
        :return the corresponding `ILibraryFileAlias` is the file was found.
 
670
        """
 
671
 
 
672
    def getBinaryPackageRelease(name, version, archtag):
 
673
        """Find the specified `IBinaryPackageRelease` in the archive.
 
674
 
 
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.
 
679
 
 
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.
 
682
        """
 
683
 
 
684
    def getBinaryPackageReleaseByFileName(filename):
 
685
        """Return the corresponding `IBinaryPackageRelease` in this context.
 
686
 
 
687
        :param filename: The filename to look up.
 
688
        :return: The `IBinaryPackageRelease` with the specified filename,
 
689
            or None if it was not found.
 
690
        """
 
691
 
 
692
    def requestPackageCopy(target_location, requestor, suite=None,
 
693
                           copy_binaries=False, reason=None):
 
694
        """Return a new `PackageCopyRequest` for this archive.
 
695
 
 
696
        :param target_location: the archive location to which the packages
 
697
            are to be copied.
 
698
        :param requestor: The `IPerson` who is requesting the package copy
 
699
            operation.
 
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
 
704
            as well.
 
705
        :param reason: The reason for this package copy request.
 
706
 
 
707
        :raises NotFoundError: if the provided suite is not found for this
 
708
            archive's distribution.
 
709
 
 
710
        :return The new `IPackageCopyRequest`
 
711
        """
 
712
 
 
713
    @operation_parameters(
 
714
        # Really IPackageset, corrected in _schema_circular_imports to avoid
 
715
        # circular import.
 
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
 
721
    # circular import.
 
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.
 
726
 
 
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.
 
731
 
 
732
        :return: `ArchivePermission` records for all the uploaders who are
 
733
            authorized to upload to the named source package set.
 
734
        """
 
735
 
 
736
    @operation_parameters(
 
737
        person=Reference(schema=IPerson))
 
738
    # Really IArchivePermission, set in _schema_circular_imports to avoid
 
739
    # circular import.
 
740
    @operation_returns_collection_of(Interface)
 
741
    @export_read_operation()
 
742
    def getPackagesetsForUploader(person):
 
743
        """The `ArchivePermission` records for the person's package sets.
 
744
 
 
745
        :param person: An `IPerson` for whom you want to find out which
 
746
            package sets he has access to.
 
747
 
 
748
        :return: `ArchivePermission` records for all the package sets that
 
749
            'person' is allowed to upload to.
 
750
        """
 
751
 
 
752
    def getComponentsForUploader(person):
 
753
        """Return the components that 'person' can upload to this archive.
 
754
 
 
755
        :param person: An `IPerson` wishing to upload to an archive.
 
756
        :return: A `set` of `IComponent`s that 'person' can upload to.
 
757
        """
 
758
 
 
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
 
764
    # circular import.
 
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.
 
769
 
 
770
        Return the `IArchivePermission` records that
 
771
            * apply to this archive
 
772
            * relate to
 
773
                - package sets that include the given source package name
 
774
                - the given `person`
 
775
 
 
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.
 
780
 
 
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
 
785
            person may upload.
 
786
        """
 
787
 
 
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
 
794
    # circular import.
 
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.
 
800
 
 
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
 
804
        principal).
 
805
 
 
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.
 
810
 
 
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
 
815
            archive in question.
 
816
        """
 
817
 
 
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.
 
824
            Interface,
 
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.
 
829
 
 
830
        Return True if there exists a permission that combines
 
831
            * this archive
 
832
            * a package set that includes the given source package name
 
833
            * the given person or a team he is a member of
 
834
 
 
835
        If the source package name is included by *any* package set with
 
836
        an explicit permission then only such explicit permissions will
 
837
        be considered.
 
838
 
 
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
 
845
            Ubuntu is assumed.
 
846
 
 
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.
 
850
        """
 
851
 
 
852
    num_pkgs_building = Attribute(
 
853
        "Tuple of packages building and waiting to build")
 
854
 
 
855
    def getSourcePackageReleases(build_status=None):
 
856
        """Return the releases for this archive.
 
857
 
 
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
 
861
            archive.
 
862
        """
 
863
 
 
864
    def updatePackageDownloadCount(bpr, day, country, count):
 
865
        """Update the daily download count for a given package.
 
866
 
 
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.
 
871
 
 
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.
 
875
        """
 
876
 
 
877
    def getPackageDownloadTotal(bpr):
 
878
        """Get the total download count for a given package."""
 
879
 
 
880
    def validatePPA(person, proposed_name):
 
881
        """Check if a proposed name for a PPA is valid.
 
882
 
 
883
        :param person: A Person identifying the requestor.
 
884
        :param proposed_name: A String identifying the proposed PPA name.
 
885
        """
 
886
 
 
887
    def getPockets():
 
888
        """Return iterable containing valid pocket names for this archive."""
 
889
 
 
890
    def getOverridePolicy():
 
891
        """Returns an instantiated `IOverridePolicy` for the archive."""
 
892
 
 
893
 
 
894
class IArchiveView(IHasBuildRecords):
 
895
    """Archive interface for operations restricted by view privilege."""
 
896
 
 
897
    buildd_secret = TextLine(
 
898
        title=_("Build farm secret"), required=False,
 
899
        description=_(
 
900
            "The password used by the build farm to access the archive."))
 
901
 
 
902
    dependencies = exported(
 
903
        CollectionField(
 
904
            title=_("Archive dependencies recorded for this archive."),
 
905
            value_type=Reference(schema=Interface),
 
906
            # Really IArchiveDependency
 
907
            readonly=True))
 
908
 
 
909
    description = exported(
 
910
        Text(
 
911
            title=_("Description"), required=False,
 
912
            description=_(
 
913
                "A short description of the archive. URLs are allowed and "
 
914
                "will be rendered as links.")))
 
915
 
 
916
    signing_key_fingerprint = exported(
 
917
        Text(
 
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 "
 
921
                          "key available.")))
 
922
 
 
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),
 
927
        status=Choice(
 
928
            title=_('Package Publishing Status'),
 
929
            description=_('The status of this publishing record'),
 
930
            # Really PackagePublishingStatus, circular import fixed below.
 
931
            vocabulary=DBEnumeratedType,
 
932
            required=False),
 
933
        distroseries=Reference(
 
934
            # Really IDistroSeries, fixed below to avoid circular import.
 
935
            Interface,
 
936
            title=_("Distroseries name"), required=False),
 
937
        pocket=Choice(
 
938
            title=_("Pocket"),
 
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),
 
943
        exact_match=Bool(
 
944
            title=_("Exact Match"),
 
945
            description=_("Whether or not to filter source names by exact"
 
946
                          " matching."),
 
947
            required=False),
 
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."),
 
952
            required=False),
 
953
        component_name=TextLine(title=_("Component name"), required=False),
 
954
        )
 
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.
 
965
 
 
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
 
975
                             matching.
 
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.
 
980
 
 
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.
 
984
        """
 
985
 
 
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),
 
991
        status=Choice(
 
992
            title=_("Package Publishing Status"),
 
993
            description=_("The status of this publishing record"),
 
994
            # Really PackagePublishingStatus, circular import fixed below.
 
995
            vocabulary=DBEnumeratedType,
 
996
            required=False),
 
997
        distroarchseries=Reference(
 
998
            # Really IDistroArchSeries, circular import fixed below.
 
999
            Interface,
 
1000
            title=_("Distro Arch Series"), required=False),
 
1001
        pocket=Choice(
 
1002
            title=_("Pocket"),
 
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),
 
1007
        exact_match=Bool(
 
1008
            description=_("Whether or not to filter binary names by exact "
 
1009
                          "matching."),
 
1010
            required=False),
 
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."),
 
1015
            required=False),
 
1016
        ordered=Bool(
 
1017
            title=_("Ordered"),
 
1018
            description=_("Return ordered results by default, but specifying "
 
1019
                          "False will return results more quickly."),
 
1020
            required=False, readonly=True),
 
1021
        )
 
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,
 
1030
                                ordered=True):
 
1031
        """All `IBinaryPackagePublishingHistory` target to this archive.
 
1032
 
 
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
 
1040
                             matching.
 
1041
        :param created_since_date: Only return publications created on or
 
1042
            after this date.
 
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
 
1047
            ordering.
 
1048
 
 
1049
        :return: A collection containing `BinaryPackagePublishingHistory`.
 
1050
        """
 
1051
 
 
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.
 
1058
 
 
1059
        This is necessary currently because the IArchive.failed_builds etc.
 
1060
        counters are not in use.
 
1061
 
 
1062
        The returned dictionary contains the follwoing keys and values:
 
1063
 
 
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.
 
1070
 
 
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.
 
1077
        :rtype: ``dict``.
 
1078
        """
 
1079
 
 
1080
    @operation_parameters(
 
1081
        source_ids=List(
 
1082
            title=_("A list of source publishing history record ids."),
 
1083
            value_type=Int()))
 
1084
    @export_read_operation()
 
1085
    def getBuildSummariesForSourceIds(source_ids):
 
1086
        """Return a dictionary containing a summary of the build statuses.
 
1087
 
 
1088
        Only information for sources belonging to the current archive will
 
1089
        be returned. See
 
1090
        `IPublishingSet`.getBuildStatusSummariesForSourceIdsAndArchive() for
 
1091
        details.
 
1092
 
 
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.
 
1097
        """
 
1098
 
 
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.
 
1105
 
 
1106
        :param dependency: is an `IArchive` object.
 
1107
 
 
1108
        :return: `IArchiveDependency` or None if a corresponding object
 
1109
            could not be found.
 
1110
        """
 
1111
 
 
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.
 
1118
 
 
1119
        :param person: An `IPerson`
 
1120
        :return: A list of `IArchivePermission` records.
 
1121
        """
 
1122
 
 
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.
 
1131
 
 
1132
        :param source_package_name: An `ISourcePackageName` or textual name
 
1133
            for the source package.
 
1134
        :return: A list of `IArchivePermission` records.
 
1135
        """
 
1136
 
 
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.
 
1144
 
 
1145
        :param component_name: An `IComponent` or textual name for the
 
1146
            component.
 
1147
        :return: A list of `IArchivePermission` records.
 
1148
        """
 
1149
 
 
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.
 
1157
 
 
1158
        :param component_name: An `IComponent` or textual name for the
 
1159
            component.
 
1160
        :return: A list of `IArchivePermission` records.
 
1161
        """
 
1162
 
 
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
 
1169
        components.
 
1170
 
 
1171
        :param person: An `IPerson`.
 
1172
        :return: A list of `IArchivePermission` records.
 
1173
        """
 
1174
 
 
1175
    def hasAnyPermission(person):
 
1176
        """Whether or not this person has any permission at all on this
 
1177
        archive.
 
1178
 
 
1179
        :param person: The `IPerson` for whom the check is performed.
 
1180
        :return: A boolean indicating if the person has any permission on this
 
1181
            archive at all.
 
1182
        """
 
1183
 
 
1184
    def getPackageDownloadCount(bpr, day, country):
 
1185
        """Get the `IBinaryPackageDownloadCount` with the given key."""
 
1186
 
 
1187
    def getFilesAndSha1s(source_files):
 
1188
        """Return a dictionary with the filenames and the SHA1s for each
 
1189
        source file.
 
1190
 
 
1191
        :param source_files: A list of filenames to return SHA1s of
 
1192
        :return: A dictionary of filenames and SHA1s.
 
1193
        """
 
1194
 
 
1195
    def getAuthToken(person):
 
1196
        """Returns an IArchiveAuthToken for the archive in question for
 
1197
        IPerson provided.
 
1198
 
 
1199
        :return: A IArchiveAuthToken, or None if the user has none.
 
1200
        """
 
1201
 
 
1202
    def newAuthToken(person, token=None, date_created=None):
 
1203
        """Create a new authorisation token.
 
1204
 
 
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
 
1209
 
 
1210
        :return: A new IArchiveAuthToken
 
1211
        """
 
1212
 
 
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"
 
1224
                          " this source"),
 
1225
            required=False),
 
1226
        sponsored=Reference(
 
1227
            schema=IPerson,
 
1228
            title=_("Sponsored Person"),
 
1229
            description=_("The person who is being sponsored for this copy."))
 
1230
        )
 
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,
 
1235
                    sponsored=None):
 
1236
        """Copy a single named source into this archive.
 
1237
 
 
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.
 
1242
 
 
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.
 
1256
 
 
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.
 
1261
        """
 
1262
 
 
1263
    @call_with(person=REQUEST_USER)
 
1264
    @operation_parameters(
 
1265
        source_names=List(
 
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"
 
1275
                          " this source"),
 
1276
            required=False),
 
1277
        sponsored=Reference(
 
1278
            schema=IPerson,
 
1279
            title=_("Sponsored Person"),
 
1280
            description=_("The person who is being sponsored for this copy."))
 
1281
        )
 
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,
 
1286
                     sponsored=None):
 
1287
        """Copy multiple named sources into this archive from another.
 
1288
 
 
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.
 
1293
 
 
1294
        Partial changes of the destination archive can happen because each
 
1295
        source is copied in its own transaction.
 
1296
 
 
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.
 
1309
 
 
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.
 
1314
        """
 
1315
 
 
1316
 
 
1317
class IArchiveAppend(Interface):
 
1318
    """Archive interface for operations restricted by append privilege."""
 
1319
 
 
1320
    @call_with(person=REQUEST_USER)
 
1321
    @operation_parameters(
 
1322
        source_names=List(
 
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"
 
1332
                          " this source"),
 
1333
            required=False))
 
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.
 
1340
 
 
1341
        It will copy the most recent PUBLISHED versions of the named
 
1342
        sources to the destination archive if necessary.
 
1343
 
 
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.
 
1348
 
 
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.
 
1357
 
 
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.
 
1362
        """
 
1363
 
 
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"
 
1375
                          " this source"),
 
1376
            required=False))
 
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.
 
1386
 
 
1387
        Copy a specific version of a named source to the destination
 
1388
        archive if necessary.
 
1389
 
 
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.
 
1399
 
 
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.
 
1404
        """
 
1405
 
 
1406
    @call_with(registrant=REQUEST_USER)
 
1407
    @operation_parameters(
 
1408
        subscriber=PublicPersonChoice(
 
1409
            title=_("Subscriber"),
 
1410
            required=True,
 
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 "
 
1415
                "expire.")),
 
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,
 
1421
                        description=None):
 
1422
        """Create a new subscribtion to this archive.
 
1423
 
 
1424
        Create an `ArchiveSubscriber` record which allows an `IPerson` to
 
1425
        access a private repository.
 
1426
 
 
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
 
1433
            being created.
 
1434
 
 
1435
        :return: The `IArchiveSubscriber` that was created.
 
1436
        """
 
1437
 
 
1438
 
 
1439
class IArchiveEdit(Interface):
 
1440
    """Archive interface for operations restricted by edit privilege."""
 
1441
 
 
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.
 
1450
 
 
1451
        :param person: An `IPerson` whom should be given permission.
 
1452
        :param source_package_name: An `ISourcePackageName` or textual package
 
1453
            name.
 
1454
        :return: An `IArchivePermission` which is the newly-created
 
1455
            permission.
 
1456
        """
 
1457
 
 
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.
 
1466
 
 
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
 
1470
            permission.
 
1471
        :raises InvalidComponent: if this archive is a PPA and the component
 
1472
            is not 'main'.
 
1473
        """
 
1474
 
 
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.
 
1483
 
 
1484
        The supplied person will gain permission to administer the
 
1485
        distroseries queue for packages in the supplied component.
 
1486
 
 
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
 
1490
            permission.
 
1491
        """
 
1492
 
 
1493
    @operation_parameters(
 
1494
        person=Reference(schema=IPerson),
 
1495
        # Really IPackageset, corrected in _schema_circular_imports to avoid
 
1496
        # circular import.
 
1497
        packageset=Reference(
 
1498
            Interface, title=_("Package set"), required=True),
 
1499
        explicit=Bool(
 
1500
            title=_("Explicit"), required=False))
 
1501
    # Really IArchivePermission, set in _schema_circular_imports to avoid
 
1502
    # circular import.
 
1503
    @export_factory_operation(Interface, [])
 
1504
    def newPackagesetUploader(person, packageset, explicit=False):
 
1505
        """Add a package set based permission for a person.
 
1506
 
 
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.
 
1511
 
 
1512
        :return: The new `ArchivePermission`, or the existing one if it
 
1513
            already exists.
 
1514
        """
 
1515
 
 
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.
 
1523
 
 
1524
        :param person: An `IPerson` whose permission should be revoked.
 
1525
        :param source_package_name: An `ISourcePackageName` or textual package
 
1526
            name.
 
1527
        """
 
1528
 
 
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.
 
1536
 
 
1537
        :param person: An `IPerson` whose permission should be revoked.
 
1538
        :param component: An `IComponent` or textual component name.
 
1539
        """
 
1540
 
 
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.
 
1548
 
 
1549
        The supplied person will lose permission to administer the
 
1550
        distroseries queue for packages in the supplied component.
 
1551
 
 
1552
        :param person: An `IPerson` whose permission should be revoked.
 
1553
        :param component: An `IComponent` or textual component name.
 
1554
        """
 
1555
 
 
1556
    @operation_parameters(
 
1557
        person=Reference(schema=IPerson),
 
1558
        # Really IPackageset, corrected in _schema_circular_imports to avoid
 
1559
        # circular import.
 
1560
        packageset=Reference(
 
1561
            Interface, title=_("Package set"), required=True),
 
1562
        explicit=Bool(
 
1563
            title=_("Explicit"), required=False))
 
1564
    @export_write_operation()
 
1565
    def deletePackagesetUploader(person, packageset, explicit=False):
 
1566
        """Revoke upload permissions for a person.
 
1567
 
 
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
 
1571
            to be revoked.
 
1572
        """
 
1573
 
 
1574
    def enable():
 
1575
        """Enable the archive."""
 
1576
 
 
1577
    def disable():
 
1578
        """Disable the archive."""
 
1579
 
 
1580
    def delete(deleted_by):
 
1581
        """Delete this archive.
 
1582
 
 
1583
        :param deleted_by: The `IPerson` requesting the deletion.
 
1584
 
 
1585
        The ArchiveStatus will be set to DELETING and any published
 
1586
        packages will be marked as DELETED by deleted_by.
 
1587
 
 
1588
        The publisher is responsible for deleting the repository area
 
1589
        when it sees the status change and sets it to DELETED once
 
1590
        processed.
 
1591
        """
 
1592
 
 
1593
    def addArchiveDependency(dependency, pocket, component=None):
 
1594
        """Record an archive dependency record for the context archive.
 
1595
 
 
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.
 
1601
 
 
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`.
 
1606
        """
 
1607
 
 
1608
    @operation_parameters(
 
1609
        dependency=Reference(schema=Interface, required=True),
 
1610
        #  Really IArchive
 
1611
        pocket=Choice(
 
1612
            title=_("Pocket"),
 
1613
            description=_("The pocket into which this entry is published"),
 
1614
            # Really PackagePublishingPocket.
 
1615
            vocabulary=DBEnumeratedType,
 
1616
            required=True),
 
1617
        component=TextLine(title=_("Component"), required=False),
 
1618
        )
 
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.
 
1624
 
 
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.
 
1630
 
 
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`.
 
1635
        """
 
1636
    @operation_parameters(
 
1637
        dependency=Reference(schema=Interface, required=True),
 
1638
        # Really IArchive
 
1639
    )
 
1640
    @export_write_operation()
 
1641
    @operation_for_version('devel')
 
1642
    def removeArchiveDependency(dependency):
 
1643
        """Remove the `IArchiveDependency` record for the given dependency.
 
1644
 
 
1645
        :param dependency: is an `IArchive` object.
 
1646
        """
 
1647
 
 
1648
 
 
1649
class IArchiveCommercial(Interface):
 
1650
    """Archive interface for operations restricted by commercial."""
 
1651
 
 
1652
    @operation_parameters(
 
1653
        family=Reference(schema=Interface, required=True),
 
1654
        # Really IProcessorFamily.
 
1655
    )
 
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.
 
1660
 
 
1661
        :param family: is an `IProcessorFamily` object.
 
1662
        """
 
1663
 
 
1664
 
 
1665
class IArchive(IArchivePublic, IArchiveAppend, IArchiveEdit, IArchiveView,
 
1666
               IArchiveCommercial):
 
1667
    """Main Archive interface."""
 
1668
    export_as_webservice_entry()
 
1669
 
 
1670
 
 
1671
class IPPA(IArchive):
 
1672
    """Marker interface so traversal works differently for PPAs."""
 
1673
 
 
1674
 
 
1675
class IDistributionArchive(IArchive):
 
1676
    """Marker interface so traversal works differently for distro archives."""
 
1677
 
 
1678
 
 
1679
class IArchiveEditDependenciesForm(Interface):
 
1680
    """Schema used to edit dependencies settings within a archive."""
 
1681
 
 
1682
    dependency_candidate = Choice(
 
1683
        title=_('Add PPA dependency'), required=False, vocabulary='PPA')
 
1684
 
 
1685
 
 
1686
class IArchiveSet(Interface):
 
1687
    """Interface for ArchiveSet"""
 
1688
 
 
1689
    title = Attribute('Title')
 
1690
 
 
1691
    def getNumberOfPPASourcesForDistribution(distribution):
 
1692
        """Return the number of sources for PPAs in a given distribution.
 
1693
 
 
1694
        Only public and published sources are considered.
 
1695
        """
 
1696
 
 
1697
    def getNumberOfPPABinariesForDistribution(distribution):
 
1698
        """Return the number of binaries for PPAs in a given distribution.
 
1699
 
 
1700
        Only public and published sources are considered.
 
1701
        """
 
1702
 
 
1703
    def new(purpose, owner, name=None, displayname=None, distribution=None,
 
1704
            description=None, enabled=True, require_virtualized=True,
 
1705
            private=False):
 
1706
        """Create a new archive.
 
1707
 
 
1708
        On named-ppa creation, the signing key for the default PPA for the
 
1709
        given owner will be used if it is present.
 
1710
 
 
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
 
1719
            used.
 
1720
        :param distribution: optional `IDistribution` to which the archive
 
1721
            will be attached;
 
1722
        :param description: optional text to be set as the archive
 
1723
            description;
 
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
 
1728
 
 
1729
        :return: an `IArchive` object.
 
1730
        :raises AssertionError if name is already taken within distribution.
 
1731
        """
 
1732
 
 
1733
    def get(archive_id):
 
1734
        """Return the IArchive with the given archive_id."""
 
1735
 
 
1736
    def getPPAByDistributionAndOwnerName(distribution, person_name, ppa_name):
 
1737
        """Return a single PPA.
 
1738
 
 
1739
        :param distribution: The context IDistribution.
 
1740
        :param person_name: The context IPerson.
 
1741
        :param ppa_name: The name of the archive (PPA)
 
1742
        """
 
1743
 
 
1744
    def getByDistroPurpose(distribution, purpose, name=None):
 
1745
        """Return the IArchive with the given distribution and purpose.
 
1746
 
 
1747
        It uses the default names defined in
 
1748
        `IArchiveSet._getDefaultArchiveNameForPurpose`.
 
1749
 
 
1750
        :raises AssertionError if used for with ArchivePurpose.PPA.
 
1751
        """
 
1752
 
 
1753
    def getByDistroAndName(distribution, name):
 
1754
        """Return the `IArchive` with the given distribution and name."""
 
1755
 
 
1756
    def __iter__():
 
1757
        """Iterates over existent archives, including the main_archives."""
 
1758
 
 
1759
    def getPPAOwnedByPerson(person, name=None, statuses=None,
 
1760
                            has_packages=False):
 
1761
        """Return the named PPA owned by person.
 
1762
 
 
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
 
1767
            source packages.
 
1768
 
 
1769
        If the name is not supplied it will default to the
 
1770
        first PPA that the person created.
 
1771
 
 
1772
        :raises NoSuchPPA: if the named PPA does not exist.
 
1773
        """
 
1774
 
 
1775
    def getPPAsForUser(user):
 
1776
        """Return all PPAs the given user can participate.
 
1777
 
 
1778
        The result is ordered by PPA displayname.
 
1779
        """
 
1780
 
 
1781
    def getPPAsPendingSigningKey():
 
1782
        """Return all PPAs pending signing key generation.
 
1783
 
 
1784
        The result is ordered by archive creation date.
 
1785
        """
 
1786
 
 
1787
    def getLatestPPASourcePublicationsForDistribution(distribution):
 
1788
        """The latest 5 PPA source publications for a given distribution.
 
1789
 
 
1790
        Private PPAs are excluded from the result.
 
1791
        """
 
1792
 
 
1793
    def getMostActivePPAsForDistribution(distribution):
 
1794
        """Return the 5 most active PPAs.
 
1795
 
 
1796
        The activity is currently measured by number of uploaded (published)
 
1797
        sources for each PPA during the last 7 days.
 
1798
 
 
1799
        Private PPAs are excluded from the result.
 
1800
 
 
1801
        :return A list with up to 5 dictionaries containing the ppa 'title'
 
1802
            and the number of 'uploads' keys and corresponding values.
 
1803
        """
 
1804
 
 
1805
    def getBuildCountersForArchitecture(archive, distroarchseries):
 
1806
        """Return a dictionary containing the build counters per status.
 
1807
 
 
1808
        The result is restricted to the given archive and distroarchseries.
 
1809
 
 
1810
        The returned dictionary contains the follwoing keys and values:
 
1811
 
 
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.
 
1817
 
 
1818
        :param archive: target `IArchive`;
 
1819
        :param distroarchseries: target `IDistroArchSeries`.
 
1820
 
 
1821
        :return a dictionary with the 4 keys specified above.
 
1822
        """
 
1823
 
 
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.
 
1827
 
 
1828
        This will return all the archives for the given distribution, with
 
1829
        the following parameters:
 
1830
 
 
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
 
1839
            will be returned.
 
1840
        :param exclude_disabled: Whether to exclude disabled archives.
 
1841
 
 
1842
        :return: A queryset of all the archives for the given
 
1843
            distribution matching the given params.
 
1844
        """
 
1845
 
 
1846
    def getPrivatePPAs():
 
1847
        """Return a result set containing all private PPAs."""
 
1848
 
 
1849
    def getCommercialPPAs():
 
1850
        """Return a result set containing all commercial PPAs.
 
1851
 
 
1852
        Commercial PPAs are private, but explicitly flagged up as commercial
 
1853
        so that they are discoverable by people who wish to buy items
 
1854
        from them.
 
1855
        """
 
1856
 
 
1857
    def getPublicationsInArchives(source_package_name, archive_list,
 
1858
                                  distribution):
 
1859
        """Return a result set of publishing records for the source package.
 
1860
 
 
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
 
1866
            be limited.
 
1867
        :return: a resultset of the `ISourcePackagePublishingHistory` objects
 
1868
            that are currently published in the given archives.
 
1869
        """
 
1870
 
 
1871
 
 
1872
default_name_by_purpose = {
 
1873
    ArchivePurpose.PRIMARY: 'primary',
 
1874
    ArchivePurpose.PPA: 'ppa',
 
1875
    ArchivePurpose.PARTNER: 'partner',
 
1876
    ArchivePurpose.DEBUG: 'debug',
 
1877
    }
 
1878
 
 
1879
 
 
1880
MAIN_ARCHIVE_PURPOSES = (
 
1881
    ArchivePurpose.PRIMARY,
 
1882
    ArchivePurpose.PARTNER,
 
1883
    ArchivePurpose.DEBUG,
 
1884
    )
 
1885
 
 
1886
ALLOW_RELEASE_BUILDS = (
 
1887
    ArchivePurpose.PARTNER,
 
1888
    ArchivePurpose.PPA,
 
1889
    ArchivePurpose.COPY,
 
1890
    )
 
1891
 
 
1892
FULL_COMPONENT_SUPPORT = (
 
1893
    ArchivePurpose.PRIMARY,
 
1894
    ArchivePurpose.DEBUG,
 
1895
    ArchivePurpose.COPY,
 
1896
    )
 
1897
 
 
1898
# Circular dependency issues fixed in _schema_circular_imports.py
 
1899
 
 
1900
 
 
1901
def validate_external_dependencies(ext_deps):
 
1902
    """Validate the external_dependencies field.
 
1903
 
 
1904
    :param ext_deps: The dependencies form field to check.
 
1905
    """
 
1906
    errors = []
 
1907
    # The field can consist of multiple entries separated by
 
1908
    # newlines, so process each in turn.
 
1909
    for dep in ext_deps.splitlines():
 
1910
        try:
 
1911
            deb, url, suite, components = dep.split(" ", 3)
 
1912
        except ValueError:
 
1913
            errors.append(
 
1914
                "'%s' is not a complete and valid sources.list entry"
 
1915
                    % dep)
 
1916
            continue
 
1917
 
 
1918
        if deb != "deb":
 
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)
 
1923
 
 
1924
    return errors