~1chb1n/ubuntu-openstack-ci/mptest0

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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#!/usr/bin/env python
'''
Scan branches for merge proposals, create properties files for
pickup by jenkins, triggering jobs.
'''

import optparse
import logging
import yaml
import os
import sys
import tempfile
from glob import glob

sys.path.insert(1, os.path.join(sys.path[0], '..'))
from common import osci_utils as u  # noqa

USAGE = '''Usage: %prog [options]

Default behavior:
  Constructs merge proposal message by examining WORKSPACE files.  Posts
  comment on the merge proposal.

For example:
    %prog -0d
  Dry run with debug output.  Displays comment but does not save or post it.

    %prog -Xd
  Dry run with debug output.  Creates comment file but does not post it.

    %prog -q
  Less noisy output.  Create message, comment file, and post it.
'''


try:
    MPC_SILENT = os.environ['MPC_SILENT']
except KeyError:
    MPC_SILENT = False

try:
    BASE_NAME = os.environ['BASE_NAME']
    DISPLAY_NAME = os.environ['DISPLAY_NAME']
    MP_TRIGGER = os.environ['MP_TRIGGER']
    MP_SRC_BR = os.environ['MP_SRC_BR']
    MP_TGT_BR = os.environ['MP_TGT_BR']
    JENKINS_URL = os.environ['JENKINS_URL']
    BUILD_DISPLAY_NAME = os.environ['BUILD_DISPLAY_NAME']
    BUILD_URL = os.environ['BUILD_URL']
    BUILD_NUMBER = os.environ['BUILD_NUMBER']
    JOB_NAME = os.environ['JOB_NAME']
    BRANCH = os.environ['BRANCH']
except KeyError:
    BASE_NAME = 'manual-base-name'
    DISPLAY_NAME = 'manual-disp-name'
    MP_TRIGGER = 'manual-trigger'
    MP_SRC_BR = 'manual-mp-src-br'
    MP_TGT_BR = 'manual-mp-tgt-br'
    JENKINS_URL = 'manual-jnk-url'
    BUILD_DISPLAY_NAME = 'manual-bld-disp-name'
    BUILD_URL = 'manual-url'
    BUILD_NUMBER = '000'
    JOB_NAME = 'manual-job'
    BRANCH = 'manual-branch'


def create_mp_comment_file(contexts, source, out_file, params):
    '''Create MP comment file, return comment filename
    '''
    (opts, args) = params

    if opts.dry:
        out_file = tempfile.mktemp()
        logging.info('Dry run, rendering to {} instead'.format(out_file))

    templates_dir = 'populate'
    for idx, context in enumerate(contexts):
        target = os.path.join(u.WORKSPACE, out_file.format(idx))
        logging.info('Rendering file: {}'.format(target))
        u.render(source, target, context, templates_dir)
    return target


