summaryrefslogtreecommitdiffstats
path: root/release/sign-tag.py
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2020-02-20 15:13:51 -0500
committerDavid Pursehouse <dpursehouse@collab.net>2020-02-21 05:20:58 +0000
commit8c268c0e7bd18d1e2f4f526cd406c569312a5f23 (patch)
treee10fd5adc97ec9321c6a351134a1d031e1ac0adf /release/sign-tag.py
parentd9254599f9bb47632313ecb90c5f281ceca5da3a (diff)
downloadgit-repo-8c268c0e7bd18d1e2f4f526cd406c569312a5f23.tar.gz
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 <vapier@google.com> Reviewed-by: David Pursehouse <dpursehouse@collab.net>
Diffstat (limited to 'release/sign-tag.py')
-rwxr-xr-xrelease/sign-tag.py135
1 files changed, 135 insertions, 0 deletions
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 @@
1#!/usr/bin/env python3
2# Copyright (C) 2020 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16"""Helper tool for signing repo release tags correctly.
17
18This is intended to be run only by the official Repo release managers.
19"""
20
21import argparse
22import os
23import re
24import subprocess
25import sys
26
27import util
28
29
30# We currently sign with the old DSA key as it's been around the longest.
31# We should transition to RSA by Jun 2020, and ECC by Jun 2021.
32KEYID = util.KEYID_DSA
33
34# Regular expression to validate tag names.
35RE_VALID_TAG = r'^v([0-9]+[.])+[0-9]+$'
36
37
38def sign(opts):
39 """Tag the commit & sign it!"""
40 # We use ! at the end of the key so that gpg uses this specific key.
41 # Otherwise it uses the key as a lookup into the overall key and uses the
42 # default signing key. i.e. It will see that KEYID_RSA is a subkey of
43 # another key, and use the primary key to sign instead of the subkey.
44 cmd = ['git', 'tag', '-s', opts.tag, '-u', f'{opts.key}!',
45 '-m', f'repo {opts.tag}', opts.commit]
46
47 key = 'GNUPGHOME'
48 print('+', f'export {key}="{opts.gpgdir}"')
49 oldvalue = os.getenv(key)
50 os.putenv(key, opts.gpgdir)
51 util.run(opts, cmd)
52 if oldvalue is None:
53 os.unsetenv(key)
54 else:
55 os.putenv(key, oldvalue)
56
57
58def check(opts):
59 """Check the signature."""
60 util.run(opts, ['git', 'tag', '--verify', opts.tag])
61
62
63def postmsg(opts):
64 """Helpful info to show at the end for release manager."""
65 cmd = ['git', 'rev-parse', 'remotes/origin/stable']
66 ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE)
67 current_release = ret.stdout.strip()
68
69 cmd = ['git', 'log', '--format=%h (%aN) %s', '--no-merges',
70 f'remotes/origin/stable..{opts.tag}']
71 ret = util.run(opts, cmd, encoding='utf-8', stdout=subprocess.PIPE)
72 shortlog = ret.stdout.strip()
73
74 print(f"""
75Here's the short log since the last release.
76{shortlog}
77
78To push release to the public:
79 git push origin {opts.commit}:stable {opts.tag} -n
80NB: People will start upgrading to this version immediately.
81
82To roll back a release:
83 git push origin --force {current_release}:stable -n
84""")
85
86
87def get_parser():
88 """Get a CLI parser."""
89 parser = argparse.ArgumentParser(description=__doc__)
90 parser.add_argument('-n', '--dry-run',
91 dest='dryrun', action='store_true',
92 help='show everything that would be done')
93 parser.add_argument('--gpgdir',
94 default=os.path.join(util.HOMEDIR, '.gnupg', 'repo'),
95 help='path to dedicated gpg dir with release keys '
96 '(default: ~/.gnupg/repo/)')
97 parser.add_argument('-f', '--force', action='store_true',
98 help='force signing of any tag')
99 parser.add_argument('--keyid', dest='key',
100 help='alternative signing key to use')
101 parser.add_argument('tag',
102 help='the tag to create (e.g. "v2.0")')
103 parser.add_argument('commit', default='HEAD', nargs='?',
104 help='the commit to tag')
105 return parser
106
107
108def main(argv):
109 """The main func!"""
110 parser = get_parser()
111 opts = parser.parse_args(argv)
112
113 if not os.path.exists(opts.gpgdir):
114 parser.error(f'--gpgdir does not exist: {opts.gpgdir}')
115
116 if not opts.force and not re.match(RE_VALID_TAG, opts.tag):
117 parser.error(f'tag "{opts.tag}" does not match regex "{RE_VALID_TAG}"; '
118 'use --force to sign anyways')
119
120 if opts.key:
121 print(f'Using custom key to sign: {opts.key}')
122 else:
123 print('Using official Repo release key to sign')
124 opts.key = KEYID
125 util.import_release_key(opts)
126
127 sign(opts)
128 check(opts)
129 postmsg(opts)
130
131 return 0
132
133
134if __name__ == '__main__':
135 sys.exit(main(sys.argv[1:]))