~launchpad-pqm/launchpad/devel

10637.3.1 by Guilherme Salgado
Use the default python version instead of a hard-coded version
1
#!/usr/bin/python -S
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
2
# pylint: disable-msg=W0403
3
10373.3.2 by Jeroen Vermeulen
Integrate cleanup of playground sample data for Soyuz testing.
4
# Copyright 2010 Canonical Ltd.  This software is licensed under the
5
# GNU Affero General Public License version 3 (see the file LICENSE).
6
#
7
# This code is based on William Grant's make-ubuntu-sane.py script, but
8
# reorganized to fit Launchpad coding guidelines, and extended.  The
9
# code is included under Canonical copyright with his permission
10
# (2010-02-24).
11
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
12
"""Clean up sample data so it will allow Soyuz to run locally.
13
14
DO NOT RUN ON PRODUCTION SYSTEMS.  This script deletes lots of
15
Ubuntu-related data.
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
16
17
This script creates a user "ppa-user" (email ppa-user@example.com,
18
password test) who is able to create PPAs.
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
19
"""
20
21
__metaclass__ = type
22
23
import _pythonpath
24
25
from optparse import OptionParser
26
import re
10373.3.5 by Jeroen Vermeulen
Automating lots of manual steps for getting Soyuz to work.
27
import os
28
import subprocess
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
29
import sys
10373.3.4 by Jeroen Vermeulen
Make soyuz sampledata script do more of the work.
30
from textwrap import dedent
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
31
import transaction
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
32
33
from zope.component import getUtility
34
from zope.event import notify
35
from zope.lifecycleevent import ObjectCreatedEvent
36
from zope.security.proxy import removeSecurityProxy
37
38
from storm.store import Store
39
40
from canonical.database.sqlbase import sqlvalues
41
42
from canonical.lp import initZopeless
43
44
from canonical.launchpad.interfaces.launchpad import (
45
    ILaunchpadCelebrities)
46
from canonical.launchpad.scripts import execute_zcml_for_scripts
47
from canonical.launchpad.scripts.logger import logger, logger_options
48
from canonical.launchpad.webapp.interfaces import (
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
49
    IStoreSelector, MAIN_STORE, MASTER_FLAVOR, SLAVE_FLAVOR)
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
50
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
51
from lp.registry.interfaces.codeofconduct import ISignedCodeOfConductSet
10427.4.6 by Jeroen Vermeulen
Final polish. I hope.
52
from lp.registry.interfaces.person import IPersonSet
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
53
from lp.registry.interfaces.series import SeriesStatus
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
54
from lp.registry.model.codeofconduct import SignedCodeOfConduct
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
55
from lp.soyuz.interfaces.component import IComponentSet
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
56
from lp.soyuz.interfaces.processor import IProcessorFamilySet
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
57
from lp.soyuz.interfaces.section import ISectionSet
10373.3.2 by Jeroen Vermeulen
Integrate cleanup of playground sample data for Soyuz testing.
58
from lp.soyuz.interfaces.sourcepackageformat import (
59
    ISourcePackageFormatSelectionSet, SourcePackageFormat)
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
60
from lp.soyuz.model.section import SectionSelection
61
from lp.soyuz.model.component import ComponentSelection
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
62
from lp.testing.factory import LaunchpadObjectFactory
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
63
64
10373.3.5 by Jeroen Vermeulen
Automating lots of manual steps for getting Soyuz to work.
65
user_name = 'ppa-user'
66
default_email = '%s@example.com' % user_name
67
68
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
69
class DoNotRunOnProduction(Exception):
70
    """Error: do not run this script on production (-like) systems."""
71
72
73
def get_max_id(store, table_name):
74
    """Find highest assigned id in given table."""
75
    max_id = store.execute("SELECT max(id) FROM %s" % table_name).get_one()
76
    if max_id is None:
77
        return None
78
    else:
79
        return max_id[0]
80
81
10373.3.11 by Jeroen Vermeulen
Merge devel
82
def get_store(flavor=MASTER_FLAVOR):
83
    """Obtain an ORM store."""
84
    return getUtility(IStoreSelector).get(MAIN_STORE, flavor)
