From 8c268c0e7bd18d1e2f4f526cd406c569312a5f23 Mon Sep 17 00:00:00 2001 From: Mike Frysinger Date: Thu, 20 Feb 2020 15:13:51 -0500 Subject: release: import some helper scripts for managing official releases Change-Id: I9abebfef5ad19f6a637bc3b12effea9dd6d0269d Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256234 Tested-by: Mike Frysinger Reviewed-by: David Pursehouse --- release/sign-tag.py | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100755 release/sign-tag.py (limited to 'release/sign-tag.py') diff --git a/release/sign-tag.py b/release/sign-tag.py new file mode 100755 index 00000000..7b4b4cab --- /dev/null +++ b/release/sign-tag.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +# Copyright (C) 2020 The Android Open Source Project +# +# 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. + +"""Helper tool for signing repo release tags correctly. + +This is intended to be run only by the official Repo release managers. +""" + +import argparse +import os +import re +import subprocess +import sys + +import util + + +# We currently sign with the old DSA key as it's been around the longest. +# We should transition to RSA by Jun 2020, and ECC by Jun 2021. +KEYID = util.KEYID_DSA + +# Regular expression to validate tag names. +RE_VALID_TAG = r'^v([0-9]+[.])+[0-9]+$' + + +def sign(opts): + """Tag the commit & sign it!""" + # We use ! at the end of the key so that gpg uses this specific key. + # Otherwise it uses the key as a lookup into the overall key and uses the + # default signing key. i.e. It will see that KEYID_RSA is a subkey of + # another key, and use the primary key to sign instead of the subkey. + cmd = ['git', 'tag', '-s', opts.tag, '-u', f'{opts.key}!', + '-m', f'repo {opts.tag}', opts.commit] + + key = 'GNUPGHOME' + print('+', f'export {key}="{opts.gpgdir}"') + oldvalue = os.getenv(key) + os.putenv(key, opts.gpgdir) + util.run(opts, cmd) + if oldvalue is None: + os.unsetenv(key) + else: + os.putenv(key, oldvalue) + + +def check(opts): + """Check the signature.""" + util.run(opts, ['git', 'tag', '--verify', opts.tag]) + + +def postmsg(opts): + """Helpful info to show at the end for release manager.""" + cmd = ['git', 'rev-parse', 'remotes/origin/stable'] + ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE) + current_release = ret.stdout.strip() + + cmd = ['git', 'log', '--format=%h (%aN) %s', '--no-merges', + f'remotes/origin/stable..{opts.tag}'] + ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE) + shortlog = ret.stdout.strip() + + print(f""" +Here's the short log since the last release. +{shortlog} + +To push release to the public: + git push origin {opts.commit}:stable {opts.tag} -n +NB: People will start upgrading to this version immediately. + +To roll back a release: + git push origin --force {current_release}:stable -n +""") + + +def get_parser(): + """Get a CLI parser.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('-n', '--dry-run', + dest='dryrun', action='store_true', + help='show everything that would be done') + parser.add_argument('--gpgdir', + default=os.path.join(util.HOMEDIR, '.gnupg', 'repo'), + help='path to dedicated gpg dir with release keys ' + '(default: ~/.gnupg/repo/)') + parser.add_argument('-f', '--force', action='store_true', + help='force signing of any tag') + parser.add_argument('--keyid', dest='key', + help='alternative signing key to use') + parser.add_argument('tag', + help='the tag to create (e.g. "v2.0")') + parser.add_argument('commit', default='HEAD', nargs='?', + help='the commit to tag') + return parser + + +def main(argv): + """The main func!""" + parser = get_parser() + opts = parser.parse_args(argv) + + if not os.path.exists(opts.gpgdir): + parser.error(f'--gpgdir does not exist: {opts.gpgdir}') + + if not opts.force and not re.match(RE_VALID_TAG, opts.tag): + parser.error(f'tag "{opts.tag}" does not match regex "{RE_VALID_TAG}"; ' + 'use --force to sign anyways') + + if opts.key: + print(f'Using custom key to sign: {opts.key}') + else: + print('Using official Repo release key to sign') + opts.key = KEYID + util.import_release_key(opts) + + sign(opts) + check(opts) + postmsg(opts) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) -- cgit v1.2.3-54-g00ecf