~andrewjbeach/juju-ci-tools/get-juju-dict

587.2.1 by Curtis Hovey
Added an outline of a script that can explain why a branch cannot be merged.
1
#!/usr/bin/python
2
3
from __future__ import print_function
4
5
from argparse import ArgumentParser
587.2.2 by Curtis Hovey
Updated script to return a code and reason why a pull rquest can or cannot be tested.
6
import json
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
7
import sys
587.2.2 by Curtis Hovey
Updated script to return a code and reason why a pull rquest can or cannot be tested.
8
import urllib2
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
9
940.1.22 by Curtis Hovey
Updated per review.
10
from launchpadlib.launchpad import (
11
    Launchpad,
12
    uris,
13
)
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
14
15
16
BUG_STATUSES = [
17
    'Incomplete', 'Confirmed', 'Triaged', 'In Progress', 'Fix Committed']
18
BUG_IMPORTANCES = ['Critical']
19
BUG_TAGS = ['blocker']
587.2.5 by Curtis Hovey
Added test for get_reason.
20
GH_COMMENTS = 'https://api.github.com/repos/juju/juju/issues/{}/comments'
587.2.2 by Curtis Hovey
Updated script to return a code and reason why a pull rquest can or cannot be tested.
21
22
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
23
def get_json(uri):
940.1.16 by Curtis Hovey
Added doc strings.
24
    """Return the json dict response for the uri request."""
660.1.1 by Martin Packman
Bypass caches when checking blocking bugs against launchpad
25
    request = urllib2.Request(uri, headers={
26
        "Cache-Control": "max-age=0, must-revalidate",
27
    })
940.3.1 by Martin Packman
Make the check_blockers script more suitable for human usage
28
    response = urllib2.urlopen(request)
29
    data = response.read()
30
    if response.getcode() == 200 and data:
587.2.2 by Curtis Hovey
Updated script to return a code and reason why a pull rquest can or cannot be tested.
31
        return json.loads(data)
587.2.1 by Curtis Hovey
Added an outline of a script that can explain why a branch cannot be merged.
32
    return None
33
34
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
35
def get_lp(script_name, credentials_file=None):
36
    """Return an LP API client."""
940.1.22 by Curtis Hovey
Updated per review.
37
    lp_args = dict()
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
38
    if credentials_file:
39
        lp_args['credentials_file'] = credentials_file
940.1.22 by Curtis Hovey
Updated per review.
40
    lp = Launchpad.login_with(
41
        script_name, service_root=uris.LPNET_SERVICE_ROOT,
42
        version='devel', **lp_args)
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
43
    return lp
44
45
587.2.3 by Curtis Hovey
Added some basic tests to confirm URLs are created for Lp bug checking.
46
def parse_args(args=None):
47
    parser = ArgumentParser('Check if a branch is blocked from landing')
940.1.4 by Curtis Hovey
Added credentials=None to get_json() and make -c universal.
48
    parser.add_argument(
940.1.6 by Curtis Hovey
Use credentials if Lp data needs to change.
49
        "-c", "--credentials-file", default=None,
940.1.4 by Curtis Hovey
Added credentials=None to get_json() and make -c universal.
50
        help="Launchpad credentials file.")
940.1.1 by Curtis Hovey
Move th check blockers args into a "check" subcommand.
51
    subparsers = parser.add_subparsers(help='sub-command help', dest="command")
52
    check_parser = subparsers.add_parser(
53
        'check', help='Check if merges are blocked for a branch.')
1315.1.5 by Curtis Hovey
lowercase the brach name because github supported uppercase, but lp does not.
54
    check_parser.add_argument(
55
        'branch', default='master', nargs='?', type=str.lower,
56
        help='The branch to merge into.')
940.3.2 by Martin Packman
Merge trunk to resolve conflicts with check_blockers update subcommand
57
    check_parser.add_argument('pull_request', default=None, nargs='?',
953.3.9 by Nate Finch
more code review changes
58
                              help='The pull request to be merged')
1315.1.2 by Curtis Hovey
Added args to block-ci-testing
59
    block_ci_testing_parser = subparsers.add_parser(
60
        'block-ci-testing',
61
        help='Check if ci testing is blocked for the branch.')
