Merge lp:~jtv/launchpad/bug-527170 into lp:launchpad

Proposed by Jeroen T. Vermeulen
Status: Superseded
Proposed branch: lp:~jtv/launchpad/bug-527170
Merge into: lp:launchpad
Diff against target: 328 lines (+196/-12)
2 files modified
utilities/soyuz-sampledata-cleanup.py (+175/-12)
utilities/start-dev-soyuz.sh (+21/-0)
To merge this branch: bzr merge lp:~jtv/launchpad/bug-527170
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) code Needs Fixing
Review via email: mp+20433@code.launchpad.net

This proposal has been superseded by a proposal from 2010-03-02.

Commit message

Automate more manual Soyuz dev startup.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

= Bug 527170 =

This branch automates more of the manual work needed to get a working
local Soyuz setup. For the current process, see:

    https://dev.launchpad.net/Soyuz/HowToUseSoyuzLocally

I already implemented a script that does part of the work needed on the
dev sample data, based on wgrant's original, but some features are
added:

 * --amd64 option automates setup of amd64 support.

 * Creates a user ppa-user, with a fixed name.

 * Signs the Ubuntu Code of Conduct for ppa-user (with a fake key).

 * Gives you a fixed URL for adding your own GPG key.

 * --email option lets you specify your email address of choice.

 * Adds ppa-user to ubuntu-team.

The dev server won't actually be able to send email to your chosen email
address (although it will try, see the wiki page) but the choice makes
it easier for you to attach your GPG key. This is needed when you sign
uploads.

I tried automating the registration of a GPG key, or use of a dedicated
one, but no solution was really satisfactory. Well, working with gpgme
to get the real key based on your email address would have been, but it
seems that our scripting environment forces gpg to live in a temporary
directory rather than in ~/.gnupg.

This is a utilities script acting on the dev playground database, so
there's no real testing. But at least the script is exercised by the
test suite so we can detect obvious breakage:
{{{
./bin/test -vv -t sampledata-cleanup
}}}

No lint.

There's also a new script utilities/start-dev-soyuz.sh, also based on
wgrant's original. I regularlized it a bit and made sure the necessary
directories in /var/tmp are created. I have no idea how to test this
second script without (1) upsetting the test environment, or (2) porting
it to python and adding a lot of weight. For this script, used in
manual testing only and easy enough to edit, I don't think that's worth
the trouble.

Jeroen

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

There's still a problem with this that escaped my notice because zeca was retaining my GPG key from previous tests.

