rrs_upstream_email: rework

* Use maintenance plans to get layerbranches
* Use from/to/subject and admin contact from maintenance plan
* Use an actual template to render the email (and drop tabulate
  dependency)
* Improve grammar in the email text
* Use a single line to represent the most recent commit

Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
This commit is contained in:
Paul Eggleton 2018-03-02 09:09:35 +13:00
parent 735353ebd1
commit 227b9c65af
4 changed files with 118 additions and 93 deletions

View File

@ -27,4 +27,3 @@ smmap==0.9.0
smmap2==2.0.3
Unidecode==0.4.19
vine==1.1.4
tabulate==0.7.3

View File

@ -3,7 +3,7 @@
# Send email to maintainers about the current status of the recipes.
#
# Copyright (C) 2015 Intel Corporation
# Copyright (C) 2015, 2018 Intel Corporation
# Author: Aníbal Limón <anibal.limon@linux.intel.com>
#
# Licensed under the MIT license, see COPYING.MIT for details
@ -12,7 +12,7 @@ import sys
import os.path
import optparse
import logging
from tabulate import tabulate
from collections import namedtuple
sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__))))
from common import common_setup, get_pv_type, get_logger
@ -21,43 +21,30 @@ from layerindex import utils
utils.setup_django()
from django.core.mail import EmailMessage
from django.template.loader import get_template
from django.template import Context, Template
from django.core.urlresolvers import reverse
from django.contrib.sites.models import Site
import settings
from layerindex.models import LayerItem, LayerBranch, Recipe
from rrs.models import Maintainer, RecipeMaintainerHistory, RecipeMaintainer, \
RecipeUpstream, RecipeUpstreamHistory
RecipeUpstream, RecipeUpstreamHistory, MaintenancePlan
logger = get_logger('UpstreamEmail', settings)
LAYERBRANCH_NAME = "master"
"""
Send email with Recipes that need update.
"""
def send_email(recipes, repodir, options):
header = """This mail was sent out by Recipe reporting system.
This message list those recipes which need to be upgraded. If maintainers
believe some of them needn't to upgrade at this time, they can fill
RECIPE_NO_UPDATE_REASON in respective recipe file to ignore this remainder
until newer upstream version was detected.
Example:
RECIPE_NO_UPDATE_REASON = "Version 2.0 is unstable"
You can check the detail information at:
http://recipes.yoctoproject.org/
"""
def send_email(maintplan, recipes, options):
upgradable_count = 0
no_upgradable_count = 0
maintainers = Maintainer.objects.all().order_by("name")
table_headers = ['Package', 'Version', 'Upstream version',
'Maintainer', 'NoUpgradeReason']
table = []
RecipeUpgradeLine = namedtuple('RecipeUpgradeLine', ['pn', 'pv', 'pv_upstream', 'maintainer', 'noupgradereason'])
recipelines = []
for m in maintainers:
for recipe in recipes.keys():
recipe_maintainer = recipes[recipe]['maintainer']
@ -69,50 +56,66 @@ http://recipes.yoctoproject.org/
name_max_len = 20
reason_max_len = 30
pn = recipe.pn
if len(pn) > pn_max_len:
pn = pn[0:pn_max_len - 3] + "..."
pv = recipe.pv
if len(pv) > pv_max_len:
pv = pv[0:pv_max_len - 3] + "..."
pv_up = recipe_upstream.version
if len(pv_up) > pv_max_len:
pv_up = pv_up[0:pv_max_len - 3] + "..."
name = m.name
if len(name) > name_max_len:
name = name[0:name_max_len - 3] + "..."
reason = recipe_upstream.no_update_reason
if len(reason) > reason_max_len:
reason = reason[0:reason_max_len - 3] + "..."
table.append([pn, pv, pv_up, name, reason])
recipelines.append(RecipeUpgradeLine(recipe.pn, recipe.pv, recipe_upstream.version, m.name, recipe_upstream.no_update_reason))
upgradable_count = upgradable_count + 1
if recipe_upstream.no_update_reason:
no_upgradable_count = no_upgradable_count + 1
body = tabulate(table, table_headers, tablefmt="simple")
footer = """
\nUpgradable count: %d\nUpgradable total count: %d\n
The based commit is:
commits = []
fetchdir = settings.LAYER_FETCH_DIR
for item in maintplan.maintenanceplanlayerbranch_set.all():
layerbranch = item.layerbranch
layer = layerbranch.layer
urldir = layer.get_fetch_dir()
repodir = os.path.join(fetchdir, urldir)
# FIXME this assumes the correct branch is checked out
topcommitdesc = utils.runcmd("git log -1 --oneline", repodir).strip()
commits.append('%s: %s' % (layerbranch.layer.name, topcommitdesc))
%s
Any problem, please contact Anibal Limon <anibal.limon@intel.com>
""" % ((upgradable_count - no_upgradable_count), upgradable_count,
utils.runcmd("git log -1", repodir))
# Render the subject as a template (to allow a bit of flexibility)
subject = options.subject or maintplan.email_subject
subject_template = Template(subject)
current_site = Site.objects.get_current()
d = Context({
'maintplan': maintplan,
'site': current_site,
})
subject_content = subject_template.render(d)
#
subject = options.subject
from_email = options._from
to_email_list = options.to.split(';')
text_content = header + body + footer
from_email = options._from or maintplan.email_from
if options.to:
to_email_list = options.to.split(';')
elif maintplan.email_to:
to_email_list = maintplan.email_to.split(';')
else:
to_email_list = []
msg = EmailMessage(subject, text_content, from_email, to_email_list)
if not subject:
logger.error('No subject specified in maintenance plan %s and none specified on command line' % maintplan.name)
sys.exit(1)
if not from_email:
logger.error('No sender email address specified in maintenance plan %s and none specified on command line' % maintplan.name)
sys.exit(1)
if not to_email_list:
logger.error('No recipient email address specified in maintenance plan %s and none specified on command line' % maintplan.name)
sys.exit(1)
plaintext = get_template('rrs/report_email.txt')
site_url = 'http://' + current_site.domain + reverse('rrs_frontpage')
d = Context({
'maintplan': maintplan,
'site': current_site,
'site_url': site_url,
'upgradable_count': (upgradable_count - no_upgradable_count),
'total_upgradable_count': upgradable_count,
'commits': commits,
'recipelines': recipelines,
})
text_content = plaintext.render(d)
msg = EmailMessage(subject_content, text_content, from_email, to_email_list)
msg.send()
def main():
@ -120,14 +123,12 @@ def main():
usage = """
%prog [options]""")
parser.add_option("-l", "--layername",
action="store", dest="layername", default=settings.CORE_LAYER_NAME)
parser.add_option("-s", "--subject",
action="store", dest="subject", default=settings.RRS_EMAIL_SUBJECT)
action="store", dest="subject", help='Override email subject')
parser.add_option("-f", "--from",
action="store", dest="_from", default=settings.RRS_EMAIL_FROM)
action="store", dest="_from", help='Override sender address')
parser.add_option("-t", "--to",
action="store", dest="to", default=settings.RRS_EMAIL_TO)
action="store", dest="to", help='Override recipient address')
parser.add_option("-d", "--debug",
help = "Enable debug output",
action="store_const", const=logging.DEBUG, dest="loglevel", default=logging.INFO)
@ -137,36 +138,40 @@ def main():
options, args = parser.parse_args(sys.argv)
recipes = {}
layer = LayerItem.objects.filter(name = options.layername)[0]
# get recipes for send email
layerbranch = layer.get_layerbranch(LAYERBRANCH_NAME)
recipe_upstream_history = RecipeUpstreamHistory.get_last()
if recipe_upstream_history is None:
logger.warn('I don\'t have Upstream information yet, run update.py script')
maintplans = MaintenancePlan.objects.filter(email_enabled=True)
if not maintplans.exists():
logger.error('No maintenance plans with email enabled were found')
sys.exit(1)
recipe_maintainer_history = RecipeMaintainerHistory.get_last()
if recipe_maintainer_history is None:
logger.warn('I don\'t have Maintainership information yet,' +
' run rrs_maintainer_history.py script')
sys.exit(1)
for maintplan in maintplans:
recipes = {}
for item in maintplan.maintenanceplanlayerbranch_set.all():
layerbranch = item.layerbranch
for recipe in Recipe.objects.filter(layerbranch = layerbranch):
recipe_upstream_query = RecipeUpstream.objects.filter(recipe =
recipe, history = recipe_upstream_history)
if recipe_upstream_query and recipe_upstream_query[0].status == 'N':
recipes[recipe] = {}
recipe_upstream_history = RecipeUpstreamHistory.get_last()
if recipe_upstream_history is None:
logger.warn('I don\'t have Upstream information yet, run update.py script')
sys.exit(1)
recipe_maintainer = RecipeMaintainer.objects.filter(recipe =
recipe, history = recipe_maintainer_history)[0]
recipes[recipe]['maintainer'] = recipe_maintainer
recipes[recipe]['upstream'] = recipe_upstream_query[0]
recipe_maintainer_history = RecipeMaintainerHistory.get_last()
if recipe_maintainer_history is None:
logger.warn('I don\'t have Maintainership information yet,' +
' run rrs_maintainer_history.py script')
sys.exit(1)
repodir = os.path.join(settings.LAYER_FETCH_DIR, layer.get_fetch_dir())
for recipe in layerbranch.recipe_set.all():
recipe_upstream_query = RecipeUpstream.objects.filter(recipe =
recipe, history = recipe_upstream_history)
if recipe_upstream_query and recipe_upstream_query[0].status == 'N':
recipes[recipe] = {}
send_email(recipes, repodir, options)
recipe_maintainer = RecipeMaintainer.objects.filter(recipe =
recipe, history = recipe_maintainer_history)[0]
recipes[recipe]['maintainer'] = recipe_maintainer
recipes[recipe]['upstream'] = recipe_upstream_query[0]
send_email(maintplan, recipes, options)
if __name__ == "__main__":
main()