62
    block_ci_testing_parser.add_argument(
1315.1.5 by Curtis Hovey
lowercase the brach name because github supported uppercase, but lp does not.
63
        'branch', type=str.lower, help='The branch to merge into.')
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
64
    update_parser = subparsers.add_parser(
940.1.3 by Curtis Hovey
Added update command.
65
        'update', help='Update blocking for a branch that passed CI.')
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
66
    update_parser.add_argument(
67
        '-d', '--dry-run', action='store_true', default=False,
68
        help='Do not make changes.')
1315.1.5 by Curtis Hovey
lowercase the brach name because github supported uppercase, but lp does not.
69
    update_parser.add_argument(
70
        'branch', type=str.lower, help='The branch that passed.')
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
71
    update_parser.add_argument(
940.1.3 by Curtis Hovey
Added update command.
72
        'build', help='The build-revision build number.')
1315.1.4 by Curtis Hovey
Force pull_request to exist in the args.
73
    args = parser.parse_args(args)
74
    if not getattr(args, 'pull_request', None):
75
        args.pull_request = None
76
    return args
587.2.3 by Curtis Hovey
Added some basic tests to confirm URLs are created for Lp bug checking.
77
78
1315.1.6 by Curtis Hovey
Remove redundat call and ambiguous arg.
79
def get_lp_bugs(lp, branch, tags):
940.1.16 by Curtis Hovey
Added doc strings.
80
    """Return a dict of blocker critical bug tasks for the branch."""
1315.1.1 by Curtis Hovey
Pass the tags the caller is intersted in.
81
    if not tags:
82
        raise ValueError('tags must be a list of bug tags')
83
    bug_tags = tags
587.2.2 by Curtis Hovey
Updated script to return a code and reason why a pull rquest can or cannot be tested.
84
    bugs = {}
1565.1.1 by Curtis Hovey
Check the juju project,
85
    if branch.startswith('1.'):
86
        # Historic Juju 1.x
87
        project = lp.projects['juju-core']
88
    else:
89
        # Juju 2.x.
90
        project = lp.projects['juju']
940.1.11 by Curtis Hovey
Pass lp to get_lp_bugs and update_bugs.
91
    if branch == 'master':
940.1.20 by Curtis Hovey
Restore comment.
92
        # Lp implicitly assigns bugs to trunk, which is not a series query.
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
93
        target = project
94
    else:
940.1.11 by Curtis Hovey
Pass lp to get_lp_bugs and update_bugs.
95
        target = project.getSeries(name=branch)
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
96
    if not target:
936.1.1 by Curtis Hovey
Look up supported branches and include incomplete and confirmed as bugs blockers.
97
        return bugs
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
98
    bug_tasks = target.searchTasks(
99
        status=BUG_STATUSES, importance=BUG_IMPORTANCES,
100
        tags=bug_tags, tags_combinator='All')
101
    for bug_task in bug_tasks:
940.1.17 by Curtis Hovey
Add a message about why the task is Fix Released.
102
        # Avoid an extra network call to get the bug report.
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
103
        bug_id = bug_task.self_link.split('/')[-1]
104
        bugs[bug_id] = bug_task
587.2.2 by Curtis Hovey
Updated script to return a code and reason why a pull rquest can or cannot be tested.
105
    return bugs
106
107
108
def get_reason(bugs, args):
940.1.16 by Curtis Hovey
Added doc strings.
109
    """Return the success code and reason why the branch can be merged."""
587.2.2 by Curtis Hovey
Updated script to return a code and reason why a pull rquest can or cannot be tested.
110
    if not bugs:
111
        return 0, 'No blocking bugs'
609 by Curtis Hovey
Do note require __ on both ends of fixes-xxxx because github thinks the underscores are markdown.
112
    fixes_ids = ['fixes-{}'.format(bug_id) for bug_id in bugs]
940.3.1 by Martin Packman
Make the check_blockers script more suitable for human usage
113
    if args.pull_request is None:
114
        return 1, 'Blocked waiting on {}'.format(fixes_ids)
587.2.5 by Curtis Hovey
Added test for get_reason.
115
    uri = GH_COMMENTS.format(args.pull_request)
587.2.2 by Curtis Hovey
Updated script to return a code and reason why a pull rquest can or cannot be tested.
116
    comments = get_json(uri)
940.3.1 by Martin Packman
Make the check_blockers script more suitable for human usage
117
    if comments is not None:
587.2.2 by Curtis Hovey
Updated script to return a code and reason why a pull rquest can or cannot be tested.
118
        for comment in comments:
595 by Curtis Hovey
Ignore comments by jujubot because its explanation about what need fixing
119
            user = comment['user']
