Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Debian New Member Process
nm.debian.org
Commits
d87388ac
Commit
d87388ac
authored
Nov 11, 2015
by
Enrico Zini
Browse files
archive-process-email now only uses Delivered-To and only looks up active processes
parent
71664a3b
Changes
2
Hide whitespace changes
Inline
Side-by-side
archive-process-email
View file @
d87388ac
...
...
@@ -30,6 +30,9 @@ class LookupError(Exception):
Q
=
lambda
s
:
s
class
umask_override
(
object
):
"""
Context manager that temporarily overrides the umask during its lifetime
"""
def
__init__
(
self
,
umask
):
self
.
new_umask
=
umask
self
.
old_umask
=
None
...
...
@@ -46,8 +49,9 @@ class umask_override(object):
class
Dispatcher
(
object
):
re_dest
=
re
.
compile
(
"^archive-(.+)@nm.debian.org$"
)
def
__init__
(
self
,
infd
=
sys
.
stdin
,
destdir
=
DEFAULT_DESTDIR
):
self
.
re_dest
=
re
.
compile
(
"^archive-(.+)@nm.debian.org$"
)
self
.
destdir
=
destdir
self
.
infd
=
infd
self
.
_db
=
None
...
...
@@ -76,24 +80,35 @@ class Dispatcher(object):
self
.
msg
.
add_header
(
"NM-Archive-Lookup-History"
,
msg
)
def
get_dest_key
(
self
):
to
=
self
.
msg
.
get
(
"Delivered-To"
,
None
)
if
to
is
None
:
self
.
log_lookup
(
"Delivered-To not found in mail"
)
return
None
"""
Lookup the archive-(?P<key>.+) destination key in the Delivered-To mail
header, extract the key and return it.
Raises LookupError if no parsable Delivered-To header is found.
"""
dests
=
self
.
msg
.
get_all
(
"Delivered-To"
)
if
dests
is
None
:
raise
LookupError
(
"No Delivered-To header found"
)
for
dest
in
dests
:
if
dest
==
"archive@nm.debian.org"
:
self
.
log_lookup
(
"ignoring {} as destination"
.
format
(
dest
))
continue
if
to
==
"archive@nm.debian.org"
:
self
.
log_lookup
(
"no dest key"
)
return
None
mo
=
self
.
re_dest
.
match
(
dest
)
if
mo
is
None
:
self
.
log_lookup
(
"delivered-to '{}' does not match any known format"
.
format
(
dest
))
continue
mo
=
self
.
re_dest
.
match
(
to
)
if
mo
is
None
:
self
.
log_lookup
(
"invalid delivered-to: '%s'"
%
to
)
return
None
self
.
log_lookup
(
"found destination key: '%s'"
%
mo
.
group
(
1
))
return
mo
.
group
(
1
)
self
.
log_lookup
(
"dest key: '%s'"
%
mo
.
group
(
1
))
return
mo
.
group
(
1
)
raise
LookupError
(
"No valid Delivered-To headers found"
)
def
deliver_to_failsafe
(
self
,
reason
=
None
,
exc
=
None
):
"""
Deliver the message to the failsafe mailbox
"""
if
reason
is
None
:
reason
=
"exception %s: %s"
%
(
exc
.
__class__
.
__name__
,
str
(
exc
))
self
.
msg
[
"NM-Archive-Failsafe-Reason"
]
=
reason
...
...
@@ -115,16 +130,18 @@ class Dispatcher(object):
SELECT pr.archive_key
FROM person p
JOIN process pr ON pr.person_id = p.id
WHERE pr.is_active = 1
"""
if
'='
in
dest_key
:
# Lookup email
email
=
dest_key
.
replace
(
"="
,
"@"
)
self
.
log_lookup
(
"lookup by email '%s'"
%
email
)
cur
.
execute
(
Q
(
query
+
"
WHERE
p.email=%s"
),
(
email
,))
cur
.
execute
(
Q
(
query
+
"
AND
p.email=%s"
),
(
email
,))
else
:
# Lookup uid
self
.
log_lookup
(
"lookup by uid '%s'"
%
dest_key
)
cur
.
execute
(
Q
(
query
+
"
WHERE
p.uid=%s"
),
(
dest_key
,))
cur
.
execute
(
Q
(
query
+
"
AND
p.uid=%s"
),
(
dest_key
,))
# Get the person ID
arc_key
=
None
...
...
@@ -136,102 +153,9 @@ class Dispatcher(object):
return
arc_key
def
emails_to_person_ids
(
self
,
emails
):
if
not
emails
:
return
[]
uids
=
[]
for
email
in
emails
:
if
email
.
endswith
(
"@debian.org"
):
uids
.
append
(
email
[:
-
11
])
where
=
[
"email IN (%s)"
%
(
", "
.
join
((
"%s"
,)
*
len
(
emails
)))
]
if
uids
:
where
.
append
(
"uid IN (%s)"
%
(
", "
.
join
((
"%s"
,)
*
len
(
uids
))))
query
=
"SELECT id FROM person WHERE (%s)"
%
(
" OR "
.
join
(
where
))
cur
=
self
.
db
.
cursor
()
cur
.
execute
(
Q
(
query
),
emails
+
uids
)
pids
=
[]
for
pid
,
in
cur
:
pids
.
append
(
pid
)
return
pids
def
list_open_processes
(
self
):
"""
Return a list of (archive_key, AM preson ID, NM person ID) tuples
for all active processes in the database
"""
query
=
"""
SELECT pr.archive_key, a.person_id, pr.person_id
FROM process pr
JOIN am a ON pr.manager_id = a.id
WHERE pr.is_active = true
"""
cur
=
self
.
db
.
cursor
()
cur
.
execute
(
Q
(
query
))
return
list
(
cur
)
def
fields_to_person_ids
(
self
,
*
names
):
"""
Merge the contents of all the named fields in the emails, parse the
emails they contain and convert them to person IDs, returning the
result as a frozenset
"""
emails
=
[]
for
name
in
names
:
emails
+=
self
.
msg
.
get_all
(
name
,
[])
emails
=
[
x
[
1
]
for
x
in
getaddresses
(
emails
)]
return
frozenset
(
self
.
emails_to_person_ids
(
emails
))
def
archive_keys_from_headers
(
self
):
# Get all Person ID given emails in From and To and Cc addresses
fpids
=
self
.
fields_to_person_ids
(
"from"
)
tpids
=
self
.
fields_to_person_ids
(
"to"
,
"resent-to"
,
"cc"
,
"resent-cc"
)
if
not
fpids
and
not
tpids
:
raise
LookupError
(
"No known people recognised in message headers"
)
# Get a list of all open processes
procs
=
self
.
list_open_processes
()
def
archive_keys
(
procs
):
"Return a list of archive keys from a procs-like list"
return
[
x
[
0
]
for
x
in
procs
]
# List of processes where the email is from an applicant
cand
=
[
p
for
p
in
procs
if
p
[
2
]
in
fpids
]
if
cand
:
# If only one active process matches, we're done
if
len
(
cand
)
==
1
:
return
archive_keys
(
cand
)
# Try to keep only those to the AM
filtered
=
[
p
for
p
in
cand
if
p
[
1
]
in
tpids
]
return
archive_keys
(
filtered
if
filtered
else
cand
)
# List of processes where the email is to an applicant
cand
=
[
p
for
p
in
procs
if
p
[
2
]
in
tpids
]
if
cand
:
# If only one active process matches, we're done
if
len
(
cand
)
==
1
:
return
archive_keys
(
cand
)
# Try to keep only those from the AM
filtered
=
[
p
for
p
in
cand
if
p
[
1
]
in
fpids
]
return
archive_keys
(
filtered
if
filtered
else
cand
)
# List of processes where the email is from or to an AM, but neither
# from nor to applicants: send to all processes for the AM
cand
=
[
p
for
p
in
procs
if
p
[
1
]
in
fpids
or
p
[
1
]
in
tpids
]
if
cand
:
return
archive_keys
(
cand
)
# If nothing matched so far, fail
raise
LookupError
(
"No active processes found matching the email headers"
)
def
get_arc_keys
(
self
):
def
get_arc_key
(
self
):
dest_key
=
self
.
get_dest_key
()
if
dest_key
is
not
None
:
return
[
self
.
archive_key_from_dest_key
(
dest_key
)]
return
self
.
archive_keys_from_headers
()
return
self
.
archive_key_from_dest_key
(
dest_key
)
def
main
():
...
...
@@ -249,8 +173,6 @@ def main():
parser
=
Parser
(
usage
=
"usage: %prog [options]"
,
version
=
"%prog "
+
VERSION
,
description
=
"Dispatch NM mails Cc-ed to the archive address"
)
#parser.add_option("-q", "--quiet", action="store_true", help="quiet mode: only output fatal errors")
#parser.add_option("-v", "--verbose", action="store_true", help="verbose mode")
parser
.
add_option
(
"--dest"
,
action
=
"store"
,
default
=
DEFAULT_DESTDIR
,
help
=
"destination directory (default: %default)"
)
parser
.
add_option
(
"--dry-run"
,
action
=
"store_true"
,
help
=
"print destinations instead of delivering mails"
)
(
opts
,
args
)
=
parser
.
parse_args
()
...
...
@@ -259,16 +181,16 @@ def main():
if
opts
.
dry_run
:
msgid
=
dispatcher
.
msg
.
get
(
"message-id"
,
"(no message id)"
)
try
:
for
arc_key
in
dispatcher
.
get_arc_key
s
()
:
print
(
msgid
,
arc_key
)
arc_key
=
dispatcher
.
get_arc_key
()
print
(
msgid
,
arc_key
)
return
0
except
Exception
as
e
:
print
(
msgid
,
"failsafe"
)
return
1
else
:
try
:
for
arc_key
in
dispatcher
.
get_arc_key
s
()
:
dispatcher
.
deliver_to_archive_key
(
arc_key
)
arc_key
=
dispatcher
.
get_arc_key
()
dispatcher
.
deliver_to_archive_key
(
arc_key
)
return
0
except
Exception
as
e
:
dispatcher
.
deliver_to_failsafe
(
exc
=
e
)
...
...
test-archive-process-email
View file @
d87388ac
...
...
@@ -59,46 +59,17 @@ class TestLookup(unittest.TestCase):
dest
=
self
.
proc
.
person
.
email
.
replace
(
"@"
,
"="
)
d
=
self
.
make_dispatcher
(
dk
=
dest
)
key
=
d
.
get_dest_key
()
self
.
assertEqual
s
(
key
,
dest
)
self
.
assertEqual
(
key
,
dest
)
arc_key
=
d
.
archive_key_from_dest_key
(
key
)
self
.
assertEqual
s
(
arc_key
,
self
.
proc
.
archive_key
)
self
.
assertEqual
(
arc_key
,
self
.
proc
.
archive_key
)
def
testDestkeyUid
(
self
):
dest
=
self
.
proc
.
person
.
uid
d
=
self
.
make_dispatcher
(
dk
=
dest
)
key
=
d
.
get_dest_key
()
self
.
assertEqual
s
(
key
,
dest
)
self
.
assertEqual
(
key
,
dest
)
arc_key
=
d
.
archive_key_from_dest_key
(
key
)
self
.
assertEquals
(
arc_key
,
self
.
proc
.
archive_key
)
def
testInfer
(
self
):
d
=
self
.
make_dispatcher
(
src
=
"nm"
,
dst
=
"am"
)
self
.
assertEquals
(
d
.
get_arc_keys
(),
[
self
.
proc
.
archive_key
])
self
.
assertNotIn
(
"lookup by distinct nm and am gave 0 results"
,
d
.
lookup_attempts
)
d
=
self
.
make_dispatcher
(
src
=
"am"
,
dst
=
"nm"
)
self
.
assertEquals
(
d
.
get_arc_keys
(),
[
self
.
proc
.
archive_key
])
self
.
assertNotIn
(
"lookup by distinct nm and am gave 0 results"
,
d
.
lookup_attempts
)
d
=
self
.
make_dispatcher
(
src
=
"fd"
,
dst
=
"nm"
)
self
.
assertEquals
(
d
.
get_arc_keys
(),
[
self
.
proc
.
archive_key
])
def
testInferMultidest
(
self
):
# Find an AM with multiple NMs
am
=
None
procs
=
None
for
a
in
bmodels
.
AM
.
objects
.
all
():
procs
=
a
.
processed
.
filter
(
is_active
=
True
)
if
procs
.
count
()
>
1
:
am
=
a
procs
=
list
(
procs
)
break
assert
am
# AM sends an email to all their applicants
d
=
self
.
make_dispatcher
(
src
=
[
am
.
person
],
dst
=
[
p
.
person
for
p
in
procs
])
self
.
assertEquals
(
sorted
(
d
.
get_arc_keys
()),
sorted
(
p
.
archive_key
for
p
in
procs
))
self
.
assertNotIn
(
"lookup by distinct nm and am gave 0 results"
,
d
.
lookup_attempts
)
self
.
assertEqual
(
arc_key
,
self
.
proc
.
archive_key
)
if
__name__
==
'__main__'
:
unittest
.
main
()
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment