~launchpad-pqm/launchpad/devel

« back to all changes in this revision

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

  • Committer: Francis J. Lacoste
  • Date: 2011-04-27 21:40:03 UTC
  • mto: This revision was merged to the branch mainline in revision 12971.
  • Revision ID: francis.lacoste@canonical.com-20110427214003-iiqhcyyswppyqjsx
Change the default timeout to production value, improved options documentation and use only one bin above timeout value.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
    'CannotUploadToArchive',
21
21
    'CannotUploadToPPA',
22
22
    'CannotUploadToPocket',
 
23
    'DistroSeriesNotFound',
23
24
    'FULL_COMPONENT_SUPPORT',
24
25
    'IArchive',
25
26
    'IArchiveAppend',
26
 
    'IArchiveCommercial',
27
27
    'IArchiveEdit',
28
28
    'IArchiveView',
29
29
    'IArchiveEditDependenciesForm',
32
32
    'IDistributionArchive',
33
33
    'InsufficientUploadRights',
34
34
    'InvalidComponent',
35
 
    'InvalidExternalDependencies',
36
35
    'InvalidPocketForPartnerArchive',
37
36
    'InvalidPocketForPPA',
38
37
    'IPPA',
44
43
    'PocketNotFound',
45
44
    'VersionRequiresName',
46
45
    'default_name_by_purpose',
47
 
    'validate_external_dependencies',
48
46
    ]
49
47
 
50
 
import httplib
51
 
from urlparse import urlparse
52
 
 
53
48
from lazr.enum import DBEnumeratedType
54
49
from lazr.restful.declarations import (
55
50
    call_with,
56
 
    error_status,
57
51
    export_as_webservice_entry,
58
52
    export_factory_operation,
59
53
    export_operation_as,
60
54
    export_read_operation,
61
55
    export_write_operation,
62
56
    exported,
63
 
    operation_for_version,
64
57
    operation_parameters,
65
58
    operation_returns_collection_of,
66
59
    operation_returns_entry,
67
60
    rename_parameters_as,
68
61
    REQUEST_USER,
 
62
    webservice_error,
69
63
    )
