3
Scan branches for merge proposals, create properties files for
4
pickup by jenkins, triggering jobs.
15
sys.path.insert(1, os.path.join(sys.path[0], '..'))
16
from common import osci_utils as u # noqa
18
USAGE = '''Usage: %prog [options]
21
Constructs merge proposal message by examining WORKSPACE files. Posts
22
comment on the merge proposal.
26
Dry run with debug output. Displays comment but does not save or post it.
29
Dry run with debug output. Creates comment file but does not post it.
32
Less noisy output. Create message, comment file, and post it.
37
MPC_SILENT = os.environ['MPC_SILENT']
42
BASE_NAME = os.environ['BASE_NAME']
43
DISPLAY_NAME = os.environ['DISPLAY_NAME']
44
MP_TRIGGER = os.environ['MP_TRIGGER']
45
MP_SRC_BR = os.environ['MP_SRC_BR']
46
MP_TGT_BR = os.environ['MP_TGT_BR']
47
JENKINS_URL = os.environ['JENKINS_URL']
48
BUILD_DISPLAY_NAME = os.environ['BUILD_DISPLAY_NAME']
49
BUILD_URL = os.environ['BUILD_URL']
50
BUILD_NUMBER = os.environ['BUILD_NUMBER']
51
JOB_NAME = os.environ['JOB_NAME']
52
BRANCH = os.environ['BRANCH']
54
BASE_NAME = 'manual-base-name'
55
DISPLAY_NAME = 'manual-disp-name'
56
MP_TRIGGER = 'manual-trigger'
57
MP_SRC_BR = 'manual-mp-src-br'
58
MP_TGT_BR = 'manual-mp-tgt-br'
59
JENKINS_URL = 'manual-jnk-url'
60
BUILD_DISPLAY_NAME = 'manual-bld-disp-name'
61
BUILD_URL = 'manual-url'
63
JOB_NAME = 'manual-job'
64
BRANCH = 'manual-branch'
67
def create_mp_comment_file(contexts, source, out_file, params):
68
'''Create MP comment file, return comment filename
73
out_file = tempfile.mktemp()
74
logging.info('Dry run, rendering to {} instead'.format(out_file))
76
templates_dir = 'populate'
77
for idx, context in enumerate(contexts):
78
target = os.path.join(u.WORKSPACE, out_file.format(idx))
79
logging.info('Rendering file: {}'.format(target))
80
u.render(source, target, context, templates_dir)
85
'''Define and handle command line parameters
87
# Define command line options
88
parser = optparse.OptionParser(USAGE)
89
parser.add_option("-d", "--debug",
90
help="Enable debug logging",
91
dest="debug", action="store_true", default=False)
92
parser.add_option('-0', '--dry-run',
93
help='Construct and display the message, but do not '
94
'create comment file and do not post comment.',
95
dest='dry', action='store_true', default=False)
96
parser.add_option('-X', '--no-post',
97
help='Do not post comment.',
98
dest='no_post', action='store_true', default=False)
99
parser.add_option('-q', '--quiet',
100
help='Do not display the message to console.',
101
dest='quiet', action='store_true', default=False)
103
params = parser.parse_args()
104
(opts, args) = params
106
# Handle parameters, inform user
108
logging.basicConfig(level=logging.DEBUG)
109
logging.info('Logging level set to DEBUG!')
110
logging.debug('parse opts: \n{}'.format(
111
yaml.dump(vars(opts), default_flow_style=False)))
112
logging.debug('parse args: {}'.format(args))
114
logging.basicConfig(level=logging.INFO)
116
logging.info('Workspace dir: {}'.format(u.WORKSPACE))
118
conf_map = u.read_yaml(u.MAP_FILE)
120
# gather info and results for comment
121
logging.info('Gathering env var items')
124
'BASE_NAME': BASE_NAME,
125
'DISPLAY_NAME': DISPLAY_NAME,
126
'MP_TRIGGER': MP_TRIGGER,
127
'MP_SRC_BR': MP_SRC_BR,
128
'JENKINS_URL': JENKINS_URL,
129
'BUILD_DISPLAY_NAME': BUILD_DISPLAY_NAME,
130
'BUILD_URL': BUILD_URL,
134
# construct results blurb by examining artifacts
135
logging.info('Constructing results blurb')
139
something_ran = False
141
for trigger_test, tt_attrs in conf_map['mp-trigger-tests'].iteritems():
142
# look for try touchfiles
144
logging.debug('Checking for {} try files'.format(trigger_test))
145
for try_mask in tt_attrs['try-mask']:
146
these_try_files = glob(os.path.join(u.WORKSPACE, try_mask))
147
try_files.extend(these_try_files)
148
if len(try_files) > 0:
150
trigger_tests.append(trigger_test)
151
logging.info('Found {} try file(s)'.format(trigger_test))
154
logging.info('No {} try file(s) found'.format(trigger_test))
157
# look for fail touchfiles
158
logging.debug('Checking for {} fyi files'.format(trigger_test))
160
for fail_mask in tt_attrs['fail-mask']:
161
logging.debug('Checking fail mask: {}'.format(fail_mask))
162
fyis = glob(os.path.join(u.WORKSPACE, fail_mask))
165
logging.debug('Found {} fyi file(s)'.format(trigger_test))
168
_line = ' {} FAIL: {}'.format(
169
trigger_test.upper(),
170
os.path.split(fyi)[1][8:].replace('.', ' '))
172
summary.append(_line)
174
logging.debug('No "fyi" files found to '
175
'include for mask: {}'.format(fail_mask))
177
# ran and didn't fail
179
_line = (' {} OK: passed'.format(trigger_test.upper()))
180
summary.append(_line)
182
# look for result files
185
logging.debug('Checking result mask for {}'.format(trigger_test))
186
for res_mask in tt_attrs['result-mask']:
187
these_res_files = glob(os.path.join(u.WORKSPACE, res_mask))
188
res_files.extend(these_res_files)
189
if len(res_files) > 0:
190
logging.info('Found {} results file(s)'.format(trigger_test))
191
# handle results files
192
for res_file in res_files:
193
pbs[res_file] = u.pb_it(res_file)
194
logging.debug('pastebin for {}: {}'.format(res_file,
196
llimit = conf_map['mp-conf']['line-limit']
197
_line = ('\n{} Results (max last {} lines):'.format(
198
trigger_test.upper(), llimit))
199
this_result.append(_line)
200
with open(res_file) as _file:
201
res_lines = [f_line.decode('ascii', 'ignore').rstrip()
202
for f_line in _file][-llimit:]
203
_line = ('\nFull {} test output: {}'.format(trigger_test,
205
res_lines.append(_line)
206
this_result.extend(res_lines)
208
_line = ('\n{} Results not found.'.format(trigger_test.upper()))
209
results.append(_line)
210
logging.info('No {} results files found to '
211
'include'.format(trigger_test))
214
results.extend(this_result)
216
logging.debug('pbs: {}'.format(pbs))
218
if not something_ran:
219
logging.error('It appears that nothing ran here.')
222
# add summary and results to context for template
223
contexts[0]['summary'] = '\n'.join(summary)
225
contexts[0]['results'] = '\n'.join(results)
227
# create mp comment file
228
logging.info('Creating mp-comment file')
229
out_file = 'mp-comment.{}'.format(BUILD_NUMBER)
230
comment_filename = create_mp_comment_file(contexts,
231
source='mp-comment.tmpl',
235
# read mp comment file and display to console
237
subject_result = "PASS"
239
subject_result = "FAIL"
241
subject = '[UOSCI] {}: {} test, {}'.format(subject_result,
242
'/'.join(trigger_tests),
245
logging.debug('Subject: {}'.format(subject))
246
f = open(comment_filename, 'r')
250
logging.info('Comment:\n')
256
# check for sandbox override (a noise avoider during dev)
257
if conf_map['mp-conf']['mp-comment-sandbox']:
258
lp_branch_url = conf_map['mp-conf']['sandbox-lp-branch-url']
259
mp_url = conf_map['mp-conf']['sandbox-mp-url']
260
logging.info('Sandbox is ENABLED: comments, if '
261
'enabled, will hit {} instead'.format(mp_url))
263
lp_branch_url = MP_TGT_BR
267
# useful for setting environment variable in a jenkins staging
268
# area to prevent mp comment posting globally
269
logging.info('MPC_SILENT env var override! No comment '
270
'will actually be posted')
272
if not opts.dry and not opts.no_post and not MPC_SILENT:
274
logging.debug('Connecting to LP via API')
275
launchpad = u.lpl_connect(anonymous=False)
276
logging.debug('Looking up MP object from LP API')
278
mp = u.lpl_get_mp(launchpad, mp_url, lp_branch_url)
279
except (AttributeError, TypeError):
280
logging.error('Merge proposal not found for '
281
'{} on {}'.format(mp_url, lp_branch_url))
283
logging.info('Posting comment to {}'.format(mp_url))
284
u.lpl_mp_post_comment(launchpad, mp, subject, comment)
286
logging.info('Not posting comment due to options')
289
if __name__ == '__main__':
290
print 'Ubuntu OSCI Merge Proposal Commentator'