mirror of https://github.com/ansible/ansible.git
Remove old release announcement scripts (#80371)
These have been replaced by `packaging/release.py`pull/80407/head
parent
d7e7c2d872
commit
4f7f7d1c26
@ -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
|
|
@ -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
|
|
Loading…
Reference in New Issue