[calm - Cygwin server-side packaging maintenance script] branch master, updated. 20230209-113-g643584c

Jon Turney jturney@sourceware.org
Sun Jun 2 14:40:11 GMT 2024




https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=643584cbd8ee057a0e433e017012ca87870e0ce7

commit 643584cbd8ee057a0e433e017012ca87870e0ce7
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Sun Jun 2 14:35:59 2024 +0100

    Add Brian Inglis to NMU maintainers list

https://sourceware.org/git/gitweb.cgi?p=cygwin-apps/calm.git;h=b972d47e8ace4f20a8d0abb0c6fc1af367f5e7d6

commit b972d47e8ace4f20a8d0abb0c6fc1af367f5e7d6
Author: Jon Turney <jon.turney@dronecode.org.uk>
Date:   Fri May 31 14:39:55 2024 +0100

    Add a tool for mailing long-inactive maintainers


Diff:
---
 calm/common_constants.py                           |   5 +-
 calm/mail-inactive-maintainers.py                  | 111 +++++++++++++++++++++
 calm/reports.py                                    |  21 +++-
 calm/utils.py                                      |   4 +-
 test/testdata/gitolite/package-repos.conf.expected |   2 +-
 5 files changed, 135 insertions(+), 8 deletions(-)

diff --git a/calm/common_constants.py b/calm/common_constants.py
index 82d7801..5e90a21 100644
--- a/calm/common_constants.py
+++ b/calm/common_constants.py
@@ -52,11 +52,12 @@ ALWAYS_BCC = 'jturney@sourceware.org'
 # - untest any package
 # - vault any package
 #