70
64
from lazr.restful.fields import (
71
65
    CollectionField,
101
95
from lp.soyuz.enums import ArchivePurpose
102
96
from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
103
97
from lp.soyuz.interfaces.component import IComponent
104
 
 
105
 
 
106
 
@error_status(httplib.BAD_REQUEST)
 
98
from lp.soyuz.interfaces.processor import IProcessorFamily
 
99
 
 
100
 
107
101
class ArchiveDependencyError(Exception):
108
102
    """Raised when an `IArchiveDependency` does not fit the context archive.
109
103
 
117
111
 
118
112
# Exceptions used in the webservice that need to be in this file to get
119
113
# picked up therein.
120
 
@error_status(httplib.BAD_REQUEST)
 
114
 
121
115
class CannotCopy(Exception):
122
116
    """Exception raised when a copy cannot be performed."""
123
 
 
124
 
 
125
 
@error_status(httplib.BAD_REQUEST)
 
117
    webservice_error(400) #Bad request.
 
118
 
 
119
 
126
120
class CannotSwitchPrivacy(Exception):
127
121
    """Raised when switching the privacy of an archive that has
128
122
    publishing records."""
129
 
 
130
 
 
131
 
class PocketNotFound(NameLookupFailed):
 
123
    webservice_error(400) # Bad request.
 
124
 
 
125
 
 
126
class PocketNotFound(Exception):
132
127
    """Invalid pocket."""
133
 
    _message_prefix = "No such pocket"
134
 
 
135
 
 
136
 
@error_status(httplib.BAD_REQUEST)
 
128
    webservice_error(400) #Bad request.
 
129
 
 
130
 
 
131
class DistroSeriesNotFound(Exception):
 
132
    """Invalid distroseries."""
 
133
    webservice_error(400) #Bad request.
 
134
 
 
135
 
137
136
class AlreadySubscribed(Exception):
138
137
    """Raised when creating a subscription for a subscribed person."""
139
 
 
140
 
 
141
 
@error_status(httplib.BAD_REQUEST)
 
138
    webservice_error(400) # Bad request.
 
139
 
 
140
 
142
141
class ArchiveNotPrivate(Exception):
143
142
    """Raised when creating an archive subscription for a public archive."""
144
 
 
145
 
 
146
 
@error_status(httplib.BAD_REQUEST)
 
143
    webservice_error(400) # Bad request.
 
144
 
 
145
 
147
146
class NoTokensForTeams(Exception):
148
147
    """Raised when creating a token for a team, rather than a person."""
149
 
 
150
 
 
151
 
class ComponentNotFound(NameLookupFailed):
 
148
    webservice_error(400) # Bad request.
 
149
 
 
150
 
 
151
class ComponentNotFound(Exception):
152
152
    """Invalid source name."""
153
 
    _message_prefix = 'No such component'
154
 
 
155
 
 
156
 
@error_status(httplib.BAD_REQUEST)
 
153
    webservice_error(400) #Bad request.
 
154
 
 
155
 
157
156
class InvalidComponent(Exception):
158
157
    """Invalid component name."""
 
158
    webservice_error(400) #Bad request.
159
159
 
160
160
 
161
161
class NoSuchPPA(NameLookupFailed):
162
162
    """Raised when we try to look up an PPA that doesn't exist."""
 
163
    webservice_error(400) #Bad request.
163
164
    _message_prefix = "No such ppa"
164
165
 
165
166
 
166
 
@error_status(httplib.BAD_REQUEST)
167
167
class VersionRequiresName(Exception):
168
168
    """Raised on some queries when version is specified but name is not."""
 
169
    webservice_error(400) # Bad request.
169
170
 
170
171
 
171
172
class CannotRestrictArchitectures(Exception):
172
173
    """The architectures for this archive can not be restricted."""
173
174
 
174
175
 
175
 
@error_status(httplib.FORBIDDEN)
176
176
class CannotUploadToArchive(Exception):
177
177
    """A reason for not being able to upload to an archive."""
 
178
    webservice_error(403) # Forbidden.
178
179
 
179
180
    _fmt = '%(person)s has no upload rights to %(archive)s.'
180
181
 
189
190
    _fmt = "Partner uploads must be for the RELEASE or PROPOSED pocket."
190
191
 
191
192
 
192
 
@error_status(httplib.FORBIDDEN)
193
193
class CannotUploadToPocket(Exception):
194
194
    """Returned when a pocket is closed for uploads."""
 
195
    webservice_error(403) # Forbidden.
195
196
 
196
197
    def __init__(self, distroseries, pocket):
197
198
        Exception.__init__(self,
247
248
        CannotUploadToArchive.__init__(self, archive_name=archive_name)
248
249
 
249
250
 
250
 
@error_status(httplib.BAD_REQUEST)
251
 
class InvalidExternalDependencies(Exception):
252
 
    """Tried to set external dependencies to an invalid value."""
253
 
 
254
 
    def __init__(self, errors):
255
 
        error_msg = 'Invalid external dependencies:\n%s\n' % '\n'.join(errors)
256
 
        super(Exception, self).__init__(self, error_msg)
257
 
        self.errors = errors
258
 
 
259
 
 
260
251
class IArchivePublic(IHasOwner, IPrivacy):
261
252
    """An Archive interface for publicly available operations."""
262
253
    id = Attribute("The archive ID.")
341
332
 
342
333
    distribution = exported(
343
334
        Reference(
344
 
            Interface,  # Redefined to IDistribution later.
 
335
            Interface, # Redefined to IDistribution later.
345
336
            title=_("The distribution that uses or is used by this "
346
337
                    "archive.")))
347
338
 
421
412
            "A delta to apply to all build scores for the archive. Builds "
422
413
            "with a higher score will build sooner."))