View File

@ -234,10 +234,5 @@ RABBIT_BACKEND = 'rpc://'
# Used for fetching repo
PARALLEL_JOBS = "4"
# Settings for Recipe reporting system
RRS_EMAIL_SUBJECT = '[Recipe reporting system] Upgradable recipe name list'
RRS_EMAIL_FROM = 'recipe-report@yoctoproject.org'
RRS_EMAIL_TO = 'list@example.com'
# Full path to directory where rrs tools stores logs
TOOLS_LOG_DIR = ""

View File

@ -0,0 +1,26 @@
This is an automated message from the Recipe Reporting System.
Listed below are recipes which need to be upgraded based on the versions
available upstream. If a maintainer believes some of them cannot be upgraded
at this time, they can set RECIPE_NO_UPDATE_REASON in the recipe file(s) as
a reminder of why not.
Example:
RECIPE_NO_UPDATE_REASON = "Version 2.0 is unstable"
For further details please visit:
{{ site_url }}
Package Version Upstream version Maintainer No upgrade reason
-------------------- --------------- -------------------- -------------------- ------------------------------
{% for line in recipelines %}{{ line.pn|truncatechars:20|ljust:20 }} {{ line.pv|truncatechars:15|ljust:15 }} {{ line.pv_upstream|truncatechars:20|ljust:20 }} {{ line.maintainer|truncatechars:20|ljust:20 }} {{ line.noupgradereason|truncatechars:30 }}
{% endfor %}
Upgradable count: {{ upgradable_count }}
Upgradable total count: {{ total_upgradable_count }}
Based upon current {{ commits|length|pluralize:"commit,commits" }}:
{% for commit in commits %} {{ commit }}
{% endfor %}
{% if maintplan.admin %}If there are any problems with the above, please contact: {{ maintplan.admin.first_name }} {{ maintplan.admin.last_name }} <{{ maintplan.admin.email }}>{% endif %}