85
86
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
87
def check_preconditions(options):
10373.3.2 by Jeroen Vermeulen
Integrate cleanup of playground sample data for Soyuz testing.
88
    """Try to ensure that it's safe to run.
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
89
90
    This script must not run on a production server, or anything
91
    remotely like it.
92
    """
10373.3.11 by Jeroen Vermeulen
Merge devel
93
    store = get_store(SLAVE_FLAVOR)
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
94
95
    # Just a guess, but dev systems aren't likely to have ids this high
96
    # in this table.  Production data does.
97
    real_data = (get_max_id(store, "TranslationMessage") >= 1000000)
98
    if real_data and not options.force:
99
        raise DoNotRunOnProduction(
100
            "Refusing to delete Ubuntu data unless you --force me.")
101
102
    # For some configs it's just absolutely clear this script shouldn't
103
    # run.  Don't even accept --force there.
104
    forbidden_configs = re.compile('(edge|lpnet|production)')
10373.3.5 by Jeroen Vermeulen
Automating lots of manual steps for getting Soyuz to work.
105
    current_config = os.getenv('LPCONFIG', 'an unknown config')
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
106
    if forbidden_configs.match(current_config):
107
        raise DoNotRunOnProduction(
108
            "I won't delete Ubuntu data on %s and you can't --force me."
109
            % current_config)
110
111
112
def parse_args(arguments):
113
    """Parse command-line arguments.
114
115
    :return: (options, args, logger)
116
    """
117
    parser = OptionParser(
10373.3.5 by Jeroen Vermeulen
Automating lots of manual steps for getting Soyuz to work.
118
        description="Set up fresh Ubuntu series and %s identity." % user_name)
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
119
    parser.add_option('-f', '--force', action='store_true', dest='force',
120
        help="DANGEROUS: run even if the database looks production-like.")
10373.3.4 by Jeroen Vermeulen
Make soyuz sampledata script do more of the work.
121
    parser.add_option('-e', '--email', action='store', dest='email',
10373.3.5 by Jeroen Vermeulen
Automating lots of manual steps for getting Soyuz to work.
122
        default=default_email,
123
        help=(
124
            "Email address to use for %s.  Should match your GPG key."
125
            % user_name))
10373.3.4 by Jeroen Vermeulen
Make soyuz sampledata script do more of the work.
126
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
127
    logger_options(parser)
128
129
    options, args = parser.parse_args(arguments)
10373.3.4 by Jeroen Vermeulen
Make soyuz sampledata script do more of the work.
130
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
131
    return options, args, logger(options)
132
133
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
134
def get_person_set():
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
135
    """Return `IPersonSet` utility."""
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
136
    return getUtility(IPersonSet)
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
137
138
139
def retire_series(distribution):
140
    """Mark all `DistroSeries` for `distribution` as obsolete."""
141
    for series in distribution.series:
142
        series.status = SeriesStatus.OBSOLETE
143
144
145
def retire_active_publishing_histories(histories, requester):
146
    """Retire all active publishing histories in the given collection."""
147
    # Avoid circular import.
148
    from lp.soyuz.interfaces.publishing import active_publishing_status
149
    for history in histories(status=active_publishing_status):
150
        history.requestDeletion(
151
            requester, "Cleaned up because of missing Librarian files.")
152
153
154
def retire_distro_archives(distribution, culprit):
155
    """Retire all items in `distribution`'s archives."""
156
    for archive in distribution.all_distro_archives:
157
        retire_active_publishing_histories(
158
            archive.getPublishedSources, culprit)
159
        retire_active_publishing_histories(
160
            archive.getAllPublishedBinaries, culprit)
161
162
163
def retire_ppas(distribution):
164
    """Disable all PPAs for `distribution`."""
165
    for ppa in distribution.getAllPPAs():
166
        removeSecurityProxy(ppa).publish = False
167
168
169
def set_lucille_config(distribution):
170
    """Set lucilleconfig on all series of `distribution`."""
171
    for series in distribution.series:
172
        removeSecurityProxy(series).lucilleconfig = '''[publishing]
173
components = main restricted universe multiverse'''
174
175
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
176
def add_architecture(distroseries, architecture_name):
177
    """Add a DistroArchSeries for the given architecture to `distroseries`."""