423
414
 
424
 
    external_dependencies = exported(
425
 
        Text(title=_("External dependencies"), required=False,
426
 
        readonly=False, description=_(
 
415
    external_dependencies = Text(
 
416
        title=_("External dependencies"), required=False, readonly=False,
 
417
        description=_(
427
418
            "Newline-separated list of repositories to be used to retrieve "
428
419
            "any external build dependencies when building packages in the "
429
420
            "archive, in the format:\n"
431
422
                "[components]\n"
432
423
            "The series variable is replaced with the series name of the "
433
424
            "context build.\n"
434
 
            "NOTE: This is for migration of OEM PPAs only!")))
 
425
            "NOTE: This is for migration of OEM PPAs only!"))
435
426
 
436
 
    enabled_restricted_families = exported(
437
 
        CollectionField(
 
427
    enabled_restricted_families = CollectionField(
438
428
            title=_("Enabled restricted families"),
439
429
            description=_(
440
430
                "The restricted architecture families on which the archive "
441
431
                "can build."),
442
 
            value_type=Reference(schema=Interface),
443
 
            # Really IProcessorFamily.
444
 
            readonly=True),
445
 
        as_of='devel')
 
432
            value_type=Reference(schema=IProcessorFamily),
 
433
            readonly=False)
446
434
 
