Parent: [267cc0] (diff)

Download this file

push_re.py    265 lines (235 with data), 10.3 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
import os
import re
import readline # changes raw_input to allow line editing (sorry, pyflakes)
import shlex
import string
import subprocess
import sys
from collections import defaultdict
from ConfigParser import ConfigParser
from datetime import date
from urlparse import urljoin
from optparse import OptionParser
from allura.lib import rest_api
VERBOSE = 2
DRY_RUN = False
CP = ConfigParser()
re_ticket_ref = re.compile(r'\[#(\d+)\]')
re_allura_ref = re.compile(r'\nreference: ')
re_git_dir = re.compile(r'.*\.git/?\Z')
re_ws = re.compile(r'\s')
CRED={}
def main():
global DRY_RUN, VERBOSE
op = OptionParser()
op.add_option('--dry-run', action='store_true', dest='dry_run', default=False)
(options, args) = op.parse_args(sys.argv[1:])
if options.dry_run:
DRY_RUN = True
print("This is a dry-run: tags will be created locally but nothing will be pushed.")
CP.read(os.path.join(os.environ['HOME'], '.forgepushrc'))
engineer = option('re', 'engineer', 'Name of engineer pushing: ')
sf_identity = option('re', 'sf_identity', 'Your SourceForge.net user-name: ')
api_key = option('re', 'api_key', 'Forge API Key:')
secret_key = option('re', 'secret_key', 'Forge Secret Key:')
classic_path = option('re', 'classic_path', 'The path to your forge-classic repo:')
theme_path = option('re', 'theme_path', 'The path to your sftheme repo:')
if not re_git_dir.match(classic_path):
classic_path += '/.git/'
if not re_git_dir.match(theme_path):
theme_path += '/.git/'
CRED['api_key'] = api_key
CRED['secret_key'] = secret_key
if ask_yes_or_no('Confirm each git command?', 'y'):
VERBOSE = 2
elif ask_yes_or_no('Echo each git command?', 'y'):
VERBOSE = 1
else:
VERBOSE = 0
if VERBOSE:
print("Making sure our existing tags are up-to-date...")
git('fetch origin --tags')
git('fetch origin --tags', git_dir=classic_path)
git('fetch origin --tags', git_dir=theme_path)
text, new_tag = make_ticket_text(engineer, classic_path, theme_path)
raw_input("Make sure you merged dev up into master for forge, forge_classic, and sftheme.")
raw_input("Make sure there are no new dependencies, or RPM's are built for all dependencies.")
raw_input("Make sure a new sandbox builds and starts without engr help.")
print('*** Create a ticket on SourceForge (https://sourceforge.net/p/allura/tickets/new/) with the following contents:')
print('*** Summary: Production Push (R:%s, D:%s) - allura' % (
new_tag, date.today().strftime('%Y%m%d')))
print('---BEGIN---')
print(text)
print('---END---')
newforge_num = raw_input('What is the newforge ticket number? ')
print('*** Create a SOG Trac ticket (https://control.sog.geek.net/sog/trac/newticket?keywords=LIAISON) with the same summary...')
print('---BEGIN---')
sog_text = re_ticket_ref.sub('FO:\g<1>', text)
print(re_allura_ref.sub('\nreference: https://sourceforge.net/p/allura/tickets/%s/' % newforge_num, sog_text))
print('---END---')
sog_num = raw_input('What is the SOG ticket number? ')
raw_input('Now link the two tickets.')
if VERBOSE:
print("Ask for approval (copy/paste the following text into Jabber)")
raw_input('Allura push, %s, for your approval (https://control.sog.geek.net/sog/trac/ticket/%s).' % (new_tag, sog_num))
if VERBOSE:
print("Tag and push the Allura repo for release...")
tag_message = '[#%s] - Push to RE' % newforge_num
git('tag', '-a', '-m', tag_message, new_tag, 'master')
git('push', 'origin', 'master', new_tag)
git('push', 'control.sog.geek.net:allura-live', 'master', new_tag)
if ask_yes_or_no('Do you want to push to the public repo, too?', 'n'):
git('push', 'ssh://%s@git.code.sf.net/p/allura/git.git' % sf_identity, 'master', new_tag, fail_ok=True)
if VERBOSE:
print("Tag and push the forge-classic repo for release...")
git('tag', '-a', '-m', tag_message, new_tag, 'master', git_dir=classic_path)
git('push', 'origin', 'master', new_tag, git_dir=classic_path)
git('push', 'control.sog.geek.net:forge-classic-live', 'master', new_tag, git_dir=classic_path)
if VERBOSE:
print("Tag and push the sftheme repo for release...")
git('tag', '-a', '-m', tag_message, new_tag, 'master', git_dir=theme_path)
git('push', 'origin', 'master', new_tag, git_dir=theme_path)
git('push', 'control.sog.geek.net:sftheme-live', 'master', new_tag, git_dir=theme_path)
if VERBOSE:
print("Tell SOG we're ready (copy/paste the following text into Jabber)")
print('Allura release %s is ready for pushing (https://control.sog.geek.net/sog/trac/ticket/%s).' % (new_tag, sog_num))
CP.write(open(os.path.join(os.environ['HOME'], '.forgepushrc'), 'w'))
if VERBOSE:
print("That's all, folks!")
def make_ticket_text(engineer, classic_path, theme_path):
tag_prefix = date.today().strftime('allura_%Y%m%d')
# get release tag
existing_tags_today = git('tag -l %s*' % tag_prefix)
if existing_tags_today:
new_tag = '%s.%.2d' % (tag_prefix, len(existing_tags_today))
else:
new_tag = tag_prefix
since_last_release = get_last_release_tag() + '..master'
format = '--format=* %h %s'
if VERBOSE:
print("Examining commits to build the list of fixed tickets...")
changes = git('log', format, since_last_release, strip_eol=False)
changes += git('log', format, since_last_release, git_dir=classic_path, strip_eol=False)
changes += git('log', format, since_last_release, git_dir=theme_path, strip_eol=False)
if not changes:
print('There were no commits found; maybe you forgot to merge dev->master? (Ctrl-C to abort)')
changelog = ''.join(changes or [])
changes = ''.join(format_changes(changes))
print('Changelog:\n%s' % changelog)
print('Tickets:\n%s' % changes)
prelaunch = []
postlaunch = []
if ask_yes_or_no('Does this release require a migration?', 'y'):
prelaunch.append('* dump the database in case we need to roll back')
postlaunch.append('* allurapaste flyway --url mongo://sfn-mongo:27017/')
if ask_yes_or_no('Does this release require ensure_index?', 'y'):
postlaunch.append('* allurapaste ensure_index /var/local/config/production.ini')
if postlaunch:
postlaunch = [ 'From sfu-scmprocess-1 do the following:\n' ] + postlaunch
postlaunch = '\n'.join(postlaunch)
else:
postlaunch = '-none-'
if prelaunch:
prelaunch = [ 'From sfn-mongo do the following:\n' ] + prelaunch
prelaunch = '\n'.join(prelaunch)
else:
prelaunch = '-none-'
return TICKET_TEMPLATE.substitute(locals()), new_tag
def format_changes(changes):
if not changes:
yield '-none-'
ticket_groups = defaultdict(list)
for change in changes:
for m in re_ticket_ref.finditer(change):
ticket_groups[m.group(0)].append(change)
try:
cli = rest_api.RestClient(
base_uri='http://sourceforge.net', **CRED)
for ref, commits in sorted(ticket_groups.iteritems()):
ticket_num = ref[2:-1]
ticket = cli.request(
'GET',
urljoin('/rest/p/allura/tickets/', str(ticket_num)) + '/')['ticket']
if ticket is None: continue
verb = {
'validation': 'Fix',
'closed': 'Fix' }.get(ticket['status'], 'Address')
yield ' * %s %s: %s\n' % (verb, ref, ticket['summary'])
except:
print('*** ERROR CONTACTING FORGE FOR TICKET SUMMARIES ***')
raise
for ci in changes:
yield ci
def ask_yes_or_no(prompt, default):
result = raw_input('%s [%s] ' % (prompt, default)) or default
return result[:1].lower() in ('y', '1')
def assemble_command(*args):
quoted = [ "'%s'" % arg if re_ws.search(arg) else arg for arg in args ]
return ' '.join(quoted)
def git(*args, **kwargs):
if len(args)==1 and isinstance(args[0], basestring):
argv = shlex.split(args[0])
else:
argv = list(args)
if argv[0] != 'git':
argv.insert(0, 'git')
if DRY_RUN and argv[1]=='push':
argv.insert(2, '--dry-run')
if 'git_dir' in kwargs:
argv.insert(1, '--git-dir=%s' % kwargs['git_dir'])
full_command = assemble_command(*argv)
if VERBOSE==2:
raw_input(full_command)
elif VERBOSE==1:
print(full_command)
p = subprocess.Popen(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
rc = p.wait()
output = p.stdout.readlines()
if kwargs.get('strip_eol', True):
output = [ line[:-1] for line in output ]
if rc:
print('Error: %s' % full_command)
for line in output: print(line.rstrip())
if not kwargs.get('fail_ok', False):
import pdb; pdb.set_trace()
return output
def get_last_release_tag():
has_clear_history = getattr(readline, 'clear_history')
if has_clear_history:
readline.clear_history()
for rtag in git('tag -l release_*'):
readline.add_history(rtag)
for atag in git('tag -l allura_*'):
readline.add_history(atag)
default = atag or rtag or ''
result = raw_input('Last successful push? [%s] ' % default) or default
if has_clear_history:
readline.clear_history()
return result
def option(section, key, prompt=None):
if not CP.has_section(section):
CP.add_section(section)
if CP.has_option(section, key):
value = CP.get(section, key)
else:
value = raw_input(prompt or ('%s: ' % key))
CP.set(section, key, value)
return value
TICKET_TEMPLATE=string.Template('''{{{
#!push
(engr) Name of Engineer pushing: $engineer
(engr) Which code tree(s): allura, forge-classic, sftheme
(engr) Is configtree to be pushed?: no
(engr) Which release/revision is going to be synced?: $new_tag
(engr) Itemized list of changes to be launched with sync:
$changes
Pre-launch dependencies:
$prelaunch
Post-launch dependencies:
$postlaunch
(engr) Approved for release (Dean/Dave/John): None
(sog) Outcome of sync:
reference:
}}}''')
if __name__ == '__main__':
main()