178
    # Avoid circular import.
179
    from lp.soyuz.model.distroarchseries import DistroArchSeries
180
10373.3.11 by Jeroen Vermeulen
Merge devel
181
    store = get_store(MASTER_FLAVOR)
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
182
    family = getUtility(IProcessorFamilySet).getByName(architecture_name)
183
    archseries = DistroArchSeries(
184
        distroseries=distroseries, processorfamily=family,
185
        owner=distroseries.owner, official=True,
186
        architecturetag=architecture_name)
187
    store.add(archseries)
188
189
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
190
def create_sections(distroseries):
191
    """Set up some sections for `distroseries`."""
192
    section_names = (
193
        'admin', 'cli-mono', 'comm', 'database', 'devel', 'debug', 'doc',
194
        'editors', 'electronics', 'embedded', 'fonts', 'games', 'gnome',
195
        'graphics', 'gnu-r', 'gnustep', 'hamradio', 'haskell', 'httpd',
196
        'interpreters', 'java', 'kde', 'kernel', 'libs', 'libdevel', 'lisp',
197
        'localization', 'mail', 'math', 'misc', 'net', 'news', 'ocaml',
198
        'oldlibs', 'otherosfs', 'perl', 'php', 'python', 'ruby', 'science',
199
        'shells', 'sound', 'tex', 'text', 'utils', 'vcs', 'video', 'web',
200
        'x11', 'xfce', 'zope')
201
    store = Store.of(distroseries)
202
    for section_name in section_names:
203
        section = getUtility(ISectionSet).ensure(section_name)
204
        if section not in distroseries.sections:
205
            store.add(
206
                SectionSelection(distroseries=distroseries, section=section))
207
208
209
def create_components(distroseries, uploader):
210
    """Set up some components for `distroseries`."""
211
    component_names = ('main', 'restricted', 'universe', 'multiverse')
212
    store = Store.of(distroseries)
213
    main_archive = distroseries.distribution.main_archive
214
    for component_name in component_names:
215
        component = getUtility(IComponentSet).ensure(component_name)
216
        if component not in distroseries.components:
217
            store.add(
218
                ComponentSelection(
219
                    distroseries=distroseries, component=component))
220
        main_archive.newComponentUploader(uploader, component)
221
        main_archive.newQueueAdmin(uploader, component)
222
223
224
def create_series(parent, full_name, version, status):
225
    """Set up a `DistroSeries`."""
226
    distribution = parent.distribution
227
    owner = parent.owner
228
    name = full_name.split()[0].lower()
229
    title = "The " + full_name
230
    displayname = full_name.split()[0]
231
    new_series = distribution.newSeries(name=name, title=title,
232
        displayname=displayname, summary='Ubuntu %s is good.' % version,
233
        description='%s is awesome.' % version, version=version,
234
        parent_series=parent, owner=owner)
235
    new_series.status = status
236
    notify(ObjectCreatedEvent(new_series))
237
238
    # This bit copied from scripts/ftpmaster-tools/initialise-from-parent.py.
239
    assert new_series.architectures.count() == 0, (
240
        "Cannot copy distroarchseries from parent; this series already has "
241
        "distroarchseries.")
242
243
    store = Store.of(parent)
244
    store.execute("""
245
        INSERT INTO DistroArchSeries
246
          (distroseries, processorfamily, architecturetag, owner, official)
247
        SELECT %s, processorfamily, architecturetag, %s, official
248
        FROM DistroArchSeries WHERE distroseries = %s
249
        """ % sqlvalues(new_series, owner, parent))
250
251
    i386 = new_series.getDistroArchSeries('i386')
252
    i386.supports_virtualized = True
253
    new_series.nominatedarchindep = i386
254
255
    new_series.initialiseFromParent()
256
    return new_series
257
258
259
def create_sample_series(original_series, log):
260
    """Set up sample `DistroSeries`.
261
262
    :param original_series: The parent for the first new series to be
263
        created.  The second new series will have the first as a parent,
264
        and so on.
265
    """
