blob: ab81e030cd0a3a75b7e31b4cd3909ffb8b7dc7ee [file] [log] [blame]
#!/usr/bin/env python
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You 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
try:
import argcomplete
except ImportError:
argcomplete = False
import os
import yaml
import json
import sys
import smtplib
import subprocess
from email.mime.text import MIMEText
class objectify(object):
def __init__(self, d):
self.__dict__ = d
def parseArgsAndConfig():
parser = argparse.ArgumentParser(description='Generates Release Candidate Vote Email')
parser.add_argument('-v', '--verbose', help='verbose output', action='store_true')
parser.add_argument('-n', '--dryrun', help='verbose output', action='store_true')
parser.add_argument('-mc', '--mail-conf', help='YAML configuration file for mailer', metavar='YAML', type=argparse.FileType('r'), required=False)
parser.add_argument('-rc', '--rc-conf', help='JSON configuration file for release candidate', metavar='JSON', type=argparse.FileType('r'), required=True)
parser.add_argument('-s', '--subject', help='Component name for subject line', metavar='NAME')
parser.add_argument('-i', '--signature', help='Signature line to conclude email', metavar='SIGNATURE')
if argcomplete:
argcomplete.autocomplete(parser)
args = parser.parse_args()
args.rcConfig = json.load(args.rc_conf)
if not args.dryrun:
if 'mail_conf' not in args:
parser.error("--mail-config required except for a dryrun.")
args.mailConfig = yaml.load(args.mail_conf)
if 'mail' not in args.mailConfig:
print('Error: bad configuration, need "mail" properties.')
return
else:
args.mailConfig = args.mailConfig['mail']
else:
args.mailConfig = None
return args
def componentList(config, version):
for r in config['RepoList']:
key = r.replace('-', '_')
hash = config[key]['hash']
name = config[key]['name'] if 'name' in config[key] else '???'
repo = config[key]['repository']
yield objectify({
'id': r,
'hash': hash,
'name': name,
'url': repo[0:-4],
'artifacts': artifactLinks(r, version.v, version.rc)
})
def artifactLinks(name, version, rc):
root = 'https://dist.apache.org/repos/dist/dev/openwhisk/%s' % (rc)
tgz = '%s-%s-sources.tar.gz' % (name, version)
asc = '%s.asc' % tgz
sha = '%s.sha512' % tgz
return objectify({
'tgz': '%s/%s' % (root, tgz),
'asc': '%s/%s' % (root, asc),
'sha': '%s/%s' % (root, sha)
})
def currentGitHead():
return subprocess.check_output(['git', 'show-ref', '--hash', '--abbrev', '--', 'refs/remotes/upstream/master']).strip()
def gitHashes(components):
s = map(lambda r: "* %s: %s\n %s/commits/%s\n %s\n %s\n %s\n" % (r.name, r.hash, r.url, r.hash, r.artifacts.tgz, r.artifacts.asc, r.artifacts.sha), components)
return '\n'.join(list(s))
def rcverify(components, version):
s = map(lambda r: "rcverify.sh %s '%s' %s %s" % (r.id, r.name, version.v, version.rc), components)
return '\n'.join(list(s))
def releaseVersion(config):
return objectify({
'v': config['versioning']['version'],
'rc': config['versioning']['pre_release_version']
})
def sendVoteEmail(mailConfig, rcConfig, dryrun, subjectLineId, signature):
version = releaseVersion(rcConfig)
components = list(componentList(rcConfig, version))
componentsString = ', '.join(map(lambda c: c.name, components))
componentCount = len(components)
subject = '[VOTE] Release Apache %s (v%s, %s)' % (subjectLineId if subjectLineId else componentsString, version.v, version.rc)
content = """Hi,
This is a call to vote on releasing version {version} release candidate {rc} of the following {N} with artifacts built from the Git repositories and commit IDs listed below.
{githashes}
This release is comprised of source code distribution only.
You can use this UNIX script to download the release and verify the checklist below:
https://gitbox.apache.org/repos/asf?p=openwhisk-release.git;a=blob_plain;f=tools/rcverify.sh;hb={HEAD}
Usage:
curl -s "https://gitbox.apache.org/repos/asf?p=openwhisk-release.git;a=blob_plain;f=tools/rcverify.sh;hb={HEAD}" -o rcverify.sh
chmod +x rcverify.sh
{rcverifies}
Please vote to approve this release:
[ ] +1 Approve the release
[ ] 0 Don't care
[ ] -1 Don't release, because ...
Release verification checklist for reference:
[ ] Download links are valid.
[ ] Checksums and PGP signatures are valid.
[ ] Source code artifacts have correct names matching the current release.
[ ] LICENSE and NOTICE files are correct for each OpenWhisk repository.
[ ] All files have license headers as specified by OpenWhisk project policy [1].
[ ] No compiled archives bundled in source archive.
This majority vote is open for at least 72 hours.
{signature}
[1] https://github.com/apache/openwhisk-release/blob/master/docs/license_compliance.md""".format(
version = version.v,
rc = version.rc,
N = ("%s project modules" % componentCount) if componentCount > 1 else "project module",
HEAD = currentGitHead(),
githashes = gitHashes(components),
rcverifies = rcverify(components, version),
signature = ("\n%s" % signature) if signature else "")
if (dryrun):
print(subject)
print(content)
return
print('Sending email: %s -> %s' % (mailConfig['from'], mailConfig['to']))
msg = MIMEText(content, _charset='utf-8')
msg['From'] = mailConfig['from']
msg['To'] = mailConfig['to']
msg['Subject'] = subject
server = smtplib.SMTP(mailConfig['smtp'])
if mailConfig['useTLS']:
server.starttls()
if 'username' in mailConfig:
server.login(mailConfig['username'], mailConfig['password'])
server.sendmail(mailConfig['from'], mailConfig['to'], msg.as_string())
server.quit()
def main(args):
sendVoteEmail(args.mailConfig, args.rcConfig, args.dryrun, args.subject, args.signature)
if __name__ == "__main__":
args = parseArgsAndConfig()
if args:
main(args)
else:
sys.exit(-1)