-# (these people have sourceware shell access and cygwin group membership, so
-# they can do whatever they like directly, anyhow)
+# (most of these people have sourceware shell access and cygwin group
+# membership, so they can do whatever they like directly, anyhow)
 TRUSTEDMAINT = '/'.join([
     'Achim Gratz',
     'Corinna Vinschen',
+    'Brian Inglis',
     'Jon Turney',
     'Ken Brown',
     'Marco Atzeri',
diff --git a/calm/mail-inactive-maintainers.py b/calm/mail-inactive-maintainers.py
new file mode 100644
index 0000000..8e692a9
--- /dev/null
+++ b/calm/mail-inactive-maintainers.py
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+# Copyright (c) 2024 Jon Turney
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+import argparse
+import logging
+import os
+import sys
+import time
+
+from . import common_constants
+from . import package
+from . import pkg2html
+from . import reports
+from . import utils
+
+MAINTAINER_ACTIVITY_THRESHOLD_YEARS = 10
+
+template = '''
+
+Hi {},
+
+As a part of keeping cygwin secure, your package maintainer account has been
+found to be long inactive, and will soon be disabled, and your packages moved to
+'ORPHANED' status.
+
+The estimated date of your last packaging activity is {}.
+
+Any action using your ssh key is sufficient to keep your account alive, e.g.:
+
+* do a git pull with an ssh://cygwin@cygwin.com/ URL
+* run 'ssh cygwin@cygwin.com alive'
+
+For reference, the list of packages you are recorded as a maintainer of is:
+
+{}
+
+Thanks for all your work on these!
+
+For further assistance, please contact us via email at <cygwin-apps@cygwin.com>
+
+'''
+
+
+def main(args):
+    packages = {}
+
+    for arch in common_constants.ARCHES:
+        logging.debug("reading existing packages for arch %s" % (arch))
+        packages[arch], _ = package.read_packages(args.relarea, arch)
+
+    activity_list = reports.maintainer_activity(args, packages)
+
+    threshold = time.time() - MAINTAINER_ACTIVITY_THRESHOLD_YEARS * 365.25 * 24 * 60 * 60
+
+    for a in activity_list:
+        last_activity = max(a.last_seen, a.last_package)
+        if last_activity < threshold:
+            logging.info('%s %s %s %s', a.name, a.email, last_activity, a.pkgs)
+            pkg_list = [packages[arch][p].orig_name for p in a.pkgs]
+
+            hdr = {}
+            hdr['To'] = a.email
+            hdr['From'] = 'cygwin-no-reply@cygwin.com'
+            hdr['Envelope-From'] = common_constants.ALWAYS_BCC  # we want to see bounces
+            hdr['Reply-To'] = 'cygwin-apps@cygwin.com'
+            hdr['Bcc'] = common_constants.ALWAYS_BCC
+            hdr['Subject'] = 'cygwin package maintainer account for %s' % a.name
+            hdr['X-Calm-Inactive-Maintainer'] = '1'
+
+            msg = template.format(a.name, pkg2html.tsformat(last_activity), '\n'.join(pkg_list))
+
+            msg_id = utils.sendmail(hdr, msg)
+            logging.info('%s', msg_id)
+
+
+if __name__ == "__main__":
+    relarea_default = common_constants.FTP
+    homedir_default = common_constants.HOMEDIR
+    pkglist_default = common_constants.PKGMAINT
+
+    parser = argparse.ArgumentParser(description='Send mail to inactive maintainers')
+    parser.add_argument('--homedir', action='store', metavar='DIR', help="maintainer home directory (default: " + homedir_default + ")", default=homedir_default)
+    parser.add_argument('--pkglist', action='store', metavar='FILE', help="package maintainer list (default: " + pkglist_default + ")", default=pkglist_default)
+    parser.add_argument('--releasearea', action='store', metavar='DIR', help="release directory (default: " + relarea_default + ")", default=relarea_default, dest='relarea')
+
+    (args) = parser.parse_args()
+
+    logging.getLogger().setLevel(logging.INFO)
+    logging.basicConfig(format=os.path.basename(sys.argv[0]) + ': %(message)s')
+
+    main(args)
diff --git a/calm/reports.py b/calm/reports.py
index 240b5d9..cd772fe 100644
--- a/calm/reports.py
+++ b/calm/reports.py
@@ -281,9 +281,9 @@ def unstable(args, packages, reportlist):
     write_report(args, 'Packages marked as unstable', body, 'unstable.html', reportlist)
 
 
-# produce a report on maintainer (in)activity
+# gather data on maintainer activity
 #
-def maintainer_activity(args, packages, reportlist):
+def maintainer_activity(args, packages):
     activity_list = []
 
     arch = 'x86_64'
@@ -296,8 +296,12 @@ def maintainer_activity(args, packages, reportlist):
 
         a = types.SimpleNamespace()
         a.name = m.name
+        a.email = m.email
         a.last_seen = m.last_seen
 
+        # because last_seen hasn't been collected for very long, we also try to
+        # estimate by looking at packages (this isn't very good as it gets
+        # confused by co-mainainted packages)
         count = 0
         mtime = 0
         pkgs = []
@@ -329,13 +333,22 @@ def maintainer_activity(args, packages, reportlist):
 
         activity_list.append(a)
 
+    return activity_list
+
+
+# produce a report on maintainer (in)activity
+#
+def maintainer_activity_report(args, packages, reportlist):
+    arch = 'x86_64'
+    activity_list = maintainer_activity(args, packages)
+
     body = io.StringIO()
     print('<p>Maintainer activity.</p>', file=body)
 
     print('<table class="grid sortable">', file=body)
     print('<tr><th>Maintainer</th><th># packages</th><th>Last ssh</th><th>Latest package</th></tr>', file=body)
 
-    for a in sorted(activity_list, key=lambda i: (i.last_seen, i.last_package)):
+    for a in sorted(activity_list, key=lambda i: max(i.last_seen, i.last_package)):
         def pkg_details(pkgs):
             return '<details><summary>%d</summary>%s</details>' % (len(pkgs), ', '.join(linkify(p, packages[arch][p]) for p in pkgs))
 
@@ -529,7 +542,7 @@ def do_reports(args, packages):
     provides_rebuild(args, packages, 'ruby_rebuilds.html', 'ruby', reportlist)
     python_rebuild(args, packages, 'python_rebuilds.html', reportlist)
 
-    maintainer_activity(args, packages, reportlist)
+    maintainer_activity_report(args, packages, reportlist)
 
     fn = os.path.join(args.htdocs, 'reports_list.inc')
     with utils.open_amifc(fn) as f:
diff --git a/calm/utils.py b/calm/utils.py
index 6fb93b9..16b69a3 100644
--- a/calm/utils.py
+++ b/calm/utils.py
@@ -169,6 +169,8 @@ def sendmail(hdr, msg):
     if not hdr['To']:
         return
 
+    envelope_from = hdr.pop('Envelope-From', hdr['From'])
+
     # build the email
     m = email.message.Message()
 
@@ -195,7 +197,7 @@ def sendmail(hdr, msg):
         logging.debug(msg)
         logging.debug('-' * 40)
     else:
-        with subprocess.Popen(['/usr/sbin/sendmail', '-t', '-oi', '-f', hdr['From']], stdin=subprocess.PIPE) as p:
+        with subprocess.Popen(['/usr/sbin/sendmail', '-t', '-oi', '-f', envelope_from], stdin=subprocess.PIPE) as p:
             p.communicate(m.as_bytes())
             logging.debug('sendmail: msgid %s, exit status %d' % (m['Message-Id'], p.returncode))
 
diff --git a/test/testdata/gitolite/package-repos.conf.expected b/test/testdata/gitolite/package-repos.conf.expected
index 6b79a85..51c3f7c 100644
--- a/test/testdata/gitolite/package-repos.conf.expected
+++ b/test/testdata/gitolite/package-repos.conf.expected
@@ -1,6 +1,6 @@
 ('# automatically generated by mkgitoliteconf\n'
  '\n'
- '@leads = Achim_Gratz Corinna_Vinschen Jon_Turney Ken_Brown Marco_Atzeri\n'
+ '@leads = Achim_Gratz Corinna_Vinschen Brian_Inglis Jon_Turney Ken_Brown Marco_Atzeri\n'
  '\n'
  'repo @all\n'
  '    RW = @leads\n'



More information about the Cygwin-apps-cvs mailing list