def main():
    '''Define and handle command line parameters
    '''
    # Define command line options
    parser = optparse.OptionParser(USAGE)
    parser.add_option("-d", "--debug",
                      help="Enable debug logging",
                      dest="debug", action="store_true", default=False)
    parser.add_option('-0', '--dry-run',
                      help='Construct and display the message, but do not '
                           'create comment file and do not post comment.',
                      dest='dry', action='store_true', default=False)
    parser.add_option('-X', '--no-post',
                      help='Do not post comment.',
                      dest='no_post', action='store_true', default=False)
    parser.add_option('-q', '--quiet',
                      help='Do not display the message to console.',
                      dest='quiet', action='store_true', default=False)

    params = parser.parse_args()
    (opts, args) = params

    # Handle parameters, inform user
    if opts.debug:
        logging.basicConfig(level=logging.DEBUG)
        logging.info('Logging level set to DEBUG!')
        logging.debug('parse opts: \n{}'.format(
            yaml.dump(vars(opts), default_flow_style=False)))
        logging.debug('parse args: {}'.format(args))
    else:
        logging.basicConfig(level=logging.INFO)

    logging.info('Workspace dir: {}'.format(u.WORKSPACE))

    conf_map = u.read_yaml(u.MAP_FILE)

    # gather info and results for comment
    logging.info('Gathering env var items')
    contexts = []
    contexts.append({
        'BASE_NAME': BASE_NAME,
        'DISPLAY_NAME': DISPLAY_NAME,
        'MP_TRIGGER': MP_TRIGGER,
        'MP_SRC_BR': MP_SRC_BR,
        'JENKINS_URL': JENKINS_URL,
        'BUILD_DISPLAY_NAME': BUILD_DISPLAY_NAME,
        'BUILD_URL': BUILD_URL,
        'JOB_NAME': JOB_NAME
    })

    # construct results blurb by examining artifacts
    logging.info('Constructing results blurb')
    summary = []
    results = []
    pbs = {}
    something_ran = False
    trigger_tests = []
    for trigger_test, tt_attrs in conf_map['mp-trigger-tests'].iteritems():
        # look for try touchfiles
        try_files = []
        logging.debug('Checking for {} try files'.format(trigger_test))
        for try_mask in tt_attrs['try-mask']:
            these_try_files = glob(os.path.join(u.WORKSPACE, try_mask))
            try_files.extend(these_try_files)
        if len(try_files) > 0:
            something_ran = True
            trigger_tests.append(trigger_test)
            logging.info('Found {} try file(s)'.format(trigger_test))
        else:
            # did not run
            logging.info('No {} try file(s) found'.format(trigger_test))
            continue

        # look for fail touchfiles
        logging.debug('Checking for {} fyi files'.format(trigger_test))
        passed = True
        for fail_mask in tt_attrs['fail-mask']:
            logging.debug('Checking fail mask: {}'.format(fail_mask))
            fyis = glob(os.path.join(u.WORKSPACE, fail_mask))
            if len(fyis) > 0:
                passed = False
                logging.debug('Found {} fyi file(s)'.format(trigger_test))
                # handle fyi files
                for fyi in fyis:
                    _line = '    {} FAIL: {}'.format(
                        trigger_test.upper(),
                        os.path.split(fyi)[1][8:].replace('.', ' '))
                    logging.debug(_line)
                    summary.append(_line)
            else:
                logging.debug('No "fyi" files found to '
                              'include for mask: {}'.format(fail_mask))

        # ran and didn't fail
        if passed:
            _line = ('    {} OK: passed'.format(trigger_test.upper()))
            summary.append(_line)

        # look for result files
        res_files = []
        this_result = []
        logging.debug('Checking result mask for {}'.format(trigger_test))
        for res_mask in tt_attrs['result-mask']:
            these_res_files = glob(os.path.join(u.WORKSPACE, res_mask))
            res_files.extend(these_res_files)
        if len(res_files) > 0:
            logging.info('Found {} results file(s)'.format(trigger_test))
            # handle results files
            for res_file in res_files:
                pbs[res_file] = u.pb_it(res_file)
                logging.debug('pastebin for {}: {}'.format(res_file,
                                                           pbs[res_file]))
                llimit = conf_map['mp-conf']['line-limit']
                _line = ('\n{} Results (max last {} lines):'.format(
                    trigger_test.upper(), llimit))
                this_result.append(_line)
                with open(res_file) as _file:
                    res_lines = [f_line.decode('ascii', 'ignore').rstrip()
                                 for f_line in _file][-llimit:]
                _line = ('\nFull {} test output: {}'.format(trigger_test,
                                                            pbs[res_file]))
                res_lines.append(_line)
                this_result.extend(res_lines)
        else:
            _line = ('\n{} Results not found.'.format(trigger_test.upper()))
            results.append(_line)
            logging.info('No {} results files found to '
                         'include'.format(trigger_test))

        if not passed:
            results.extend(this_result)

    logging.debug('pbs: {}'.format(pbs))

    if not something_ran:
        logging.error('It appears that nothing ran here.')
        sys.exit(1)

    # add summary and results to context for template
    contexts[0]['summary'] = '\n'.join(summary)
#    if not passed:
    contexts[0]['results'] = '\n'.join(results)

    # create mp comment file
    logging.info('Creating mp-comment file')
    out_file = 'mp-comment.{}'.format(BUILD_NUMBER)
    comment_filename = create_mp_comment_file(contexts,
                                              source='mp-comment.tmpl',
                                              out_file=out_file,
                                              params=params)

    # read mp comment file and display to console
    if passed:
        subject_result = "PASS"
    else:
        subject_result = "FAIL"

    subject = '[UOSCI] {}: {} test, {}'.format(subject_result,
                                               '/'.join(trigger_tests),
                                               DISPLAY_NAME)

    logging.debug('Subject: {}'.format(subject))
    f = open(comment_filename, 'r')
    comment = f.read()
    f.close()
    if not opts.quiet:
        logging.info('Comment:\n')
        print '-' * 80
        print subject
        print comment
        print '-' * 80

    # check for sandbox override (a noise avoider during dev)
    if conf_map['mp-conf']['mp-comment-sandbox']:
        lp_branch_url = conf_map['mp-conf']['sandbox-lp-branch-url']
        mp_url = conf_map['mp-conf']['sandbox-mp-url']
        logging.info('Sandbox is ENABLED: comments, if '
                     'enabled, will hit {} instead'.format(mp_url))
    else:
        lp_branch_url = MP_TGT_BR
        mp_url = MP_TRIGGER

    if MPC_SILENT:
        # useful for setting environment variable in a jenkins staging
        # area to prevent mp comment posting globally
        logging.info('MPC_SILENT env var override!  No comment '
                     'will actually be posted')

    if not opts.dry and not opts.no_post and not MPC_SILENT:
        # post mp comment
        logging.debug('Connecting to LP via API')
        launchpad = u.lpl_connect(anonymous=False)
        logging.debug('Looking up MP object from LP API')
        try:
            mp = u.lpl_get_mp(launchpad, mp_url, lp_branch_url)
        except (AttributeError, TypeError):
            logging.error('Merge proposal not found for '
                          '{} on {}'.format(mp_url, lp_branch_url))
            sys.exit(1)
        logging.info('Posting comment to {}'.format(mp_url))
        u.lpl_mp_post_comment(launchpad, mp, subject, comment)
    else:
        logging.info('Not posting comment due to options')


if __name__ == '__main__':
    print 'Ubuntu OSCI Merge Proposal Commentator'
    main()