~1chb1n/ubuntu-openstack-ci/mptest0

« back to all changes in this revision

Viewing changes to tools/mp_comment.py

  • Committer: Ryan Beisner
  • Date: 2016-02-23 14:26:10 UTC
  • mfrom: (30.2.201 ubuntu-openstack-ci)
  • Revision ID: ryan.beisner@canonical.com-20160223142610-g4tajprej8p21o08
Rebase parent

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
'''
 
3
Scan branches for merge proposals, create properties files for
 
4
pickup by jenkins, triggering jobs.
 
5
'''
 
6
 
 
7
import optparse
 
8
import logging
 
9
import yaml
 
10
import os
 
11
import sys
 
12
import tempfile
 
13
from glob import glob
 
14
 
 
15
sys.path.insert(1, os.path.join(sys.path[0], '..'))
 
16
from common import osci_utils as u  # noqa
 
17
 
 
18
USAGE = '''Usage: %prog [options]
 
19
 
 
20
Default behavior:
 
21
  Constructs merge proposal message by examining WORKSPACE files.  Posts
 
22
  comment on the merge proposal.
 
23
 
 
24
For example:
 
25
    %prog -0d
 
26
  Dry run with debug output.  Displays comment but does not save or post it.
 
27
 
 
28
    %prog -Xd
 
29
  Dry run with debug output.  Creates comment file but does not post it.
 
30
 
 
31
    %prog -q
 
32
  Less noisy output.  Create message, comment file, and post it.
 
33
'''
 
34
 
 
35
 
 
36
try:
 
37
    MPC_SILENT = os.environ['MPC_SILENT']
 
38
except KeyError:
 
39
    MPC_SILENT = False
 
40
 
 
41
try:
 
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']
 
53
except KeyError:
 
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'
 
62
    BUILD_NUMBER = '000'
 
63
    JOB_NAME = 'manual-job'
 
64
    BRANCH = 'manual-branch'
 
65
 
 
66
 
 
67
def create_mp_comment_file(contexts, source, out_file, params):
 
68
    '''Create MP comment file, return comment filename
 
69
    '''
 
70
    (opts, args) = params
 
71
 
 
72
    if opts.dry:
 
73
        out_file = tempfile.mktemp()
 
74
        logging.info('Dry run, rendering to {} instead'.format(out_file))
 
75
 
 
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)
 
81
    return target
 
82
 
 
83
 
 
84
def main():
 
85
    '''Define and handle command line parameters
 
86
    '''
 
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)
 
102
 
 
103
    params = parser.parse_args()
 
104
    (opts, args) = params
 
105
 
 
106
    # Handle parameters, inform user
 
107
    if opts.debug:
 
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))
 
113
    else:
 
114
        logging.basicConfig(level=logging.INFO)
 
115
 
 
116
    logging.info('Workspace dir: {}'.format(u.WORKSPACE))
 
117
 
 
118
    conf_map = u.read_yaml(u.MAP_FILE)
 
119
 
 
120
    # gather info and results for comment
 
121
    logging.info('Gathering env var items')
 
122
    contexts = []
 
123
    contexts.append({
 
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,
 
131
        'JOB_NAME': JOB_NAME
 
132
    })
 
133
 
 
134
    # construct results blurb by examining artifacts
 
135
    logging.info('Constructing results blurb')
 
136
    summary = []
 
137
    results = []
 
138
    pbs = {}
 
139
    something_ran = False
 
140
    trigger_tests = []
 
141
    for trigger_test, tt_attrs in conf_map['mp-trigger-tests'].iteritems():
 
142
        # look for try touchfiles
 
143
        try_files = []
 
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:
 
149
            something_ran = True
 
150
            trigger_tests.append(trigger_test)
 
151
            logging.info('Found {} try file(s)'.format(trigger_test))
 
152
        else:
 
153
            # did not run
 
154
            logging.info('No {} try file(s) found'.format(trigger_test))
 
155
            continue
 
156
 
 
157
        # look for fail touchfiles
 
158
        logging.debug('Checking for {} fyi files'.format(trigger_test))
 
159
        passed = True
 
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))
 
163
            if len(fyis) > 0:
 
164
                passed = False
 
165
                logging.debug('Found {} fyi file(s)'.format(trigger_test))
 
166
                # handle fyi files
 
167
                for fyi in fyis:
 
168
                    _line = '    {} FAIL: {}'.format(
 
169
                        trigger_test.upper(),
 
170
                        os.path.split(fyi)[1][8:].replace('.', ' '))
 
171
                    logging.debug(_line)
 
172
                    summary.append(_line)
 
173
            else:
 
174
                logging.debug('No "fyi" files found to '
 
175
                              'include for mask: {}'.format(fail_mask))
 
176
 
 
177
        # ran and didn't fail
 
178
        if passed:
 
179
            _line = ('    {} OK: passed'.format(trigger_test.upper()))
 
180
            summary.append(_line)
 
181
 
 
182
        # look for result files
 
183
        res_files = []
 
184
        this_result = []
 
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,
 
195
                                                           pbs[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,
 
204
                                                            pbs[res_file]))
 
205
                res_lines.append(_line)
 
206
                this_result.extend(res_lines)
 
207
        else:
 
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))
 
212
 
 
213
        if not passed:
 
214
            results.extend(this_result)
 
215
 
 
216
    logging.debug('pbs: {}'.format(pbs))
 
217
 
 
218
    if not something_ran:
 
219
        logging.error('It appears that nothing ran here.')
 
220
        sys.exit(1)
 
221
 
 
222
    # add summary and results to context for template
 
223
    contexts[0]['summary'] = '\n'.join(summary)
 
224
#    if not passed:
 
225
    contexts[0]['results'] = '\n'.join(results)
 
226
 
 
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',
 
232
                                              out_file=out_file,
 
233
                                              params=params)
 
234
 
 
235
    # read mp comment file and display to console
 
236
    if passed:
 
237
        subject_result = "PASS"
 
238
    else:
 
239
        subject_result = "FAIL"
 
240
 
 
241
    subject = '[UOSCI] {}: {} test, {}'.format(subject_result,
 
242
                                               '/'.join(trigger_tests),
 
243
                                               DISPLAY_NAME)
 
244
 
 
245
    logging.debug('Subject: {}'.format(subject))
 
246
    f = open(comment_filename, 'r')
 
247
    comment = f.read()
 
248
    f.close()
 
249
    if not opts.quiet:
 
250
        logging.info('Comment:\n')
 
251
        print '-' * 80
 
252
        print subject
 
253
        print comment
 
254
        print '-' * 80
 
255
 
 
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))
 
262
    else:
 
263
        lp_branch_url = MP_TGT_BR
 
264
        mp_url = MP_TRIGGER
 
265
 
 
266
    if MPC_SILENT:
 
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')
 
271
 
 
272
    if not opts.dry and not opts.no_post and not MPC_SILENT:
 
273
        # post mp comment
 
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')
 
277
        try:
 
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))
 
282
            sys.exit(1)
 
283
        logging.info('Posting comment to {}'.format(mp_url))
 
284
        u.lpl_mp_post_comment(launchpad, mp, subject, comment)
 
285
    else:
 
286
        logging.info('Not posting comment due to options')
 
287
 
 
288
 
 
289
if __name__ == '__main__':
 
290
    print 'Ubuntu OSCI Merge Proposal Commentator'
 
291
    main()