447
435
    commercial = exported(
448
436
        Bool(
532
520
            records.
533
521
        """
534
522
 
 
523
    def removeArchiveDependency(dependency):
 
524
        """Remove the `IArchiveDependency` record for the given dependency.
 
525
 
 
526
        :param dependency: is an `IArchive` object.
 
527
        """
 
528
 
 
529
    def addArchiveDependency(dependency, pocket, component=None):
 
530
        """Record an archive dependency record for the context archive.
 
531
 
 
532
        :param dependency: is an `IArchive` object.
 
533
        :param pocket: is an `PackagePublishingPocket` enum.
 
534
        :param component: is an optional `IComponent` object, if not given
 
535
            the archive dependency will be tied to the component used
 
536
            for a corresponding source in primary archive.
 
537
 
 
538
        :raise: `ArchiveDependencyError` if given 'dependency' does not fit
 
539
            the context archive.
 
540
        :return: a `IArchiveDependency` object targeted to the context
 
541
            `IArchive` requiring 'dependency' `IArchive`.
 
542
        """
 
543
 
535
544
    def getPermissions(person, item, perm_type):
536
545
        """Get the `IArchivePermission` record with the supplied details.
537
546
 
607
616
        :param component: The `Component` being uploaded to.
608
617
        :param pocket: The `PackagePublishingPocket` of 'distroseries' being
609
618
            uploaded to.
610
 
        :param strict_component: True if access to the specific component for
611
 
            the package is needed to upload to it. If False, then access to
612
 
            any component will do.
613
619
        :return: The reason for not being able to upload, None otherwise.
614
620
        """
615
621
 
626
632
        :param distroseries: The upload's target distro series.
627
633
        :param strict_component: True if access to the specific component for
628
634
            the package is needed to upload to it. If False, then access to
629
 
            any component will do.
 
635
            any package will do.
630
636
        :return: CannotUploadToArchive if 'person' cannot upload to the
631
637
            archive,
632
638
            None otherwise.
883
889
        :return: True if the person is allowed to upload the source package.
884
890
        """
885
891
 
886
 
    num_pkgs_building = Attribute(
887
 
        "Tuple of packages building and waiting to build")
 
892
    num_pkgs_building = Attribute("Tuple of packages building and waiting to build")
888
893
 
889
894
    def getSourcePackageReleases(build_status=None):
890
895
        """Return the releases for this archive.
921
926
    def getPockets():
922
927
        """Return iterable containing valid pocket names for this archive."""
923
928
 
924
 
    def getOverridePolicy():
925
 
        """Returns an instantiated `IOverridePolicy` for the archive."""
926
 
 
927
929
 
928
930
class IArchiveView(IHasBuildRecords):
929
931
    """Archive interface for operations restricted by view privilege."""
936
938
    dependencies = exported(
937
939
        CollectionField(
938
940
            title=_("Archive dependencies recorded for this archive."),
939
 
            value_type=Reference(schema=Interface),
940
 
            # Really IArchiveDependency
 
941
            value_type=Reference(schema=Interface), #Really IArchiveDependency
941
942
            readonly=True))
942
943
 
943
944
    description = exported(
997
998
 
998
999
        :param name: source name filter (exact match or SQL LIKE controlled
999
1000
                     by 'exact_match' argument).
1000
 
                     Name can be a single string or a list of strings.
1001
1001
        :param version: source version filter (always exact match).
1002
1002
        :param status: `PackagePublishingStatus` filter, can be a sequence.
1003
1003
        :param distroseries: `IDistroSeries` filter.
1105
1105
        """
1106
1106
 
1107
1107
    @operation_parameters(
1108
 
        dependency=Reference(schema=Interface))  # Really IArchive. See below.
1109
 
    @operation_returns_entry(schema=Interface)  # Really IArchiveDependency.
 
1108
        dependency=Reference(schema=Interface)) #Really IArchive. See below.
 
1109
    @operation_returns_entry(schema=Interface) #Really IArchiveDependency.
1110
1110
    @export_read_operation()
1111
1111
    def getArchiveDependency(dependency):
1112
1112
        """Return the `IArchiveDependency` object for the given dependency.
1173
1173
    @operation_returns_collection_of(Interface)
1174
1174
    @export_read_operation()
1175
1175
    def getComponentsForQueueAdmin(person):
1176
 
        """Return `IArchivePermission` for the person's queue admin
1177
 
        components.
 
1176
        """Return `IArchivePermission` for the person's queue admin components
1178
1177
 
1179
 
        :param person: An `IPerson`.
 
1178
        :param person: An `IPerson`
1180
1179
        :return: A list of `IArchivePermission` records.
1181
1180
        """
1182
1181
 
1183
 
    def hasAnyPermission(person):
1184
 
        """Whether or not this person has any permission at all on this
1185
 
        archive.
1186
 
 
1187
 
        :param person: The `IPerson` for whom the check is performed.
1188
 
        :return: A boolean indicating if the person has any permission on this
1189
 
            archive at all.
1190
 
        """
1191
 
 
1192
1182
    def getPackageDownloadCount(bpr, day, country):
1193
1183
        """Get the `IBinaryPackageDownloadCount` with the given key."""
1194
1184
 
1222
1212
class IArchiveAppend(Interface):
1223
1213
    """Archive interface for operations restricted by append privilege."""
1224
1214
 
1225
 
    @call_with(person=REQUEST_USER)
1226
1215
    @operation_parameters(
1227
1216
        source_names=List(
1228
1217
            title=_("Source package names"),
1229
1218
            value_type=TextLine()),
1230
 
        from_archive=Reference(schema=Interface),
1231
 
        #Really IArchive, see below
 
1219
        from_archive=Reference(schema=Interface), #Really IArchive, see below
1232
1220
        to_pocket=TextLine(title=_("Pocket name")),
1233
1221
        to_series=TextLine(title=_("Distroseries name"), required=False),
1234
1222
        include_binaries=Bool(
1239
1227
    @export_write_operation()
1240
1228
    # Source_names is a string because exporting a SourcePackageName is
1241
1229
    # rather nonsensical as it only has id and name columns.
1242
 
    def syncSources(source_names, from_archive, to_pocket, to_series=None,
1243
 
                    include_binaries=False, person=None):
 
1230
    def syncSources(source_names, from_archive, to_pocket,
 
1231
                    to_series=None, include_binaries=False):
1244
1232
        """Synchronise (copy) named sources into this archive from another.
1245
1233
 
1246
1234
        It will copy the most recent PUBLISHED versions of the named
1258
1246
        :param include_binaries: optional boolean, controls whether or not
1259
1247
            the published binaries for each given source should also be
1260
1248
            copied along with the source.
1261
 
        :param person: the `IPerson` who requests the sync.
1262
1249
 
1263
1250
        :raises NoSuchSourcePackageName: if the source name is invalid
1264
1251
        :raises PocketNotFound: if the pocket name is invalid
1265
 
        :raises NoSuchDistroSeries: if the distro series name is invalid
 
1252
        :raises DistroSeriesNotFound: if the distro series name is invalid
1266
1253
        :raises CannotCopy: if there is a problem copying.
1267
1254
        """
1268
1255
 
1269
 
    @call_with(person=REQUEST_USER)
1270
1256
    @operation_parameters(
1271
1257
        source_name=TextLine(title=_("Source package name")),
1272
1258
        version=TextLine(title=_("Version")),
1273
 
        from_archive=Reference(schema=Interface),
1274
 
        # Really IArchive, see below
 
1259
        from_archive=Reference(schema=Interface), #Really IArchive, see below
1275
1260
        to_pocket=TextLine(title=_("Pocket name")),
1276
1261
        to_series=TextLine(title=_("Distroseries name"), required=False),
1277
1262
        include_binaries=Bool(
1286
1271
    # we should consider either changing this method or adding a new one
1287
1272
    # that takes that object instead.
1288
1273
    def syncSource(source_name, version, from_archive, to_pocket,
1289
 
                   to_series=None, include_binaries=False, person=None):
 
1274
                   to_series=None, include_binaries=False):
1290
1275
        """Synchronise (copy) a single named source into this archive.
1291
1276
 
1292
1277
        Copy a specific version of a named source to the destination
1300
1285
        :param include_binaries: optional boolean, controls whether or not
1301
1286
            the published binaries for each given source should also be
1302
1287
            copied along with the source.
1303
 
        :param person: the `IPerson` who requests the sync.
1304
1288
 
1305
1289
        :raises NoSuchSourcePackageName: if the source name is invalid
1306
1290
        :raises PocketNotFound: if the pocket name is invalid
1307
 
        :raises NoSuchDistroSeries: if the distro series name is invalid
 
1291
        :raises DistroSeriesNotFound: if the distro series name is invalid
1308
1292
        :raises CannotCopy: if there is a problem copying.
1309
1293
        """
1310
1294
 
1311
1295
    @call_with(registrant=REQUEST_USER)
1312
1296
    @operation_parameters(
1313
 
        subscriber=PublicPersonChoice(
 
1297
        subscriber = PublicPersonChoice(
1314
1298
            title=_("Subscriber"),
1315
1299
            required=True,
1316
1300
            vocabulary='ValidPersonOrTeam',
1454
1438
        processed.
1455
1439
        """
1456
1440
 
1457
 
    def addArchiveDependency(dependency, pocket, component=None):
1458
 
        """Record an archive dependency record for the context archive.
1459
 
 
1460
 
        :param dependency: is an `IArchive` object.
1461
 
        :param pocket: is an `PackagePublishingPocket` enum.
1462
 
        :param component: is an optional `IComponent` object, if not given
1463
 
            the archive dependency will be tied to the component used
1464
 
            for a corresponding source in primary archive.
1465
 
 
1466
 
        :raise: `ArchiveDependencyError` if given 'dependency' does not fit
1467
 
            the context archive.
1468
 
        :return: a `IArchiveDependency` object targeted to the context
1469
 
            `IArchive` requiring 'dependency' `IArchive`.
1470
 
        """
1471
 
 
1472
 
    @operation_parameters(
1473
 
        dependency=Reference(schema=Interface, required=True),
1474
 
        #  Really IArchive
1475
 
        pocket=Choice(
1476
 
            title=_("Pocket"),
1477
 
            description=_("The pocket into which this entry is published"),
1478
 
            # Really PackagePublishingPocket.
1479
 
            vocabulary=DBEnumeratedType,
1480
 
            required=True),
1481
 
        component=TextLine(title=_("Component"), required=False),
1482
 
        )
1483
 
    @export_operation_as('addArchiveDependency')
1484
 
    @export_factory_operation(Interface, [])  # Really IArchiveDependency
1485
 
    @operation_for_version('devel')
1486
 
    def _addArchiveDependency(dependency, pocket, component=None):
1487
 
        """Record an archive dependency record for the context archive.
1488
 
 
1489
 
        :param dependency: is an `IArchive` object.
1490
 
        :param pocket: is an `PackagePublishingPocket` enum.
1491
 
        :param component: is the name of a component.  If not given,
1492
 
            the archive dependency will be tied to the component used
1493
 
            for a corresponding source in primary archive.
1494
 
 
1495
 
        :raise: `ArchiveDependencyError` if given 'dependency' does not fit
1496
 
            the context archive.
1497
 
        :return: a `IArchiveDependency` object targeted to the context
1498
 
            `IArchive` requiring 'dependency' `IArchive`.
1499
 
        """
1500
 
    @operation_parameters(
1501
 
        dependency=Reference(schema=Interface, required=True),
1502
 
        # Really IArchive
1503
 
    )
1504
 
    @export_write_operation()
1505
 
    @operation_for_version('devel')
1506
 
    def removeArchiveDependency(dependency):
1507
 
        """Remove the `IArchiveDependency` record for the given dependency.
1508
 
 
1509
 
        :param dependency: is an `IArchive` object.
1510
 
        """
1511
 
 
1512
 
 
1513
 
class IArchiveCommercial(Interface):
1514
 
    """Archive interface for operations restricted by commercial."""
1515
 
 
1516
 
    @operation_parameters(
1517
 
        family=Reference(schema=Interface, required=True),
1518
 
        # Really IProcessorFamily.
1519
 
    )
1520
 
    @export_write_operation()
1521
 
    @operation_for_version('devel')
1522
 
    def enableRestrictedFamily(family):
1523
 
        """Add the processor family to the set of enabled restricted families.
1524
 
 
1525
 
        :param family: is an `IProcessorFamily` object.
1526
 
        """
1527
 
 
1528
 
 
1529
 
class IArchive(IArchivePublic, IArchiveAppend, IArchiveEdit, IArchiveView,
1530
 
               IArchiveCommercial):
 
1441
 
 
1442
class IArchive(IArchivePublic, IArchiveAppend, IArchiveEdit, IArchiveView):
1531
1443
    """Main Archive interface."""
1532
1444
    export_as_webservice_entry()
1533
1445
 
1758
1670
    )
1759
1671
 
1760
1672
# Circular dependency issues fixed in _schema_circular_imports.py
1761
 
 
1762
 
 
1763
 
def validate_external_dependencies(ext_deps):
1764
 
    """Validate the external_dependencies field.
1765
 
 
1766
 
    :param ext_deps: The dependencies form field to check.
1767
 
    """
1768
 
    errors = []
1769
 
    # The field can consist of multiple entries separated by
1770
 
    # newlines, so process each in turn.
1771
 
    for dep in ext_deps.splitlines():
1772
 
        try:
1773
 
            deb, url, suite, components = dep.split(" ", 3)
1774
 
        except ValueError:
1775
 
            errors.append(
1776
 
                "'%s' is not a complete and valid sources.list entry"
1777
 
                    % dep)
1778
 
            continue
1779
 
 
1780
 
        if deb != "deb":
1781
 
            errors.append("%s: Must start with 'deb'" % dep)
1782
 
        url_components = urlparse(url)
1783
 
        if not url_components[0] or not url_components[1]:
1784
 
            errors.append("%s: Invalid URL" % dep)
1785
 
 
1786
 
    return errors