10637.3.1
by Guilherme Salgado
Use the default python version instead of a hard-coded version |
1 |
#!/usr/bin/python -S
|
8687.15.22
by Karl Fogel
Add the copyright header block to the remaining .py files. |
2 |
#
|
3 |
# Copyright 2009 Canonical Ltd. This software is licensed under the
|
|
4 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
5427.2.2
by Barry Warsaw
A script to be used on staging to synchronize the Mailman data structures with |
5 |
|
5655.1.1
by Barry Warsaw
Update the sync script so that it properly handles synchronizing Mailman data |
6 |
"""Sync Mailman data from one Launchpad to another."""
|
7 |
||
6916.1.1
by Curtis Hovey
Fixed comment formats to fidn missing persons, dates, and some bugs. |
8 |
# XXX BarryWarsaw 2008-02-12:
|
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
9 |
# Things this script does NOT do correctly.
|
5655.1.1
by Barry Warsaw
Update the sync script so that it properly handles synchronizing Mailman data |
10 |
#
|
11 |
# - Fix up the deactivated lists. This isn't done because that data lives in
|
|
12 |
# the backed up tar file, so handling this would mean untar'ing, tricking
|
|
13 |
# Mailman into loading the pickle (or manually loading and patching), then
|
|
14 |
# re-tar'ing. I don't think it's worth it because the only thing that will
|
|
15 |
# be broken is if a list that's deactivated on production is re-activated on
|
|
16 |
# staging.
|
|
17 |
#
|
|
18 |
# - Backpatch all the message footers and RFC 2369 headers of the messages in
|
|
19 |
# the archive. To do this, we'd have to iterate through all messages,
|
|
20 |
# tweaking the List-* headers (easy) and ripping apart the footers,
|
|
21 |
# recalculating them and reattaching them (difficult). Doing the iteration
|
|
22 |
# and update is quite painful in Python 2.4, but would be easier with Python
|
|
23 |
# 2.5's new mailbox module. /Then/ we'd have to regenerate the archives.
|
|
24 |
# Not doing this means that some of the links in staging's MHonArc archive
|
|
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
25 |
# will point to production archives.
|
5655.1.1
by Barry Warsaw
Update the sync script so that it properly handles synchronizing Mailman data |
26 |
|
9641.1.7
by Gary Poster
fix scripts to work |
27 |
# pylint: disable-msg=W0403
|
28 |
import _pythonpath |
|
29 |
||
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
30 |
import os |
5427.2.2
by Barry Warsaw
A script to be used on staging to synchronize the Mailman data structures with |
31 |
import sys |
32 |
import logging |
|
33 |
import textwrap |
|
34 |
import subprocess |
|
35 |
||
5655.1.1
by Barry Warsaw
Update the sync script so that it properly handles synchronizing Mailman data |
36 |
from zope.component import getUtility |
6667.1.2
by Barry Warsaw
Lint cleanups |
37 |
from zope.security.proxy import removeSecurityProxy |
5655.1.1
by Barry Warsaw
Update the sync script so that it properly handles synchronizing Mailman data |
38 |
|
5427.2.2
by Barry Warsaw
A script to be used on staging to synchronize the Mailman data structures with |
39 |
from canonical.config import config |
11882.2.2
by Jonathan Lange
Clear up a heck of a lot of imports from canonical.launchpad.interfaces. |
40 |
from canonical.launchpad.interfaces.emailaddress import IEmailAddressSet |
41 |
from lp.registry.interfaces.mailinglist import IMailingListSet |
|
42 |
from lp.registry.interfaces.person import IPersonSet |
|
11568.1.1
by Curtis Hovey
Move mailman to lp.services. Fixed staging.txt and logging.txt that were broken by |
43 |
from lp.services.mailman.config import configure_prefix |
11684.1.2
by Curtis Hovey
Convert mlist-sync to a LaunchpadScript. Refactored the staging test to reliably run. |
44 |
from lp.services.scripts.base import LaunchpadScript |
5427.2.2
by Barry Warsaw
A script to be used on staging to synchronize the Mailman data structures with |
45 |
|
46 |
||
5655.1.1
by Barry Warsaw
Update the sync script so that it properly handles synchronizing Mailman data |
47 |
RSYNC_OPTIONS = ('-avz', '--delete') |
48 |
RSYNC_COMMAND = '/usr/bin/rsync' |
|
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
49 |
RSYNC_SUBDIRECTORIES = ('archives', 'backups', 'lists', 'mhonarc') |
50 |
SPACE = ' ' |
|
51 |
||
52 |
||
11684.1.2
by Curtis Hovey
Convert mlist-sync to a LaunchpadScript. Refactored the staging test to reliably run. |
53 |
class MailingListSyncScript(LaunchpadScript): |
5427.2.2
by Barry Warsaw
A script to be used on staging to synchronize the Mailman data structures with |
54 |
"""
|
5655.1.1
by Barry Warsaw
Update the sync script so that it properly handles synchronizing Mailman data |
55 |
%prog [options] source_url
|
56 |
||
57 |
Sync the Mailman data structures between production and staging. This
|
|
58 |
takes the most efficient route by rsync'ing over the list pickles, raw
|
|
59 |
archive mboxes, and mhonarc files, then it fixes up anything that needs
|
|
60 |
fixing. This does /not/ sync over any qfiles because staging doesn't send
|
|
61 |
emails anyway.
|
|
62 |
||
63 |
source_url is required and it is the rsync source url which contains
|
|
64 |
mailman's var directory. The destination is taken from the launchpad.conf
|
|
65 |
file.
|
|
5427.2.2
by Barry Warsaw
A script to be used on staging to synchronize the Mailman data structures with |
66 |
"""
|
67 |
||
68 |
loglevel = logging.INFO |
|
69 |
description = 'Sync the Mailman data structures with the database.' |
|
70 |
||
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
71 |
def __init__(self, name, dbuser=None): |
5427.2.2
by Barry Warsaw
A script to be used on staging to synchronize the Mailman data structures with |
72 |
self.usage = textwrap.dedent(self.__doc__) |
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
73 |
super(MailingListSyncScript, self).__init__(name, dbuser) |
74 |
||
5719.1.1
by Barry Warsaw
Fix to handle teams with both a mailing list and a contact address (i.e. 2 |
75 |
def add_my_options(self): |
76 |
"""See `LaunchpadScript`."""
|
|
77 |
# Add optional override of production mailing list host name. In real
|
|
78 |
# use, it's always going to be lists.launchpad.net so that makes a
|
|
79 |
# reasonable default. Some testing environments may override this.
|
|
80 |
self.parser.add_option('--hostname', default='lists.launchpad.net', |
|
81 |
help=('The hostname for the production ' |
|
82 |
'mailing list system. This is used to '
|
|
83 |
'resolve teams which have multiple '
|
|
84 |
'email addresses.')) |
|
85 |
||
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
86 |
def syncMailmanDirectories(self, source_url): |
87 |
"""Synchronize the Mailman directories.
|
|
88 |
||
89 |
:param source_url: the base url of the source
|
|
90 |
"""
|
|
91 |
# This can't be done at module global scope.
|
|
5723.8.8
by Barry Warsaw
Fix lint issues, either by suppression or fix. |
92 |
# pylint: disable-msg=F0401
|
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
93 |
from Mailman import mm_cfg |
5655.1.1
by Barry Warsaw
Update the sync script so that it properly handles synchronizing Mailman data |
94 |
# Start by rsync'ing over the entire $vardir/lists, $vardir/archives,
|
95 |
# $vardir/backups, and $vardir/mhonarc directories. We specifically
|
|
96 |
# do not rsync the data, locks, logs, qfiles, or spam directories.
|
|
5863.9.1
by Curtis Hovey
Updated configs and code to used simple datatypes. Changes may still be needed aftert testing. |
97 |
destination_url = config.mailman.build_var_dir |
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
98 |
# Do one rsync for all subdirectories.
|
99 |
rsync_command = [RSYNC_COMMAND] |
|
100 |
rsync_command.extend(RSYNC_OPTIONS) |
|
101 |
rsync_command.append('--exclude=%s' % mm_cfg.MAILMAN_SITE_LIST) |
|
102 |
rsync_command.append('--exclude=%s.mbox' % mm_cfg.MAILMAN_SITE_LIST) |
|
103 |
rsync_command.extend(os.path.join(source_url, subdirectory) |
|
104 |
for subdirectory in RSYNC_SUBDIRECTORIES) |
|
105 |
rsync_command.append(destination_url) |
|
106 |
self.logger.info('executing: %s', SPACE.join(rsync_command)) |
|
107 |
process = subprocess.Popen(rsync_command, |
|
108 |
stdout=subprocess.PIPE, |
|
109 |
stderr=subprocess.PIPE) |
|
110 |
stdout, stderr = process.communicate() |
|
111 |
if process.returncode == 0: |
|
112 |
self.logger.info('%s', stdout) |
|
113 |
else: |
|
114 |
self.logger.error('rsync command failed with exit status: %s', |
|
115 |
process.returncode) |
|
116 |
self.logger.error('STDOUT:\n%s\nSTDERR:\n%s', stdout, stderr) |
|
117 |
return process.returncode |
|
118 |
||
119 |
def fixHostnames(self): |
|
120 |
"""Fix up the host names in Mailman and the LP database."""
|
|
121 |
# These can't be done at module global scope.
|
|
5723.8.8
by Barry Warsaw
Fix lint issues, either by suppression or fix. |
122 |
# pylint: disable-msg=F0401
|
5655.1.1
by Barry Warsaw
Update the sync script so that it properly handles synchronizing Mailman data |
123 |
from Mailman import Utils |
5427.2.2
by Barry Warsaw
A script to be used on staging to synchronize the Mailman data structures with |
124 |
from Mailman import mm_cfg |
5655.1.1
by Barry Warsaw
Update the sync script so that it properly handles synchronizing Mailman data |
125 |
from Mailman.MailList import MailList |
126 |
||
127 |
# Grab a couple of useful components.
|
|
128 |
email_address_set = getUtility(IEmailAddressSet) |
|
129 |
mailing_list_set = getUtility(IMailingListSet) |
|
130 |
||
131 |
# Clean things up per mailing list.
|
|
132 |
for list_name in Utils.list_names(): |
|
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
133 |
# Skip the site list.
|
134 |
if list_name == mm_cfg.MAILMAN_SITE_LIST: |
|
135 |
continue
|
|
136 |
||
5655.1.1
by Barry Warsaw
Update the sync script so that it properly handles synchronizing Mailman data |
137 |
# The first thing to clean up is the mailing list pickles. There
|
138 |
# are things like host names in some attributes that need to be
|
|
139 |
# converted. The following opens a locked list.
|
|
140 |
mailing_list = MailList(list_name) |
|
141 |
try: |
|
142 |
mailing_list.host_name = mm_cfg.DEFAULT_EMAIL_HOST |
|
143 |
mailing_list.web_page_url = ( |
|
144 |
mm_cfg.DEFAULT_URL_PATTERN % mm_cfg.DEFAULT_URL_HOST) |
|
145 |
mailing_list.Save() |
|
146 |
finally: |
|
147 |
mailing_list.Unlock() |
|
148 |
||
149 |
# Patch up the email address for the list in the Launchpad
|
|
150 |
# database.
|
|
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
151 |
lp_mailing_list = mailing_list_set.get(list_name) |
152 |
if lp_mailing_list is None: |
|
5719.1.1
by Barry Warsaw
Fix to handle teams with both a mailing list and a contact address (i.e. 2 |
153 |
# We found a mailing list in Mailman that does not exist in
|
154 |
# the Launchpad database. This can happen if we rsync'd the
|
|
155 |
# Mailman directories after the lists were created, but we
|
|
156 |
# copied the LP database /before/ the lists were created.
|
|
157 |
# If we don't delete the Mailman lists, we won't be able to
|
|
158 |
# create the mailing lists on staging.
|
|
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
159 |
self.logger.error('No LP mailing list for: %s', list_name) |
5719.1.1
by Barry Warsaw
Fix to handle teams with both a mailing list and a contact address (i.e. 2 |
160 |
self.deleteMailmanList(list_name) |
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
161 |
continue
|
162 |
||
5723.8.7
by Barry Warsaw
Add support for testing the mlist-sync.py script, and also merge the changes |
163 |
# Clean up the team email addresses corresponding to their mailing
|
164 |
# lists. Note that teams can have two email addresses if they
|
|
165 |
# have a different contact address.
|
|
5719.1.1
by Barry Warsaw
Fix to handle teams with both a mailing list and a contact address (i.e. 2 |
166 |
team = getUtility(IPersonSet).getByName(list_name) |
167 |
mlist_addresses = email_address_set.getByPerson(team) |
|
168 |
if mlist_addresses.count() == 0: |
|
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
169 |
self.logger.error('No LP email address for: %s', list_name) |
12736.14.4
by Curtis Hovey
Added test to verify the teams second email address is not updated by mlist-sync. |
170 |
else: |
171 |
# Teams can have both a mailing list and a contact address.
|
|
5719.1.1
by Barry Warsaw
Fix to handle teams with both a mailing list and a contact address (i.e. 2 |
172 |
old_address = '%s@%s' % (list_name, self.options.hostname) |
173 |
for email_address in mlist_addresses: |
|
174 |
if email_address.email == old_address: |
|
6667.1.2
by Barry Warsaw
Lint cleanups |
175 |
new_address = lp_mailing_list.address |
176 |
removeSecurityProxy(email_address).email = new_address |
|
177 |
self.logger.info('%s -> %s', old_address, new_address) |
|
5719.1.1
by Barry Warsaw
Fix to handle teams with both a mailing list and a contact address (i.e. 2 |
178 |
break
|
179 |
else: |
|
180 |
self.logger.error('No change to LP email address for: %s', |
|
181 |
list_name) |
|
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
182 |
|
5719.1.1
by Barry Warsaw
Fix to handle teams with both a mailing list and a contact address (i.e. 2 |
183 |
def deleteMailmanList(self, list_name): |
184 |
"""Delete all Mailman data structures for `list_name`."""
|
|
185 |
mailman_bindir = os.path.normpath(os.path.join( |
|
5863.9.7
by Curtis Hovey
Refacortings per review. |
186 |
configure_prefix(config.mailman.build_prefix), 'bin')) |
5719.1.1
by Barry Warsaw
Fix to handle teams with both a mailing list and a contact address (i.e. 2 |
187 |
process = subprocess.Popen(('./rmlist', '-a', list_name), |
188 |
stdout=subprocess.PIPE, |
|
189 |
stderr=subprocess.PIPE, |
|
190 |
cwd=mailman_bindir) |
|
191 |
stdout, stderr = process.communicate() |
|
192 |
if process.returncode == 0: |
|
193 |
self.logger.info('%s', stdout) |
|
194 |
else: |
|
195 |
self.logger.error('rmlist command failed with exit status: %s', |
|
196 |
process.returncode) |
|
197 |
self.logger.error('STDOUT:\n%s\nSTDERR:\n%s', stdout, stderr) |
|
198 |
# Keep going.
|
|
199 |
||
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
200 |
def main(self): |
11684.1.2
by Curtis Hovey
Convert mlist-sync to a LaunchpadScript. Refactored the staging test to reliably run. |
201 |
"""See `LaunchpadScript`."""
|
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
202 |
source_url = None |
203 |
if len(self.args) == 0: |
|
204 |
self.parser.error('Missing source_url') |
|
205 |
elif len(self.args) > 1: |
|
206 |
self.parser.error('Too many arguments') |
|
207 |
else: |
|
208 |
source_url = self.args[0] |
|
209 |
||
210 |
# We need to get to the Mailman API. Set up the paths so that Mailman
|
|
211 |
# can be imported. This can't be done at module global scope.
|
|
5863.9.7
by Curtis Hovey
Refacortings per review. |
212 |
mailman_path = configure_prefix(config.mailman.build_prefix) |
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
213 |
sys.path.append(mailman_path) |
214 |
||
215 |
retcode = self.syncMailmanDirectories(source_url) |
|
216 |
if retcode != 0: |
|
217 |
return retcode |
|
218 |
||
219 |
self.fixHostnames() |
|
5655.1.1
by Barry Warsaw
Update the sync script so that it properly handles synchronizing Mailman data |
220 |
|
221 |
# All done; commit the database changes.
|
|
222 |
self.txn.commit() |
|
223 |
return 0 |
|
5427.2.2
by Barry Warsaw
A script to be used on staging to synchronize the Mailman data structures with |
224 |
|
225 |
||
226 |
if __name__ == '__main__': |
|
5655.1.3
by Barry Warsaw
Changes in response to Francis's review: |
227 |
script = MailingListSyncScript('scripts.mlist-sync', 'mlist-sync') |
228 |
status = script.lock_and_run() |
|
5427.2.2
by Barry Warsaw
A script to be used on staging to synchronize the Mailman data structures with |
229 |
sys.exit(status) |