diff --git a/hacking/build_library/build_ansible/announce.py b/hacking/build_library/build_ansible/announce.py deleted file mode 100644 index c245bfb97b9..00000000000 --- a/hacking/build_library/build_ansible/announce.py +++ /dev/null @@ -1,293 +0,0 @@ -# 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 asyncio -import datetime -import hashlib - -import aiohttp -from jinja2 import Environment, DictLoader - - -VERSION_FRAGMENT = """ -{%- if versions | length > 1 %} - {% for version in versions %} - {% if loop.last %}and {{ pretty_version(version) }}{% else %} - {% if versions | length == 2 %}{{ pretty_version(version) }} {% else %}{{ pretty_version(version) }}, {% endif -%} - {% endif -%} - {% endfor -%} -{%- else %}{{ pretty_version(versions[0]) }}{% endif -%} -""" - -LONG_TEMPLATE = """ -{% set plural = False if versions | length == 1 else True %} -{% set latest_ver = (versions | sort(attribute='ver_obj'))[-1] %} - -To: ansible-releases@redhat.com, ansible-devel@googlegroups.com, ansible-project@googlegroups.com, ansible-announce@googlegroups.com -Subject: New release{% if plural %}s{% endif %}: {{ version_str }} - -{% filter wordwrap %} -Hi all- we're happy to announce that the general release of {{ version_str }}{% if plural %} are{%- else %} is{%- endif %} now available! -{% endfilter %} - - - -How to get it -------------- - -{% for version in versions %} -$ pip install ansible{% if is_ansible_base(version) %}-base{% endif %}=={{ 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 %} -* {{ pretty_version(version) }} -{% if is_ansible_base(version) %} - https://pypi.python.org/packages/source/a/ansible-base/ansible-base-{{ version }}.tar.gz -{% else %} - https://pypi.python.org/packages/source/a/ansible/ansible-{{ version }}.tar.gz -{% endif %} - SHA256: {{ hashes[version] }} -{% endfor %} - - -What's new in {{ version_str }} -{{ '-' * (14 + version_str | length) }} - -{% filter wordwrap %} -{% if plural %}These releases are{% else %}This release is a{% endif %} maintenance release{% if plural %}s{% endif %} containing numerous bugfixes. The full {% if plural %} changelogs are{% else %} changelog is{% 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 use the following link to report the regression: -{% endfilter %} - - - https://github.com/ansible/ansible/issues/new/choose - -{% filter wordwrap %} -In your issue, be sure to mention the 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 = """ -{% set plural = False if versions | length == 1 else True %} -{% set version = (versions|sort(attribute='ver_obj'))[-1] %} -@ansible -{{ version_str }} -{% if plural %} - have -{% else %} - has -{% endif %} -been released! Get -{% if plural %} -them -{% else %} -it -{% endif %} -on PyPI: pip install ansible{% if is_ansible_base(version) %}-base{% endif %}=={{ version }}, -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, -) - - -async def calculate_hash_from_tarball(session, version): - tar_url = f'https://pypi.python.org/packages/source/a/ansible-base/ansible-base-{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-base-{version}.tar.gz' - hash_url = f'https://releases.ansible.com/ansible-base/{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-base-{version}.tar.gz.sha {precreated_hash} does not' - f' match hash of tarball from pypi {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 is_ansible_base(version): - ''' - Determines if a version is an ansible-base version or not, by checking - if it is >= 2.10.0. Stops comparing when it gets to the first non-numeric - component to allow for .dev and .beta suffixes. - ''' - # Ignore .beta/.dev suffixes - ver_split = [] - for component in version.split('.'): - if not component.isdigit(): - if 'rc' in component: - ver_split.append(int(component.split('rc')[0])) - if 'b' in component: - ver_split.append(int(component.split('b')[0])) - continue - ver_split.append(int(component)) - return tuple(ver_split) >= (2, 10, 0) - - -# Currently only use with a single element list, but left general for later -# in case we need to refer to the releases collectively. -def release_variants(versions): - if all(is_ansible_base(v) for v in versions): - return 'ansible-base' - - if all(not is_ansible_base(v) for v in versions): - return 'Ansible' - - return 'Ansible and ansible-base' - - -def pretty_version(version): - return '{0} {1}'.format( - release_variants([version]), - version, - ) - - -def create_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, - pretty_version=pretty_version).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, - is_ansible_base=is_ansible_base, - pretty_version=pretty_version) - return message - - -def create_short_message(versions): - version_template = JINJA_ENV.get_template('version_string') - version_str = version_template.render(versions=versions, - pretty_version=pretty_version).strip() - - template = JINJA_ENV.get_template('short') - message = template.render(versions=versions, version_str=version_str, - is_ansible_base=is_ansible_base, - pretty_version=pretty_version) - message = ' '.join(message.split()) + '\n' - return message diff --git a/hacking/build_library/build_ansible/command_plugins/release_announcement.py b/hacking/build_library/build_ansible/command_plugins/release_announcement.py deleted file mode 100644 index edc928a0ec9..00000000000 --- a/hacking/build_library/build_ansible/command_plugins/release_announcement.py +++ /dev/null @@ -1,78 +0,0 @@ -# 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 sys -from collections import UserString -from ansible.module_utils.compat.version import LooseVersion - -# Pylint doesn't understand Python3 namespace modules. -from ..commands import Command # pylint: disable=relative-beyond-top-level -from .. import errors # pylint: disable=relative-beyond-top-level - - -class VersionStr(UserString): - def __init__(self, string): - super().__init__(string.strip()) - self.ver_obj = LooseVersion(string) - - -def transform_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 - - -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) - - -class ReleaseAnnouncementCommand(Command): - name = 'release-announcement' - - @classmethod - def init_parser(cls, add_parser): - parser = add_parser(cls.name, - 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") - - @classmethod - def main(cls, args): - if sys.version_info < (3, 6): - raise errors.DependencyError('The {0} subcommand needs Python-3.6+' - ' to run'.format(cls.name)) - - # Import here because these functions are invalid on Python-3.5 and the command plugins and - # init_parser() method need to be compatible with Python-3.4+ for now. - # Pylint doesn't understand Python3 namespace modules. - from .. announce import create_short_message, create_long_message # pylint: disable=relative-beyond-top-level - - args = transform_args(args) - - twitter_message = create_short_message(args.versions) - email_message = create_long_message(args.versions, args.name) - - write_message(args.twitter_out, twitter_message) - write_message(args.email_out, email_message) - return 0