feat: isolate py27 code interfacing with mailman from py39 code interfacing with django. (#4140)
* feat: isolate py27 code interfacing with mailman from py39 code interfacing with django. * fix: improve memory footprint and remove unneeded import. * fix: make new bin command executable.
This commit is contained in:
parent
ba7d468a4d
commit
0ba50999d9
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Hourly datatracker jobs, run as mailman
|
# Hourly datatracker jobs, ***run as mailman***
|
||||||
#
|
#
|
||||||
# This script is expected to be triggered by cron from
|
# This script is expected to be triggered by cron from
|
||||||
# $DTDIR/etc/cron.d/datatracker which should be symlinked from
|
# $DTDIR/etc/cron.d/datatracker which should be symlinked from
|
||||||
|
@ -13,8 +13,7 @@ export PYTHONIOENCODING=utf-8
|
||||||
program=${0##*/}
|
program=${0##*/}
|
||||||
trap 'echo "$program($LINENO): Command failed with error code $? ([$$] $0 $*)"; exit 1' ERR
|
trap 'echo "$program($LINENO): Command failed with error code $? ([$$] $0 $*)"; exit 1' ERR
|
||||||
|
|
||||||
# Note that we're using the last 2.7 release here, not the current release
|
DTDIR=/a/www/ietf-datatracker/web
|
||||||
DTDIR=/a/www/ietf-datatracker/py27
|
|
||||||
cd $DTDIR/
|
cd $DTDIR/
|
||||||
|
|
||||||
# Set up the virtual environment
|
# Set up the virtual environment
|
||||||
|
@ -22,5 +21,4 @@ source $DTDIR/env/bin/activate
|
||||||
|
|
||||||
logger -p user.info -t cron "Running $DTDIR/bin/mm_hourly"
|
logger -p user.info -t cron "Running $DTDIR/bin/mm_hourly"
|
||||||
|
|
||||||
## XXX commented out pending rewrite -- mailman 2 python interface is not available under Python 3
|
|
||||||
$DTDIR/ietf/manage.py import_mailman_listinfo
|
$DTDIR/ietf/manage.py import_mailman_listinfo
|
||||||
|
|
50
ietf/bin/mailman_listinfo.py
Executable file
50
ietf/bin/mailman_listinfo.py
Executable file
|
@ -0,0 +1,50 @@
|
||||||
|
#!/usr/bin/python2.7
|
||||||
|
# Copyright The IETF Trust 2022, All Rights Reserved
|
||||||
|
# Note the shebang. This specifically targets deployment on IETFA and intends to use its system python2.7.
|
||||||
|
|
||||||
|
# This is an adaptor to pull information out of Mailman2 using its python libraries (which are only available for python2).
|
||||||
|
# It is NOT django code, and does not have access to django.conf.settings.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
def main():
|
||||||
|
|
||||||
|
sys.path.append('/usr/lib/mailman')
|
||||||
|
|
||||||
|
have_mailman = False
|
||||||
|
try:
|
||||||
|
from Mailman import Utils
|
||||||
|
from Mailman import MailList
|
||||||
|
from Mailman import MemberAdaptor
|
||||||
|
have_mailman = True
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if not have_mailman:
|
||||||
|
sys.stderr.write("Could not import mailman modules -- skipping import of mailman list info")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
names = list(Utils.list_names())
|
||||||
|
|
||||||
|
# need to emit dict of names, each name has an mlist, and each mlist has description, advertised, and members (calculated as below)
|
||||||
|
result = defaultdict(dict)
|
||||||
|
for name in names:
|
||||||
|
mlist = MailList.MailList(name, lock=False)
|
||||||
|
result[name] = dict()
|
||||||
|
result[name]['internal_name'] = mlist.internal_name()
|
||||||
|
result[name]['real_name'] = mlist.real_name
|
||||||
|
result[name]['description'] = mlist.description # Not attempting to change encoding
|
||||||
|
result[name]['advertised'] = mlist.advertised
|
||||||
|
result[name]['members'] = list()
|
||||||
|
if mlist.advertised:
|
||||||
|
members = mlist.getRegularMemberKeys() + mlist.getDigestMemberKeys()
|
||||||
|
members = set([ m for m in members if mlist.getDeliveryStatus(m) == MemberAdaptor.ENABLED ])
|
||||||
|
result[name]['members'] = list(members)
|
||||||
|
json.dump(result, sys.stdout)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -1,29 +1,22 @@
|
||||||
# Copyright The IETF Trust 2016-2019, All Rights Reserved
|
# Copyright The IETF Trust 2016-2019, All Rights Reserved
|
||||||
|
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
import debug # pyflakes:ignore
|
import debug # pyflakes:ignore
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.core.exceptions import MultipleObjectsReturned
|
from django.core.exceptions import MultipleObjectsReturned
|
||||||
|
|
||||||
sys.path.append(settings.MAILMAN_LIB_DIR)
|
|
||||||
|
|
||||||
have_mailman = False
|
|
||||||
try:
|
|
||||||
from Mailman import Utils
|
|
||||||
from Mailman import MailList
|
|
||||||
from Mailman import MemberAdaptor
|
|
||||||
have_mailman = True
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from ietf.mailinglists.models import List, Subscribed
|
from ietf.mailinglists.models import List, Subscribed
|
||||||
from ietf.utils.log import log
|
from ietf.utils.log import log
|
||||||
from ietf.utils.text import decode
|
|
||||||
|
|
||||||
mark = time.time()
|
mark = time.time()
|
||||||
|
|
||||||
|
@ -39,13 +32,16 @@ def import_mailman_listinfo(verbosity=0):
|
||||||
log(msg+' (%.1fs)'% (t-mark))
|
log(msg+' (%.1fs)'% (t-mark))
|
||||||
mark = t
|
mark = t
|
||||||
|
|
||||||
if not have_mailman:
|
cmd = str(Path(settings.BASE_DIR) / "bin" / "mailman_listinfo.py")
|
||||||
note("Could not import mailman modules -- skipping import of mailman list info")
|
result = subprocess.run([cmd], capture_output=True)
|
||||||
|
if result.stderr:
|
||||||
|
log("Error exporting information from mailmain")
|
||||||
|
log(result.stderr)
|
||||||
return
|
return
|
||||||
|
mailman_export = json.loads(result.stdout)
|
||||||
|
|
||||||
log("Starting import of list info from Mailman")
|
log("Starting import of list info from Mailman")
|
||||||
names = list(Utils.list_names())
|
names = sorted(mailman_export.keys())
|
||||||
names.sort()
|
|
||||||
log_time("Fetched list of mailman list names")
|
log_time("Fetched list of mailman list names")
|
||||||
addr_max_length = Subscribed._meta.get_field('email').max_length
|
addr_max_length = Subscribed._meta.get_field('email').max_length
|
||||||
|
|
||||||
|
@ -53,37 +49,33 @@ def import_mailman_listinfo(verbosity=0):
|
||||||
log_time("Computed dictionary of list members")
|
log_time("Computed dictionary of list members")
|
||||||
|
|
||||||
for name in names:
|
for name in names:
|
||||||
mlist = MailList.MailList(name, lock=False)
|
note("List: %s" % mailman_export[name]['internal_name'])
|
||||||
note("List: %s" % mlist.internal_name())
|
|
||||||
log_time("Fetched Mailman list object for %s" % name)
|
log_time("Fetched Mailman list object for %s" % name)
|
||||||
|
|
||||||
lists = List.objects.filter(name=mlist.real_name)
|
lists = List.objects.filter(name=mailman_export[name]['real_name'])
|
||||||
if lists.count() > 1:
|
if lists.count() > 1:
|
||||||
# Arbitrary choice; we'll update the remaining item next
|
# Arbitrary choice; we'll update the remaining item next
|
||||||
for item in lists[1:]:
|
for item in lists[1:]:
|
||||||
item.delete()
|
item.delete()
|
||||||
mmlist, created = List.objects.get_or_create(name=mlist.real_name)
|
mmlist, created = List.objects.get_or_create(name=mailman_export[name]['real_name'])
|
||||||
dirty = False
|
dirty = False
|
||||||
desc = decode(mlist.description)[:256]
|
desc = mailman_export[name]['description'][:256]
|
||||||
if mmlist.description != desc:
|
if mmlist.description != desc:
|
||||||
mmlist.description = desc
|
mmlist.description = desc
|
||||||
dirty = True
|
dirty = True
|
||||||
if mmlist.advertised != mlist.advertised:
|
if mmlist.advertised != mailman_export[name]['advertised']:
|
||||||
mmlist.advertised = mlist.advertised
|
mmlist.advertised = mailman_export[name]['advertised']
|
||||||
dirty = True
|
dirty = True
|
||||||
if dirty:
|
if dirty:
|
||||||
mmlist.save()
|
mmlist.save()
|
||||||
log_time(" Updated database List object for %s" % name)
|
log_time(" Updated database List object for %s" % name)
|
||||||
# The following calls return lowercased addresses
|
# The following calls return lowercased addresses
|
||||||
if mlist.advertised:
|
if mailman_export[name]['advertised']:
|
||||||
members = mlist.getRegularMemberKeys() + mlist.getDigestMemberKeys()
|
members = set(mailman_export[name]['members'])
|
||||||
log_time(" Fetched list of list members")
|
if not mailman_export[name]['real_name'] in subscribed:
|
||||||
members = set([ m for m in members if mlist.getDeliveryStatus(m) == MemberAdaptor.ENABLED ])
|
log("Note: didn't find '%s' in the dictionary of subscriptions" % mailman_export[name]['real_name'])
|
||||||
log_time(" Filtered list of list members")
|
|
||||||
if not mlist.real_name in subscribed:
|
|
||||||
log("Note: didn't find '%s' in the dictionary of subscriptions" % mlist.real_name)
|
|
||||||
continue
|
continue
|
||||||
known = subscribed[mlist.real_name]
|
known = subscribed[mailman_export[name]['real_name']]
|
||||||
log_time(" Fetched known list members from database")
|
log_time(" Fetched known list members from database")
|
||||||
to_remove = known - members
|
to_remove = known - members
|
||||||
to_add = members - known
|
to_add = members - known
|
||||||
|
|
Loading…
Reference in a new issue