Add a script to generate twitter and mailing list announcements
Announcements taken from https://github.com/ansible/community/wiki/RelEng:-ReleaseProcess and then cleaned up: * Update issue reporting blurb from feedback from acozine and gundalow * Add a subject and to line for email output * Ignore long line tests on the jinja templates (as jinja doesn't give enough control to get rid of newlines when text wrapping) * Skip shebang and compile tests for older pythons since this is a release engineer-only script. (ok'd by mattclay)
This commit is contained in:
parent
a1639f27d5
commit
5fb416ae34
5 changed files with 300 additions and 0 deletions
292
hacking/release-announcement.py
Executable file
292
hacking/release-announcement.py
Executable file
|
@ -0,0 +1,292 @@
|
|||
#!/usr/bin/env python3
|
||||
# coding: utf-8
|
||||
# Copyright: (c) 2019, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import datetime
|
||||
import hashlib
|
||||
import sys
|
||||
from collections import UserString
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
import aiohttp
|
||||
from jinja2 import Environment, DictLoader
|
||||
|
||||
# pylint: disable=
|
||||
VERSION_FRAGMENT = """
|
||||
{%- if versions | length > 1 %}
|
||||
{% for version in versions %}
|
||||
{% if loop.last %}and {{ version }}{% else %}
|
||||
{% if versions | length == 2 %}{{ version }} {% else %}{{ version }}, {% endif -%}
|
||||
{% endif -%}
|
||||
{% endfor -%}
|
||||
{%- else %}{{ versions[0] }}{% endif -%}
|
||||
"""
|
||||
|
||||
LONG_TEMPLATE = """
|
||||
{% set plural = True if versions | length == 1 else False %}
|
||||
{% set latest_ver = (versions | sort(attribute='ver_obj'))[-1] %}
|
||||
|
||||
To: ansible-devel@googlegroups.com, ansible-project@googlegroups.com, ansible-announce@googlegroups.com
|
||||
Subject: New Ansible release{% if plural %}s{% endif %} {{ version_str }}
|
||||
|
||||
{% filter wordwrap %}
|
||||
Hi all- we're happy to announce that the general release of Ansible {{ version_str }}{% if plural %} are{%- else %} is{%- endif %} now available!
|
||||
{% endfilter %}
|
||||
|
||||
|
||||
|
||||
How do you get it?
|
||||
------------------
|
||||
|
||||
{% for version in versions %}
|
||||
$ pip install ansible=={{ version }} --user
|
||||
{% if not loop.last %}
|
||||
or
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
The tar.gz of the release{% if plural %}s{% endif %} can be found here:
|
||||
|
||||
{% for version in versions %}
|
||||
* {{ version }}
|
||||
https://releases.ansible.com/ansible/ansible-{{ version }}.tar.gz
|
||||
SHA256: {{ hashes[version] }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
What's new in {{ version_str }}
|
||||
{{ '-' * (14 + version_str | length) }}
|
||||
|
||||
{% filter wordwrap %}
|
||||
{% if plural %}This release is a{% else %}These releases are{% endif %} maintenance release{% if plural %}s{% endif %} containing numerous bugfixes. The full {% if versions | length <= 1 %} changelog is{% else %} changelogs are{% endif %} at:
|
||||
{% endfilter %}
|
||||
|
||||
|
||||
{% for version in versions %}
|
||||
* {{ version }}
|
||||
https://github.com/ansible/ansible/blob/stable-{{ version.split('.')[:2] | join('.') }}/changelogs/CHANGELOG-v{{ version.split('.')[:2] | join('.') }}.rst
|
||||
{% endfor %}
|
||||
|
||||
|
||||
What's the schedule for future maintenance releases?
|
||||
----------------------------------------------------
|
||||
|
||||
{% filter wordwrap %}
|
||||
Future maintenance releases will occur approximately every 3 weeks. So expect the next one around {{ next_release.strftime('%Y-%m-%d') }}.
|
||||
{% endfilter %}
|
||||
|
||||
|
||||
|
||||
Porting Help
|
||||
------------
|
||||
|
||||
{% filter wordwrap %}
|
||||
We've published a porting guide at
|
||||
https://docs.ansible.com/ansible/devel/porting_guides/porting_guide_{{ latest_ver.split('.')[:2] | join('.') }}.html to help migrate your content to {{ latest_ver.split('.')[:2] | join('.') }}.
|
||||
{% endfilter %}
|
||||
|
||||
|
||||
|
||||
{% filter wordwrap %}
|
||||
If you discover any errors or if any of your working playbooks break when you upgrade to {{ latest_ver }}, please report the regression via https://github.com/ansible/ansible/issues/new/choose In your issue, be sure to mention the Ansible version that works and the one that doesn't.
|
||||
{% endfilter %}
|
||||
|
||||
|
||||
Thanks!
|
||||
|
||||
-{{ name }}
|
||||
|
||||
""" # noqa for E501 (line length).
|
||||
# jinja2 is horrid about getting rid of extra newlines so we have to have a single per paragraph for
|
||||
# proper wrapping to occur
|
||||
|
||||
SHORT_TEMPLATE = """
|
||||
@ansible
|
||||
{{ version_str }}
|
||||
{% if versions | length > 1 %}
|
||||
have
|
||||
{% else %}
|
||||
has
|
||||
{% endif %}
|
||||
been released! Get
|
||||
{% if versions | length > 1 %}
|
||||
them
|
||||
{% else %}
|
||||
it
|
||||
{% endif %}
|
||||
on PyPI: pip install ansible=={{ (versions|sort(attribute='ver_obj'))[-1] }},
|
||||
https://releases.ansible.com/ansible/, the Ansible PPA on Launchpad, or GitHub. Happy automating!
|
||||
""" # noqa for E501 (line length).
|
||||
# jinja2 is horrid about getting rid of extra newlines so we have to have a single per paragraph for
|
||||
# proper wrapping to occur
|
||||
|
||||
JINJA_ENV = Environment(
|
||||
loader=DictLoader({'long': LONG_TEMPLATE,
|
||||
'short': SHORT_TEMPLATE,
|
||||
'version_string': VERSION_FRAGMENT,
|
||||
}),
|
||||
extensions=['jinja2.ext.i18n'],
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
)
|
||||
|
||||
|
||||
class VersionStr(UserString):
|
||||
def __init__(self, string):
|
||||
super().__init__(string.strip())
|
||||
self.ver_obj = LooseVersion(string)
|
||||
|
||||
|
||||
def parse_args(args):
|
||||
parser = argparse.ArgumentParser(description="Generate email and twitter announcements"
|
||||
" from template")
|
||||
parser.add_argument("--version", dest="versions", type=str, required=True, action='append',
|
||||
help="Versions of Ansible to announce")
|
||||
parser.add_argument("--name", type=str, required=True, help="Real name to use on emails")
|
||||
parser.add_argument("--email-out", type=str, default="-",
|
||||
help="Filename to place the email announcement into")
|
||||
parser.add_argument("--twitter-out", type=str, default="-",
|
||||
help="Filename to place the twitter announcement into")
|
||||
|
||||
args = parser.parse_args(args)
|
||||
|
||||
# Make it possible to sort versions in the jinja2 templates
|
||||
new_versions = []
|
||||
for version in args.versions:
|
||||
new_versions.append(VersionStr(version))
|
||||
args.versions = new_versions
|
||||
|
||||
return args
|
||||
|
||||
|
||||
async def calculate_hash_from_tarball(session, version):
|
||||
tar_url = f'https://releases.ansible.com/ansible/ansible-{version}.tar.gz'
|
||||
tar_task = asyncio.create_task(session.get(tar_url))
|
||||
tar_response = await tar_task
|
||||
|
||||
tar_hash = hashlib.sha256()
|
||||
while True:
|
||||
chunk = await tar_response.content.read(1024)
|
||||
if not chunk:
|
||||
break
|
||||
tar_hash.update(chunk)
|
||||
|
||||
return tar_hash.hexdigest()
|
||||
|
||||
|
||||
async def parse_hash_from_file(session, version):
|
||||
filename = f'ansible-{version}.tar.gz'
|
||||
hash_url = f'https://releases.ansible.com/ansible/{filename}.sha'
|
||||
hash_task = asyncio.create_task(session.get(hash_url))
|
||||
hash_response = await hash_task
|
||||
|
||||
hash_content = await hash_response.read()
|
||||
precreated_hash, precreated_filename = hash_content.split(None, 1)
|
||||
if filename != precreated_filename.strip().decode('utf-8'):
|
||||
raise ValueError(f'Hash file contains hash for a different file: {precreated_filename}')
|
||||
|
||||
return precreated_hash.decode('utf-8')
|
||||
|
||||
|
||||
async def get_hash(session, version):
|
||||
calculated_hash = await calculate_hash_from_tarball(session, version)
|
||||
precreated_hash = await parse_hash_from_file(session, version)
|
||||
|
||||
if calculated_hash != precreated_hash:
|
||||
raise ValueError(f'Hash in file ansible-{version}.tar.gz.sha {precreated_hash} does not'
|
||||
f' match hash of tarball {calculated_hash}')
|
||||
|
||||
return calculated_hash
|
||||
|
||||
|
||||
async def get_hashes(versions):
|
||||
hashes = {}
|
||||
requestors = {}
|
||||
async with aiohttp.ClientSession() as aio_session:
|
||||
for version in versions:
|
||||
requestors[version] = asyncio.create_task(get_hash(aio_session, version))
|
||||
|
||||
for version, request in requestors.items():
|
||||
await request
|
||||
hashes[version] = request.result()
|
||||
|
||||
return hashes
|
||||
|
||||
|
||||
def next_release_date(weeks=3):
|
||||
days_in_the_future = weeks * 7
|
||||
today = datetime.datetime.now()
|
||||
numeric_today = today.weekday()
|
||||
|
||||
# We release on Thursdays
|
||||
if numeric_today == 3:
|
||||
# 3 is Thursday
|
||||
pass
|
||||
elif numeric_today == 4:
|
||||
# If this is Friday, we can adjust back to Thursday for the next release
|
||||
today -= datetime.timedelta(days=1)
|
||||
elif numeric_today < 3:
|
||||
# Otherwise, slide forward to Thursday
|
||||
today += datetime.timedelta(days=(3 - numeric_today))
|
||||
else:
|
||||
# slightly different formula if it's past Thursday this week. We need to go forward to
|
||||
# Thursday of next week
|
||||
today += datetime.timedelta(days=(10 - numeric_today))
|
||||
|
||||
next_release = today + datetime.timedelta(days=days_in_the_future)
|
||||
return next_release
|
||||
|
||||
|
||||
def generate_long_message(versions, name):
|
||||
hashes = asyncio.run(get_hashes(versions))
|
||||
|
||||
version_template = JINJA_ENV.get_template('version_string')
|
||||
version_str = version_template.render(versions=versions).strip()
|
||||
|
||||
next_release = next_release_date()
|
||||
|
||||
template = JINJA_ENV.get_template('long')
|
||||
message = template.render(versions=versions, version_str=version_str,
|
||||
name=name, hashes=hashes, next_release=next_release)
|
||||
return message
|
||||
|
||||
|
||||
def generate_short_message(versions):
|
||||
version_template = JINJA_ENV.get_template('version_string')
|
||||
version_str = version_template.render(versions=versions).strip()
|
||||
|
||||
template = JINJA_ENV.get_template('short')
|
||||
message = template.render(versions=versions, version_str=version_str)
|
||||
message = ' '.join(message.split()) + '\n'
|
||||
return message
|
||||
|
||||
|
||||
def write_message(filename, message):
|
||||
if filename != '-':
|
||||
with open(filename, 'w') as out_file:
|
||||
out_file.write(message)
|
||||
else:
|
||||
sys.stdout.write('\n\n')
|
||||
sys.stdout.write(message)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args(sys.argv[1:])
|
||||
|
||||
twitter_message = generate_short_message(args.versions)
|
||||
email_message = generate_long_message(args.versions, args.name)
|
||||
|
||||
write_message(args.twitter_out, twitter_message)
|
||||
write_message(args.email_out, email_message)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -34,6 +34,8 @@ def main():
|
|||
'test/integration/targets/win_module_utils/library/legacy_only_old_way_win_line_ending.ps1',
|
||||
'test/utils/shippable/timing.py',
|
||||
'test/integration/targets/old_style_modules_posix/library/helloworld.sh',
|
||||
# Python 3-only. Only run by release engineers
|
||||
'hacking/release-announcement.py',
|
||||
])
|
||||
|
||||
# see https://unicode.org/faq/utf_bom.html#bom1
|
||||
|
|
2
test/sanity/compile/python2.6-skip.txt
Normal file
2
test/sanity/compile/python2.6-skip.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Only run by release engineers who can be asked to have newer Python3 on their systems
|
||||
hacking/release-announcement.py
|
2
test/sanity/compile/python2.7-skip.txt
Normal file
2
test/sanity/compile/python2.7-skip.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Only run by release engineers who can be asked to have newer Python3 on their systems
|
||||
hacking/release-announcement.py
|
2
test/sanity/compile/python3.5-skip.txt
Normal file
2
test/sanity/compile/python3.5-skip.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Only run by release engineers who can be asked to have newer Python3 on their systems
|
||||
hacking/release-announcement.py
|
Loading…
Reference in a new issue