266
    series_descriptions = [
267
        ('Dapper Drake', SeriesStatus.SUPPORTED, '6.06'),
268
        ('Edgy Eft', SeriesStatus.OBSOLETE, '6.10'),
269
        ('Feisty Fawn', SeriesStatus.OBSOLETE, '7.04'),
270
        ('Gutsy Gibbon', SeriesStatus.OBSOLETE, '7.10'),
271
        ('Hardy Heron', SeriesStatus.SUPPORTED, '8.04'),
272
        ('Intrepid Ibex', SeriesStatus.SUPPORTED, '8.10'),
273
        ('Jaunty Jackalope', SeriesStatus.SUPPORTED, '9.04'),
11091.5.2 by Aaron Bentley
Update soyuz sample data
274
        ('Karmic Koala', SeriesStatus.SUPPORTED, '9.10'),
275
        ('Lucid Lynx', SeriesStatus.CURRENT, '10.04'),
11091.5.6 by Aaron Bentley
Ficks speling
276
        ('Maverick Meerkat', SeriesStatus.DEVELOPMENT, '10.10'),
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
277
        ]
278
279
    parent = original_series
280
    for full_name, status, version in series_descriptions:
281
        log.info('Creating %s...' % full_name)
282
        parent = create_series(parent, full_name, version, status)
283
284
285
def clean_up(distribution, log):
286
    # First we eliminate all active publishings in the Ubuntu main archives.
287
    # None of the librarian files exist, so it kills the publisher.
288
289
    # Could use IPublishingSet.requestDeletion() on the published sources to
290
    # get rid of the binaries too, but I don't trust that there aren't
291
    # published binaries without corresponding sources.
292
293
    log.info("Deleting all items in official archives...")
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
294
    retire_distro_archives(distribution, get_person_set().getByName('name16'))
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
295
296
    # Disable publishing of all PPAs, as they probably have broken
297
    # publishings too.
298
    log.info("Disabling all PPAs...")
299
    retire_ppas(distribution)
300
301
    retire_series(distribution)
302
303
10373.3.2 by Jeroen Vermeulen
Integrate cleanup of playground sample data for Soyuz testing.
304
def set_source_package_format(distroseries):
305
    """Register a series' source package format selection."""
306
    utility = getUtility(ISourcePackageFormatSelectionSet)
307
    format = SourcePackageFormat.FORMAT_1_0
308
    if utility.getBySeriesAndFormat(distroseries, format) is None:
309
        utility.add(distroseries, format)
310
311
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
312
def populate(distribution, parent_series_name, uploader_name, options, log):
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
313
    """Set up sample data on `distribution`."""
314
    parent_series = distribution.getSeries(parent_series_name)
315
316
    # Set up lucilleconfig on all series.  The sample data lacks this.
317
    log.info("Setting lucilleconfig...")
318
    set_lucille_config(distribution)
319
320
    log.info("Configuring sections...")
321
    create_sections(parent_series)
10373.3.9 by Jeroen Vermeulen
Always include amd64 support.
322
    add_architecture(parent_series, 'amd64')
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
323
324
    log.info("Configuring components and permissions...")
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
325
    uploader = get_person_set().getByName(uploader_name)
326
    create_components(parent_series, uploader)
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
327
10373.3.2 by Jeroen Vermeulen
Integrate cleanup of playground sample data for Soyuz testing.
328
    set_source_package_format(parent_series)
329
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
330
    create_sample_series(parent_series, log)
331
332
10373.3.4 by Jeroen Vermeulen
Make soyuz sampledata script do more of the work.
333
def sign_code_of_conduct(person, log):
334
    """Sign Ubuntu Code of Conduct for `person`, if necessary."""
335
    if person.is_ubuntu_coc_signer:
336
        # Already signed.
337
        return
338
339
    log.info("Signing Ubuntu code of conduct.")
340
    signedcocset = getUtility(ISignedCodeOfConductSet)
341
    person_id = person.id
342
    if signedcocset.searchByUser(person_id).count() == 0:
343
        fake_gpg_key = LaunchpadObjectFactory().makeGPGKey(person)
344
        Store.of(person).add(SignedCodeOfConduct(
345
            owner=person, signingkey=fake_gpg_key,
346
            signedcode="Normally a signed CoC would go here.", active=True))
347
348
349
def create_ppa_user(username, options, approver, log):
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
350
    """Create new user, with password "test," and sign code of conduct."""
351
    person = get_person_set().getByName(username)
352
    if person is None:
10427.4.2 by Jeroen Vermeulen
Moved user/gpg creation into make-lp-user.
353
        have_email = (options.email != default_email)
354
        command_line = [
355
            'utilities/make-lp-user',
356
            username,
357
            'ubuntu-team'
358
            ]
359
        if have_email:
360
            command_line += ['--email', options.email]
361
362
        pipe = subprocess.Popen(command_line, stderr=subprocess.PIPE)
363
        stdout, stderr = pipe.communicate()
364
        if stderr != '':
365
            print stderr
366
        if pipe.returncode != 0:
367
            sys.exit(2)
368
369
    transaction.commit()
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
370
10427.4.6 by Jeroen Vermeulen
Final polish. I hope.
371
    person = getUtility(IPersonSet).getByName(username)
10373.3.4 by Jeroen Vermeulen
Make soyuz sampledata script do more of the work.
372
    sign_code_of_conduct(person, log)
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
373
10373.3.5 by Jeroen Vermeulen
Automating lots of manual steps for getting Soyuz to work.
374
    return person
375
376
10373.3.10 by Jeroen Vermeulen
Creating PPA as well.
377
def create_ppa(distribution, person, name):
378
    """Create a PPA for `person`."""
10373.3.12 by Jeroen Vermeulen
Set external dependencies.
379
    ppa = LaunchpadObjectFactory().makeArchive(
10373.3.10 by Jeroen Vermeulen
Creating PPA as well.
380
        distribution=distribution, owner=person, name=name, virtualized=False,
381
        description="Automatically created test PPA.")
382
10373.3.12 by Jeroen Vermeulen
Set external dependencies.
383
    series_name = distribution.currentseries.name
384
    ppa.external_dependencies = (
385
        "deb http://archive.ubuntu.com/ubuntu %s "
386
        "main restricted universe multiverse\n") % series_name
387
10373.3.10 by Jeroen Vermeulen
Creating PPA as well.
388
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
389
def main(argv):
390
    options, args, log = parse_args(argv[1:])
391
392
    execute_zcml_for_scripts()
393
    txn = initZopeless(dbuser='launchpad')
394
395
    check_preconditions(options.force)
396
397
    ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
398
    clean_up(ubuntu, log)
399
400
    # Use Hoary as the root, as Breezy and Grumpy are broken.
10373.3.3 by Jeroen Vermeulen
Make the script take over much more of the manual work.
401
    populate(ubuntu, 'hoary', 'ubuntu-team', options, log)
402
403
    admin = get_person_set().getByName('name16')
10373.3.5 by Jeroen Vermeulen
Automating lots of manual steps for getting Soyuz to work.
404
    person = create_ppa_user(user_name, options, admin, log)
405
10373.3.10 by Jeroen Vermeulen
Creating PPA as well.
406
    create_ppa(ubuntu, person, 'test-ppa')
10303.1.9 by Gary Poster
merge with devel; it has been awhile!
407
10427.4.2 by Jeroen Vermeulen
Moved user/gpg creation into make-lp-user.
408
    txn.commit()
10373.3.8 by Jeroen Vermeulen
Moved instructions to the very end again.
409
    log.info("Done.")
410
10373.3.5 by Jeroen Vermeulen
Automating lots of manual steps for getting Soyuz to work.
411
    print dedent("""
10427.4.2 by Jeroen Vermeulen
Moved user/gpg creation into make-lp-user.
412
        Now start your local Launchpad with "make run_codehosting" and log
413
        into https://launchpad.dev/ as "%(email)s" with "test" as the
414
        password.
10373.3.5 by Jeroen Vermeulen
Automating lots of manual steps for getting Soyuz to work.
415
        Your user name will be %(user_name)s."""
416
        % {
417
            'email': options.email,
418
            'user_name': user_name,
419
            })
420
10373.3.2 by Jeroen Vermeulen
Integrate cleanup of playground sample data for Soyuz testing.
421
10373.3.1 by Jeroen Vermeulen
Import make-ubuntu-sane.py into the LP tree, with cleanups.
422
if __name__ == "__main__":
423
    main(sys.argv)