606 by Curtis Hovey
Reject comments with Juju bot in them because that implies a reply or someone
120
            if user['login'] == 'jujubot' or 'Juju bot' in comment['body']:
595 by Curtis Hovey
Ignore comments by jujubot because its explanation about what need fixing
121
                continue
607 by Curtis Hovey
Added __JFDI__ to allow engineers to force a merge.
122
            if '__JFDI__' in comment['body']:
123
                return 0, 'Engineer says JFDI'
587.2.2 by Curtis Hovey
Updated script to return a code and reason why a pull rquest can or cannot be tested.
124
            for fid in fixes_ids:
125
                if fid in comment['body']:
126
                    return 0, 'Matches {}'.format(fid)
127
        else:
128
            return 1, 'Does not match {}'.format(fixes_ids)
129
    return 1, 'Could not get {} comments from github'.format(args.pull_request)
587.2.1 by Curtis Hovey
Added an outline of a script that can explain why a branch cannot be merged.
130
131
940.1.17 by Curtis Hovey
Add a message about why the task is Fix Released.
132
def update_bugs(bugs, branch, build, dry_run=False):
940.1.21 by Curtis Hovey
Fix status name.
133
    """Update the critical blocker+ci bugs for the branch to Fix Released."""
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
134
    changes = []
940.1.13 by Curtis Hovey
Added tests for update_bugs.
135
    for bug_id, bug_task in bugs.items():
1565.1.2 by Curtis Hovey
Do not close automatically close intermittent-bugs.
136
        if 'intermittent-failure' in bug_task.bug.tags:
137
            changes.append('Skipping intermittent-failure %s' % bug_task.title)
138
            continue
940.1.18 by Curtis Hovey
Revise text formatting based on the bugtask.title format.
139
        changes.append('Updated %s' % bug_task.title)
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
140
        bug_task.status = 'Fix Released'
141
        if not dry_run:
142
            bug_task.lp_save()
940.1.17 by Curtis Hovey
Add a message about why the task is Fix Released.
143
            subject = 'Fix Released in juju-core %s' % branch
144
            content = (
145
                'Juju-CI verified that this issue is %s:\n'
146
                '    http://reports.vapour.ws/releases/%s' % (subject, build))
147
            bug_task.bug.newMessage(subject=subject, content=content)
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
148
    changes = '\n'.join(changes)
149
    return 0, changes
940.1.9 by Curtis Hovey
Added test for check_blockers.py main call chain.
150
151
152
def main(argv):
153
    args = parse_args(argv)
940.1.11 by Curtis Hovey
Pass lp to get_lp_bugs and update_bugs.
154
    lp = get_lp('check_blockers', credentials_file=args.credentials_file)
940.1.2 by Curtis Hovey
Op name is "check".
155
    if args.command == 'check':
1332 by Curtis Hovey
Fix check_blockers which was passing tags=.
156
        bugs = get_lp_bugs(lp, args.branch, ['blocker'])
940.1.1 by Curtis Hovey
Move th check blockers args into a "check" subcommand.
157
        code, reason = get_reason(bugs, args)
158
        print(reason)
1315.1.3 by Curtis Hovey
Added main support for block-ci-testing.
159
    if args.command == 'block-ci-testing':
1332 by Curtis Hovey
Fix check_blockers which was passing tags=.
160
        bugs = get_lp_bugs(lp, args.branch, ['block-ci-testing'])
1315.1.3 by Curtis Hovey
Added main support for block-ci-testing.
161
        code, reason = get_reason(bugs, args)
162
        print(reason)
940.1.4 by Curtis Hovey
Added credentials=None to get_json() and make -c universal.
163
    elif args.command == 'update':
1332 by Curtis Hovey
Fix check_blockers which was passing tags=.
164
        bugs = get_lp_bugs(lp, args.branch, ['blocker', 'ci'])
940.1.17 by Curtis Hovey
Add a message about why the task is Fix Released.
165
        code, changes = update_bugs(
166
            bugs, args.branch, args.build, dry_run=args.dry_run)
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
167
        print(changes)
587.2.1 by Curtis Hovey
Added an outline of a script that can explain why a branch cannot be merged.
168
    return code
169
170
171
if __name__ == '__main__':
940.1.9 by Curtis Hovey
Added test for check_blockers.py main call chain.
172
    sys.exit(main(sys.argv[1:]))