election/openstack_election/cmds/update_releases_calendar.py

177 lines
6.6 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import argparse
import datetime
import os
import sys
import textwrap
from openstack_election import config
from openstack_election import yamlutils
conf = config.load_conf()
rst_template = textwrap.dedent("""
{release} TC and PTL Elections
---------------------------
.. _{nrl}-election-email-deadline:
{release} Election Email Deadline
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Contributors that will be in the electorate for the upcoming election
should confirm their gerrit email addresses by this date ({deadline_date}
at {deadline_time} UTC). Electorate rolls are generated after this date and ballots will
be sent to the listed gerrit email address.
.. _{nrl}-election-nominations:
{release} Election Nomination Begins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Candidates interested in serving for the next calendar year (TC), or
development cycle (PTL) should announce their candidacies and platforms during
this week. Please see the `Election site`_ for specific timing information.
.. _{nrl}-election-campaigning:
{release} Election Campaigning Begins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The electorate has time to ask candidates questions about their platforms
and debate topics before polling begins. Please see the `Election site`_ for
specific timing information.
.. _{nrl}-election-voting:
{release} Election Polling Begins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Election polling for open seats on the TC and any required PTL elections.
Please see the `Election site`_ for specific timing information.
.. _{nrl}-election-close:
{release} Election Polling Ends
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
All polls close in the {release} Election and results announced. Please see the
`Election site`_ for specific timing information.
.. _Election site: https://governance.openstack.org/election/
""") # noqa
def next_release_letter(release_name):
ord_a = ord('a')
next_ord = (ord(release_name[0]) - ord_a + 1) % 26
next_letter = chr(ord_a + next_ord)
return next_letter
def custom_strftime(format, t):
def _suffix(d):
return {1: 'st', 2: 'nd', 3: 'rd'}.get(d % 20, 'th')
return t.strftime(format).replace('{S}', str(t.day) + _suffix(t.day))
def add_election_event_to_schedule(nrl, election_events,
schedule_events, schedule_data):
election_event_idx = 0
for cycle_event in schedule_data["cycle"]:
(cycle_event_start, cycle_event_end) = (
datetime.date.fromisoformat(cycle_event["start"]),
datetime.date.fromisoformat(cycle_event["end"]))
event_type = election_events[election_event_idx]
event_label = f"{nrl}-election-{event_type}"
(event_start, event_end) = schedule_events[event_label]
if cycle_event_start < event_start.date():
continue
if "x-project" not in cycle_event:
cycle_event["x-project"] = []
if event_label not in cycle_event["x-project"]:
cycle_event["x-project"].append(event_label)
if event_end.date() < cycle_event_end:
election_event_idx += 1
if election_event_idx >= len(election_events):
break
def main():
if conf["election_type"] != "combined":
print("This tool currently only supports 'combined' elections",
file=sys.stderr)
return 1
description = ('Update development cycle in openstack/releases with '
'key election events')
parser = argparse.ArgumentParser(description)
parser.add_argument('--releases-repo', dest='releases_repo',
required=True,
help=('Path to a clone of the releases repo'))
args = parser.parse_args()
series_path = os.path.join(args.releases_repo, "data",
"series_status.yaml")
with open(series_path) as f:
series_data = yamlutils.loads(f)
release = conf.get("release")
release_name = series_data[0]["name"]
nrl = next_release_letter(release_name)
schedule_path = os.path.join(args.releases_repo, "doc", "source",
release_name, "schedule.yaml")
with open(schedule_path) as f:
schedule_data = yamlutils.loads(f)
rst_path = os.path.join(args.releases_repo, "doc", "source", release_name,
"schedule.rst")
with open(rst_path) as f:
rst = f.read()
email_deadline = conf["timeframe"]["email_deadline"]
deadline_date = custom_strftime('%B {S}, %Y', email_deadline)
deadline_time = email_deadline.strftime("%H:%M")
rst_blob = rst_template.format(**dict(release=release,
nrl=nrl,
deadline_time=deadline_time,
deadline_date=deadline_date,))
election_canary = rst_blob.split("\n")[1]
if election_canary not in rst:
print(f"Updating {rst_path} to add {rst_blob}\n")
with open(rst_path, "w") as f:
f.write(rst)
f.write(rst_blob)
schedule_events = {f"{nrl}-election-close":
(conf["timeframe"]["end"], conf["timeframe"]["end"]),
f"{nrl}-election-email-deadline":
(email_deadline, email_deadline), }
for event in conf["timeline"]:
event_type = event["name"].split()[-1].lower()
if event_type == "election":
event_type = "voting"
event_label = f"{nrl}-election-{event_type}"
schedule_events[event_label] = (event["start"], event["end"])
election_events = ["email-deadline"]
add_election_event_to_schedule(nrl, election_events, schedule_events,
schedule_data)
election_events = ["nominations", "campaigning", "voting", "close"]
add_election_event_to_schedule(nrl, election_events, schedule_events,
schedule_data)
with open(schedule_path, "w") as f:
print(f"Updating {schedule_path}")
f.write(yamlutils.dumps(schedule_data))