Merge lp:~jtv/launchpad/bug-527170 into lp:launchpad
- bug-527170
- Merge into devel
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 |
Related bugs: |
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.
Description of the change
To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote : | # |
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." |
= 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/ HowToUseSoyuzLo cally
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