def monkey_patch(mailman_path, config): """Monkey-patch an installed Mailman 2.1 tree. Rather than maintain a forked tree of Mailman 2.1, we apply a set of changes to an installed Mailman tree. This tree can be found rooted at mailman_path. This should usually mean just copying a file from this directory into mailman_path. Rather than build a lot of process into the mix, just hard code each transformation here. """ # Hook Mailman to Launchpad by writing a custom mm_cfg.py file which adds # the top of our Launchpad tree to Mailman's sys.path. The mm_cfg.py file # won't do much more than set up sys.path and do an from-import-* to get # everything that doesn't need to be dynamically calculated at run-time. # Things that can only be calculated at run-time are written to mm_cfg.py # now. It's okay to simply overwrite any existing mm_cfg.py, since we'll # provide everything Mailman needs. # # Remember, don't rely on Launchpad's config object in the mm_cfg.py file # or in the lp.services.mailman.monkeypatches.defaults module because # Mailman will not be able to initialize Launchpad's configuration system. # Instead, anything that's needed from config should be written to the # mm_cfg.py file now. # # Calculate the parent directory of the lp module. This directory # will get appended to Mailman's sys.path. import lp from lp.services.mailman.config import configure_siteowner launchpad_top = os.path.abspath( os.path.join(os.path.dirname(lp.__file__), os.pardir, os.pardir)) # Read the email footer template for all Launchpad messages. from lp.services.mail.helpers import get_email_template footer = get_email_template( 'mailinglist-footer.txt', app='services/mailman/monkeypatches') # Write the mm_cfg.py file, filling in the dynamic values now. host, port = as_host_port(config.mailman.smtp) owner_address, owner_password = configure_siteowner( config.mailman.build_site_list_owner) config_path_in = os.path.join(os.path.dirname(__file__), 'mm_cfg.py.in') config_file_in = open(config_path_in) try: config_template = config_file_in.read() finally: config_file_in.close() config_path_out = os.path.join(mailman_path, 'Mailman', 'mm_cfg.py') config_file_out = open(config_path_out, 'w') try: print >> config_file_out, config_template % dict( launchpad_top=launchpad_top, smtp_host=host, smtp_port=port, smtp_max_rcpts=config.mailman.smtp_max_rcpts, smtp_max_sesions_per_connection=( config.mailman.smtp_max_sesions_per_connection), xmlrpc_url=config.mailman.xmlrpc_url, xmlrpc_sleeptime=config.mailman.xmlrpc_runner_sleep, xmlrpc_timeout=config.mailman.xmlrpc_timeout, xmlrpc_subscription_batch_size=( config.mailman.subscription_batch_size), site_list_owner=owner_address, list_help_header=config.mailman.list_help_header, list_subscription_headers=( config.mailman.list_subscription_headers), archive_url_template=config.mailman.archive_url_template, list_owner_header_template=( config.mailman.list_owner_header_template), footer=footer, var_dir=config.mailman.build_var_dir, shared_secret=config.mailman.shared_secret, soft_max_size=config.mailman.soft_max_size, hard_max_size=config.mailman.hard_max_size, register_bounces_every=config.mailman.register_bounces_every, ) finally: config_file_out.close() # Mailman's qrunner system requires runner modules to live in the # Mailman.Queue package. Set things up so that there's a hook module in # there for the XMLRPCRunner. runner_path = os.path.join(mailman_path, 'Mailman', 'Queue', 'XMLRPCRunner.py') runner_file = open(runner_path, 'w') try: print >> runner_file, ( 'from lp.services.mailman.monkeypatches.xmlrpcrunner ' 'import *') finally: runner_file.close() # Install our handler wrapper modules so that Mailman can find them. Most # of the actual code of the handler comes from our monkey patches modules. for mm_name, lp_name in (('LaunchpadMember', 'lphandler'), ('LaunchpadHeaders', 'lpheaders'), ('LPStanding', 'lpstanding'), ('LPModerate', 'lpmoderate'), ('LPSize', 'lpsize'), ): handler_path = os.path.join( mailman_path, 'Mailman', 'Handlers', mm_name + '.py') handler_file = open(handler_path, 'w') try: package = 'lp.services.mailman.monkeypatches' module = package + '.' + lp_name print >> handler_file, 'from', module, 'import *' finally: handler_file.close() here = os.path.dirname(__file__) # Install the MHonArc control file. mhonarc_rc_file = os.path.join(here, 'lp-mhonarc-common.mrc') runtime_data_dir = os.path.join(config.mailman.build_var_dir, 'data') shutil.copy(mhonarc_rc_file, runtime_data_dir) # Install the launchpad site templates. launchpad_template_path = os.path.join(here, 'sitetemplates') site_template_path = os.path.join(mailman_path, 'templates', 'site') if os.path.isdir(site_template_path): shutil.rmtree(site_template_path) shutil.copytree(launchpad_template_path, site_template_path)
def build_mailman(): # Build and install Mailman if it is enabled and not yet built. if not config.mailman.build: # There's nothing to do. return 0 mailman_path = configure_prefix(config.mailman.build_prefix) mailman_bin = os.path.join(mailman_path, 'bin') var_dir = os.path.abspath(config.mailman.build_var_dir) executable = os.path.abspath('bin/py') # If we can import the package, we assume Mailman is properly built at # the least. This does not catch re-installs that might be necessary # should our copy in sourcecode be updated. Do that manually. try: import Mailman except ImportError: need_build = need_install = True else: need_build = need_install = False # Make sure that the configure prefix is correct, in case this tree # was moved after building Mailman. try: from Mailman import Defaults except ImportError: need_build = need_install = True else: if Defaults.PYTHON != executable: need_build = need_install = True # We'll need to remove this; "make install" won't overwrite # the existing file, and then installation will fail due to # it having the wrong sys.path. try: os.unlink( os.path.join(mailman_path, 'Mailman', 'mm_cfg.py')) except OSError as e: if e.errno != errno.ENOENT: raise if not need_install: # Also check for Launchpad-specific bits stuck into the source tree by # monkey_patch(), in case this is half-installed. See # <https://bugs.launchpad.net/launchpad-registry/+bug/683486>. try: from Mailman.Queue import XMLRPCRunner from Mailman.Handlers import LPModerate except ImportError: # Monkey patches not present, redo install and patch steps. need_install = True # Make sure the target directories exist and have the correct # permissions, otherwise configure will complain. user, group = as_username_groupname(config.mailman.build_user_group) # Now work backwards to get the uid and gid try: uid = pwd.getpwnam(user).pw_uid except KeyError: print('No user found:', user, file=sys.stderr) sys.exit(1) try: gid = grp.getgrnam(group).gr_gid except KeyError: print('No group found:', group, file=sys.stderr) sys.exit(1) # Ensure that the var_dir exists, is owned by the user:group, and has # the necessary permissions. Set the mode separately after the # makedirs() call because some platforms ignore mkdir()'s mode (though # I think Linux does not ignore it -- better safe than sorry). try: os.makedirs(var_dir) except OSError as e: if e.errno != errno.EEXIST: raise else: # Just created the var directory, will need to install mailmain bits. need_install = True os.chown(var_dir, uid, gid) os.chmod(var_dir, 0o2775) # Skip mailman setup if nothing so far has shown a reinstall needed. if not need_install: return 0 mailman_source = os.path.join('sourcecode', 'mailman') if config.mailman.build_host_name: build_host_name = config.mailman.build_host_name else: build_host_name = socket.getfqdn() # Build and install the Mailman software. Note that we don't care about # --with-cgi-gid because we're not going to use that Mailman subsystem. configure_args = ( './configure', '--prefix', mailman_path, '--with-var-prefix=' + var_dir, '--with-python=' + executable, '--with-username='******'--with-groupname=' + group, '--with-mail-gid=' + group, '--with-mailhost=' + build_host_name, '--with-urlhost=' + build_host_name, ) if need_build: # Configure. retcode = subprocess.call(configure_args, cwd=mailman_source) if retcode: print('Could not configure Mailman:', file=sys.stderr) sys.exit(retcode) # Make. retcode = subprocess.call(('make', ), cwd=mailman_source) if retcode: print('Could not make Mailman.', file=sys.stderr) sys.exit(retcode) retcode = subprocess.call(('make', 'install'), cwd=mailman_source) if retcode: print('Could not install Mailman.', file=sys.stderr) sys.exit(retcode) # Symlink Mailman's Python modules into the import path. try: os.unlink(os.path.join('lib', 'Mailman')) except OSError as e: if e.errno != errno.ENOENT: raise os.symlink(os.path.join('mailman', 'Mailman'), os.path.join('lib', 'Mailman')) # Try again to import the package. try: import Mailman except ImportError: print('Could not import the Mailman package', file=sys.stderr) return 1 # Check to see if the site list exists. The output can go to /dev/null # because we don't really care about it. The site list exists if # config_list returns a zero exit status, otherwise it doesn't # (probably). Before we can do this however, we must monkey patch # Mailman, otherwise mm_cfg.py won't be set up correctly. monkey_patch(mailman_path, config) import Mailman.mm_cfg retcode = subprocess.call( ('./config_list', '-o', '/dev/null', Mailman.mm_cfg.MAILMAN_SITE_LIST), cwd=mailman_bin, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if retcode: addr, password = configure_siteowner( config.mailman.build_site_list_owner) # The site list does not yet exist, so create it now. retcode = subprocess.call( ('./newlist', '--quiet', '--emailhost=' + build_host_name, Mailman.mm_cfg.MAILMAN_SITE_LIST, addr, password), cwd=mailman_bin) if retcode: print('Could not create site list', file=sys.stderr) return retcode retcode = configure_site_list(mailman_bin, Mailman.mm_cfg.MAILMAN_SITE_LIST) if retcode: print('Could not configure site list', file=sys.stderr) return retcode # Create a directory to hold the gzip'd tarballs for the directories of # deactivated lists. try: os.mkdir(os.path.join(Mailman.mm_cfg.VAR_PREFIX, 'backups')) except OSError as e: if e.errno != errno.EEXIST: raise return 0
def build_mailman(): # Build and install Mailman if it is enabled and not yet built. if not config.mailman.build: # There's nothing to do. return 0 mailman_path = configure_prefix(config.mailman.build_prefix) mailman_bin = os.path.join(mailman_path, 'bin') var_dir = os.path.abspath(config.mailman.build_var_dir) # If we can import the package, we assume Mailman is properly built at # the least. This does not catch re-installs that might be necessary # should our copy in sourcecode be updated. Do that manually. sys.path.append(mailman_path) try: import Mailman except ImportError: # sys.path_importer_cache is a mapping of elements of sys.path to # importer objects used to handle them. In Python2.5+ when an element # of sys.path is found to not exist on disk, a NullImporter is created # and cached - this causes Python to never bother re-inspecting the # disk for that path element. We must clear that cache element so that # our second attempt to import MailMan after building it will actually # check the disk. del sys.path_importer_cache[mailman_path] need_build = need_install = True else: need_build = need_install = False # Also check for Launchpad-specific bits stuck into the source tree by # monkey_patch(), in case this is half-installed. See # <https://bugs.launchpad.net/launchpad-registry/+bug/683486>. try: from Mailman.Queue import XMLRPCRunner from Mailman.Handlers import LPModerate except ImportError: # Monkey patches not present, redo install and patch steps. need_install = True # Make sure the target directories exist and have the correct # permissions, otherwise configure will complain. user, group = as_username_groupname(config.mailman.build_user_group) # Now work backwards to get the uid and gid try: uid = pwd.getpwnam(user).pw_uid except KeyError: print >> sys.stderr, 'No user found:', user sys.exit(1) try: gid = grp.getgrnam(group).gr_gid except KeyError: print >> sys.stderr, 'No group found:', group sys.exit(1) # Ensure that the var_dir exists, is owned by the user:group, and has # the necessary permissions. Set the mode separately after the # makedirs() call because some platforms ignore mkdir()'s mode (though # I think Linux does not ignore it -- better safe than sorry). try: os.makedirs(var_dir) except OSError as e: if e.errno != errno.EEXIST: raise else: # Just created the var directory, will need to install mailmain bits. need_install = True os.chown(var_dir, uid, gid) os.chmod(var_dir, 02775) # Skip mailman setup if nothing so far has shown a reinstall needed. if not need_install: return 0 mailman_source = os.path.join('sourcecode', 'mailman') if config.mailman.build_host_name: build_host_name = config.mailman.build_host_name else: build_host_name = socket.getfqdn() # Build and install the Mailman software. Note that we don't care about # --with-cgi-gid because we're not going to use that Mailman subsystem. executable = os.path.abspath('bin/py') configure_args = ( './configure', '--prefix', mailman_path, '--with-var-prefix=' + var_dir, '--with-python=' + executable, '--with-username='******'--with-groupname=' + group, '--with-mail-gid=' + group, '--with-mailhost=' + build_host_name, '--with-urlhost=' + build_host_name, ) if need_build: # Configure. retcode = subprocess.call(configure_args, cwd=mailman_source) if retcode: print >> sys.stderr, 'Could not configure Mailman:' sys.exit(retcode) # Make. retcode = subprocess.call(('make', ), cwd=mailman_source) if retcode: print >> sys.stderr, 'Could not make Mailman.' sys.exit(retcode) retcode = subprocess.call(('make', 'install'), cwd=mailman_source) if retcode: print >> sys.stderr, 'Could not install Mailman.' sys.exit(retcode) # Try again to import the package. try: import Mailman except ImportError: print >> sys.stderr, 'Could not import the Mailman package' return 1 # Check to see if the site list exists. The output can go to /dev/null # because we don't really care about it. The site list exists if # config_list returns a zero exit status, otherwise it doesn't # (probably). Before we can do this however, we must monkey patch # Mailman, otherwise mm_cfg.py won't be set up correctly. monkey_patch(mailman_path, config) import Mailman.mm_cfg retcode = subprocess.call( ('./config_list', '-o', '/dev/null', Mailman.mm_cfg.MAILMAN_SITE_LIST), cwd=mailman_bin, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if retcode: addr, password = configure_siteowner( config.mailman.build_site_list_owner) # The site list does not yet exist, so create it now. retcode = subprocess.call( ('./newlist', '--quiet', '--emailhost=' + build_host_name, Mailman.mm_cfg.MAILMAN_SITE_LIST, addr, password), cwd=mailman_bin) if retcode: print >> sys.stderr, 'Could not create site list' return retcode retcode = configure_site_list( mailman_bin, Mailman.mm_cfg.MAILMAN_SITE_LIST) if retcode: print >> sys.stderr, 'Could not configure site list' return retcode # Create a directory to hold the gzip'd tarballs for the directories of # deactivated lists. try: os.mkdir(os.path.join(Mailman.mm_cfg.VAR_PREFIX, 'backups')) except OSError as e: if e.errno != errno.EEXIST: raise return 0