1"""module to send announcements for processed packages
3@contact: Debian FTP Master <ftpmaster@debian.org>
4@copyright: 2012, Ansgar Burchardt <ansgar@debian.org>
5@license: GPL-2+
6"""
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License as published by
10# the Free Software Foundation; either version 2 of the License, or
11# (at your option) any later version.
12#
13# This program is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program; if not, write to the Free Software Foundation, Inc.,
20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22import os
23from typing import Optional, Union
25from daklib.config import Config
26from daklib.textutils import fix_maintainer
27from daklib.utils import mail_addresses_for_upload, TemplateSubst, send_mail
30class ProcessedUpload:
31 """Contains data of a processed upload.
32 """
33 # people
34 maintainer = None #: Maintainer: field contents
35 changed_by = None #: Changed-By: field contents
36 fingerprint = None #: Fingerprint of upload signer
38 # suites
39 suites = [] #: Destination suites
40 from_policy_suites = [] #: Policy suites
42 # package
43 changes = None #: Contents of .changes file from upload
44 changes_filename = None #: Changes Filename
45 sourceful = None #: Did upload contain source
46 source = None #: Source value from changes
47 architecture = None #: Architectures from changes
48 version = None #: Version from changes
49 bugs = None #: Bugs closed in upload
51 # program
52 program = "unknown-program" #: Which dak program was in use
54 warnings = [] #: Eventual warnings for upload
57def _subst_for_upload(upload: ProcessedUpload) -> dict:
58 """ Prepare substitutions used for announce mails.
60 :param upload: upload to handle
61 :return: A dict of substition values for use by :func:`daklib.utils.TemplateSubst`
62 """
63 cnf = Config()
65 maintainer = upload.maintainer or cnf['Dinstall::MyEmailAddress']
66 changed_by = upload.changed_by or maintainer
67 if upload.sourceful:
68 maintainer_to = mail_addresses_for_upload(maintainer, changed_by, upload.fingerprint)
69 else:
70 maintainer_to = mail_addresses_for_upload(maintainer, maintainer, upload.fingerprint)
72 bcc = 'X-DAK: dak {0}'.format(upload.program)
73 if 'Dinstall::Bcc' in cnf: 73 ↛ 74line 73 didn't jump to line 74, because the condition on line 73 was never true
74 bcc = '{0}\nBcc: {1}'.format(bcc, cnf['Dinstall::Bcc'])
76 subst = {
77 '__DISTRO__': cnf['Dinstall::MyDistribution'],
78 '__BUG_SERVER__': cnf.get('Dinstall::BugServer'),
79 '__ADMIN_ADDRESS__': cnf['Dinstall::MyAdminAddress'],
80 '__DAK_ADDRESS__': cnf['Dinstall::MyEmailAddress'],
81 '__REJECTOR_ADDRESS__': cnf['Dinstall::MyEmailAddress'],
82 '__MANUAL_REJECT_MESSAGE__': '',
84 '__BCC__': bcc,
86 '__MAINTAINER__': changed_by,
87 '__MAINTAINER_FROM__': fix_maintainer(changed_by)[1],
88 '__MAINTAINER_TO__': ', '.join(maintainer_to),
89 '__CHANGES_FILENAME__': upload.changes_filename,
90 '__FILE_CONTENTS__': upload.changes,
91 '__SOURCE__': upload.source,
92 '__VERSION__': upload.version,
93 '__ARCHITECTURE__': upload.architecture,
94 '__WARNINGS__': '\n'.join(upload.warnings),
95 }
97 override_maintainer = cnf.get('Dinstall::OverrideMaintainer')
98 if override_maintainer: 98 ↛ 99line 98 didn't jump to line 99, because the condition on line 98 was never true
99 subst['__MAINTAINER_FROM__'] = subst['__MAINTAINER_TO__'] = override_maintainer
101 return subst
104def _whitelists(upload: ProcessedUpload):
105 return [s.mail_whitelist for s in upload.suites]
108def announce_reject(upload: ProcessedUpload, reason: str, rejected_by: Optional[str] = None) -> None:
109 """ Announce a reject.
111 :param upload: upload to handle
112 :param reason: Reject reason
113 :param rejected_by: Who is doing the reject.
114 """
115 cnf = Config()
116 subst = _subst_for_upload(upload)
117 whitelists = _whitelists(upload)
119 automatic = rejected_by is None
121 subst['__CC__'] = 'X-DAK-Rejection: {0}'.format('automatic' if automatic else 'manual')
122 subst['__REJECT_MESSAGE__'] = reason
124 if rejected_by:
125 subst['__REJECTOR_ADDRESS__'] = rejected_by
127 if not automatic:
128 subst['__BCC__'] = '{0}\nBcc: {1}'.format(subst['__BCC__'], subst['__REJECTOR_ADDRESS__'])
130 message = TemplateSubst(subst, os.path.join(cnf['Dir::Templates'], 'queue.rejected'))
131 send_mail(message, whitelists=whitelists)
134def announce_accept(upload: ProcessedUpload) -> None:
135 """ Announce an upload.
137 :param upload: upload to handle
138 """
140 cnf = Config()
141 subst = _subst_for_upload(upload)
142 whitelists = _whitelists(upload)
144 accepted_to_real_suite = any(suite.policy_queue is None or suite in upload.from_policy_suites for suite in upload.suites)
145 subst['__ACTION__'] = 'accept' if accepted_to_real_suite else 'policy'
147 suite_names = []
148 for suite in upload.suites:
149 if suite.policy_queue and suite not in upload.from_policy_suites:
150 suite_names.append("{0}->{1}".format(suite.suite_name, suite.policy_queue.queue_name))
151 else:
152 suite_names.append(suite.suite_name)
153 subst['__SUITE__'] = ', '.join(suite_names) or '(none)'
155 message = TemplateSubst(subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.accepted'))
156 send_mail(message, whitelists=whitelists)
158 if accepted_to_real_suite and upload.sourceful:
159 # send mail to announce lists and tracking server
160 announce = set()
161 for suite in upload.suites:
162 if suite.policy_queue is None or suite in upload.from_policy_suites: 162 ↛ 161line 162 didn't jump to line 161, because the condition on line 162 was never false
163 announce.update(suite.announce or [])
165 announce_list_address = ", ".join(announce)
167 # according to #890944 this email shall be sent to dispatch@<TrackingServer> to avoid
168 # bouncing emails
169 # the package email alias is not yet created shortly after accepting the package
170 tracker = cnf.get('Dinstall::TrackingServer')
171 if tracker: 171 ↛ 172line 171 didn't jump to line 172, because the condition on line 171 was never true
172 announce_list_address = "{0}\nBcc: dispatch@{1}".format(announce_list_address, tracker)
174 if len(announce_list_address) != 0: 174 ↛ 175line 174 didn't jump to line 175, because the condition on line 174 was never true
175 my_subst = subst.copy()
176 my_subst['__ANNOUNCE_LIST_ADDRESS__'] = announce_list_address
178 message = TemplateSubst(my_subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.announce'))
179 send_mail(message, whitelists=whitelists)
181 close_bugs_default = cnf.find_b('Dinstall::CloseBugs')
182 close_bugs = any(s.close_bugs if s.close_bugs is not None else close_bugs_default for s in upload.suites)
183 if accepted_to_real_suite and upload.sourceful and close_bugs: 183 ↛ 184line 183 didn't jump to line 184, because the condition on line 183 was never true
184 for bug in upload.bugs:
185 my_subst = subst.copy()
186 my_subst['__BUG_NUMBER__'] = str(bug)
188 message = TemplateSubst(my_subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.bug-close'))
189 send_mail(message, whitelists=whitelists)
192def announce_new(upload: ProcessedUpload) -> None:
193 """ Announce an upload going to NEW.
195 :param upload: upload to handle
196 """
198 cnf = Config()
199 subst = _subst_for_upload(upload)
200 whitelists = _whitelists(upload)
202 message = TemplateSubst(subst, os.path.join(cnf['Dir::Templates'], 'process-unchecked.new'))
203 send_mail(message, whitelists=whitelists)