review: Needs Fixing (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'utilities/soyuz-sampledata-cleanup.py'
2--- utilities/soyuz-sampledata-cleanup.py 2010-02-24 12:13:29 +0000
3+++ utilities/soyuz-sampledata-cleanup.py 2010-03-02 08:52:26 +0000
4@@ -13,6 +13,9 @@
5
6 DO NOT RUN ON PRODUCTION SYSTEMS. This script deletes lots of
7 Ubuntu-related data.
8+
9+This script creates a user "ppa-user" (email ppa-user@example.com,
10+password test) who is able to create PPAs.
11 """
12
13 __metaclass__ = type
14@@ -20,9 +23,12 @@
15 import _pythonpath
16
17 from optparse import OptionParser
18-from os import getenv
19 import re
20+import os
21+import subprocess
22 import sys
23+from textwrap import dedent
24+import transaction
25
26 from zope.component import getUtility
27 from zope.event import notify
28@@ -35,20 +41,33 @@
29
30 from canonical.lp import initZopeless
31
32+from canonical.launchpad.ftests.keys_for_tests import gpgkeysdir
33+from canonical.launchpad.interfaces.account import AccountStatus
34+from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
35+from canonical.launchpad.interfaces.gpghandler import IGPGHandler
36 from canonical.launchpad.interfaces.launchpad import (
37 ILaunchpadCelebrities)
38 from canonical.launchpad.scripts import execute_zcml_for_scripts
39 from canonical.launchpad.scripts.logger import logger, logger_options
40 from canonical.launchpad.webapp.interfaces import (
41- IStoreSelector, MAIN_STORE, SLAVE_FLAVOR)
42+ IStoreSelector, MAIN_STORE, MASTER_FLAVOR, SLAVE_FLAVOR)
43
44+from lp.registry.interfaces.codeofconduct import ISignedCodeOfConductSet
45+from lp.registry.interfaces.gpg import GPGKeyAlgorithm, IGPGKeySet
46 from lp.registry.interfaces.series import SeriesStatus
47+from lp.registry.model.codeofconduct import SignedCodeOfConduct
48 from lp.soyuz.interfaces.component import IComponentSet
49+from lp.soyuz.interfaces.processor import IProcessorFamilySet
50 from lp.soyuz.interfaces.section import ISectionSet
51 from lp.soyuz.interfaces.sourcepackageformat import (
52 ISourcePackageFormatSelectionSet, SourcePackageFormat)
53 from lp.soyuz.model.section import SectionSelection
54 from lp.soyuz.model.component import ComponentSelection
55+from lp.testing.factory import LaunchpadObjectFactory
56+
57+
58+user_name = 'ppa-user'
59+default_email = '%s@example.com' % user_name
60
61
62 class DoNotRunOnProduction(Exception):
63@@ -82,7 +101,7 @@
64 # For some configs it's just absolutely clear this script shouldn't
65 # run. Don't even accept --force there.
66 forbidden_configs = re.compile('(edge|lpnet|production)')
67- current_config = getenv('LPCONFIG', 'an unknown config')
68+ current_config = os.getenv('LPCONFIG', 'an unknown config')
69 if forbidden_configs.match(current_config):
70 raise DoNotRunOnProduction(
71 "I won't delete Ubuntu data on %s and you can't --force me."
72@@ -95,22 +114,31 @@
73 :return: (options, args, logger)
74 """
75 parser = OptionParser(
76- description="Delete existing Ubuntu releases and set up new ones.")
77+ description="Set up fresh Ubuntu series and %s identity." % user_name)
78 parser.add_option('-f', '--force', action='store_true', dest='force',
79 help="DANGEROUS: run even if the database looks production-like.")
80 parser.add_option('-n', '--dry-run', action='store_true', dest='dry_run',
81 help="Do not commit changes.")
82+ parser.add_option('-A', '--amd64', action='store_true', dest='amd64',
83+ help="Support amd64 architecture.")
84+ parser.add_option('-e', '--email', action='store', dest='email',
85+ default=default_email,
86+ help=(
87+ "Email address to use for %s. Should match your GPG key."
88+ % user_name))
89+
90 logger_options(parser)
91
92 options, args = parser.parse_args(arguments)
93+
94 return options, args, logger(options)
95
96
97-def get_person(name):
98+def get_person_set():
99 """Return `IPersonSet` utility."""
100 # Avoid circular import.
101 from lp.registry.interfaces.person import IPersonSet
102- return getUtility(IPersonSet).getByName(name)
103+ return getUtility(IPersonSet)
104
105
106 def retire_series(distribution):
107@@ -150,6 +178,26 @@
108 components = main restricted universe multiverse'''
109
110
111+def add_architecture(distroseries, architecture_name):
112+ """Add a DistroArchSeries for the given architecture to `distroseries`."""
113+ # Avoid circular import.
114+ from lp.soyuz.model.distroarchseries import DistroArchSeries
115+
116+ store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
117+ family = getUtility(IProcessorFamilySet).getByName(architecture_name)
118+ archseries = DistroArchSeries(
119+ distroseries=distroseries, processorfamily=family,
120+ owner=distroseries.owner, official=True,
121+ architecturetag=architecture_name)
122+ store.add(archseries)
123+
124+
125+def add_architectures(distroseries, options):
126+ """Add support for additional architectures as specified by options."""
127+ if options.amd64:
128+ add_architecture(distroseries, 'amd64')
129+
130+
131 def create_sections(distroseries):
132 """Set up some sections for `distroseries`."""
133 section_names = (
134@@ -253,7 +301,7 @@
135 # published binaries without corresponding sources.
136
137 log.info("Deleting all items in official archives...")
138- retire_distro_archives(distribution, get_person('name16'))
139+ retire_distro_archives(distribution, get_person_set().getByName('name16'))
140
141 # Disable publishing of all PPAs, as they probably have broken
142 # publishings too.
143@@ -271,7 +319,7 @@
144 utility.add(distroseries, format)
145
146
147-def populate(distribution, parent_series_name, uploader_name, log):
148+def populate(distribution, parent_series_name, uploader_name, options, log):
149 """Set up sample data on `distribution`."""
150 parent_series = distribution.getSeries(parent_series_name)
151
152@@ -281,15 +329,115 @@
153
154 log.info("Configuring sections...")
155 create_sections(parent_series)
156+ add_architectures(parent_series, options)
157
158 log.info("Configuring components and permissions...")
159- create_components(parent_series, get_person(uploader_name))
160+ uploader = get_person_set().getByName(uploader_name)
161+ create_components(parent_series, uploader)
162
163 set_source_package_format(parent_series)
164
165 create_sample_series(parent_series, log)
166
167
168+def sign_code_of_conduct(person, log):
169+ """Sign Ubuntu Code of Conduct for `person`, if necessary."""
170+ if person.is_ubuntu_coc_signer:
171+ # Already signed.
172+ return
173+
174+ log.info("Signing Ubuntu code of conduct.")
175+ signedcocset = getUtility(ISignedCodeOfConductSet)
176+ person_id = person.id
177+ if signedcocset.searchByUser(person_id).count() == 0:
178+ fake_gpg_key = LaunchpadObjectFactory().makeGPGKey(person)
179+ Store.of(person).add(SignedCodeOfConduct(
180+ owner=person, signingkey=fake_gpg_key,
181+ signedcode="Normally a signed CoC would go here.", active=True))
182+
183+
184+def create_ppa_user(username, options, approver, log):
185+ """Create new user, with password "test," and sign code of conduct."""
186+ # Avoid circular import.
187+ from lp.registry.interfaces.person import PersonCreationRationale
188+
189+ store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
190+
191+ log.info("Creating %s with email address '%s'." % (
192+ user_name, options.email))
193+
194+ person = get_person_set().getByName(username)
195+ if person is None:
196+ person, email = get_person_set().createPersonAndEmail(
197+ options.email, PersonCreationRationale.OWNER_CREATED_LAUNCHPAD,
198+ name=username, displayname=username, password='test')
199+ email.status = EmailAddressStatus.PREFERRED
200+ email.account.status = AccountStatus.ACTIVE
201+
202+ if not options.dry_run:
203+ transaction.commit()
204+
205+ sign_code_of_conduct(person, log)
206+ get_person_set().getByName('ubuntu-team').addMember(person, approver)
207+
208+ return person
209+
210+
211+def parse_fingerprints(gpg_output):
212+ """Find key fingerprints in "gpg --fingerprint <email>" output."""
213+ line_prefix = re.compile('\s*Key fingerprint\s*=\s*')
214+ return [
215+ ''.join(re.sub(line_prefix, '', line).split())
216+ for line in gpg_output.splitlines()
217+ if line_prefix.match(line)
218+ ]
219+
220+
221+def add_gpg_key(person, fingerprint, log):
222+ """Add the GPG key with the given fingerprint to `person`."""
223+ log.info("Adding GPG key %s" % fingerprint)
224+ gpghandler = getUtility(IGPGHandler)
225+ key = gpghandler.retrieveKey(fingerprint)
226+
227+ gpgkeyset = getUtility(IGPGKeySet)
228+ if gpgkeyset.getByFingerprint(fingerprint) is not None:
229+ # We already have this key.
230+ return
231+
232+ algorithm = GPGKeyAlgorithm.items[key.algorithm]
233+ can_encrypt = True
234+ lpkey = gpgkeyset.new(
235+ person.id, key.keyid, fingerprint, key.keysize, algorithm,
236+ active=True, can_encrypt=can_encrypt)
237+ Store.of(person).add(lpkey)
238+ log.info("Created Launchpad key %s" % lpkey.displayname)
239+
240+
241+def attach_gpg_keys(options, person, log):
242+ """Attach the selected GPG key to `person`."""
243+ log.info("Looking for GPG keys.")
244+
245+ # Need to override GNUPGHOME or we'll get a dummy GPG in a temp
246+ # directory, which won't find any keys.
247+ env = os.environ.copy()
248+ if 'GNUPGHOME' in env:
249+ del env['GNUPGHOME']
250+ pipe = subprocess.Popen(
251+ ['gpg', '--fingerprint', options.email], env=env,
252+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
253+ stdout, stderr = pipe.communicate()
254+ if stderr != '':
255+ log.error(stderr)
256+ if pipe.returncode != 0:
257+ raise Exception("GPG error.")
258+
259+ fingerprints = parse_fingerprints(stdout)
260+ if len(fingerprints) == 0:
261+ log.warn("No GPG key fingerprints found!")
262+ for fingerprint in fingerprints:
263+ add_gpg_key(person, fingerprint, log)
264+
265+
266 def main(argv):
267 options, args, log = parse_args(argv[1:])
268
269@@ -302,8 +450,24 @@
270 clean_up(ubuntu, log)
271
272 # Use Hoary as the root, as Breezy and Grumpy are broken.
273- populate(ubuntu, 'hoary', 'ubuntu-team', log)
274-
275+ populate(ubuntu, 'hoary', 'ubuntu-team', options, log)
276+
277+ admin = get_person_set().getByName('name16')
278+ person = create_ppa_user(user_name, options, admin, log)
279+
280+ gave_email = (options.email != default_email)
281+ print dedent("""
282+ Now start your local Launchpad with "make run" and log into
283+ https://launchpad.dev/ as "%(email)s" with "test" as the password.
284+ Your user name will be %(user_name)s."""
285+ % {
286+ 'email': options.email,
287+ 'user_name': user_name,
288+ })
289+
290+ if gave_email:
291+ attach_gpg_keys(options, person, log)
292+
293 if options.dry_run:
294 txn.abort()
295 else:
296@@ -311,6 +475,5 @@
297
298 log.info("Done.")
299
300-
301 if __name__ == "__main__":
302 main(sys.argv)
303
304=== added file 'utilities/start-dev-soyuz.sh'
305--- utilities/start-dev-soyuz.sh 1970-01-01 00:00:00 +0000
306+++ utilities/start-dev-soyuz.sh 2010-03-02 08:52:26 +0000
307@@ -0,0 +1,21 @@
308+#!/bin/sh -e
309+# Start up Soyuz for local testing on a dev machine.
310+
311+start_twistd() {
312+ # Start twistd for service $1.
313+ mkdir -p "/var/tmp/$1"
314+ echo "Starting $1."
315+ bin/twistd \
316+ --logfile "/var/tmp/development-$1.log" \
317+ --pidfile "/var/tmp/development-$1.pid" \
318+ -y "daemons/$1.tac"
319+}
320+
321+start_twistd zeca
322+start_twistd buildd-manager
323+
324+echo "Starting poppy."
325+mkdir -p /var/tmp/poppy
326+bin/py daemons/poppy-upload.py /var/tmp/poppy/incoming 2121 &
327+
328+echo "Done."