~sinzui/juju-ci-tools/cloudsigma-lib

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.')
940.3.2 by Martin Packman
Merge trunk to resolve conflicts with check_blockers update subcommand
54
    check_parser.add_argument('branch', default='master', nargs='?',
953.3.9 by Nate Finch
more code review changes
55
                              help='The branch to merge into.')
940.3.2 by Martin Packman
Merge trunk to resolve conflicts with check_blockers update subcommand
56
    check_parser.add_argument('pull_request', default=None, nargs='?',
953.3.9 by Nate Finch
more code review changes
57
                              help='The pull request to be merged')
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
58
    update_parser = subparsers.add_parser(
940.1.3 by Curtis Hovey
Added update command.
59
        'update', help='Update blocking for a branch that passed CI.')
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
60
    update_parser.add_argument(
61
        '-d', '--dry-run', action='store_true', default=False,
62
        help='Do not make changes.')
63
    update_parser.add_argument('branch', help='The branch that passed.')
64
    update_parser.add_argument(
940.1.3 by Curtis Hovey
Added update command.
65
        'build', help='The build-revision build number.')
587.2.3 by Curtis Hovey
Added some basic tests to confirm URLs are created for Lp bug checking.
66
    return parser.parse_args(args)
67
68
940.1.11 by Curtis Hovey
Pass lp to get_lp_bugs and update_bugs.
69
def get_lp_bugs(lp, branch, with_ci=False):
940.1.16 by Curtis Hovey
Added doc strings.
70
    """Return a dict of blocker critical bug tasks for the branch."""
587.2.2 by Curtis Hovey
Updated script to return a code and reason why a pull rquest can or cannot be tested.
71
    bugs = {}
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
72
    project = lp.projects['juju-core']
940.1.11 by Curtis Hovey
Pass lp to get_lp_bugs and update_bugs.
73
    if branch == 'master':
940.1.20 by Curtis Hovey
Restore comment.
74
        # Lp implicitly assigns bugs to trunk, which is not a series query.
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
75
        target = project
76
    else:
940.1.11 by Curtis Hovey
Pass lp to get_lp_bugs and update_bugs.
77
        target = project.getSeries(name=branch)
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
78
    if not target:
936.1.1 by Curtis Hovey
Look up supported branches and include incomplete and confirmed as bugs blockers.
79
        return bugs
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
80
    if with_ci:
81
        bug_tags = BUG_TAGS + ['ci']
587.2.2 by Curtis Hovey
Updated script to return a code and reason why a pull rquest can or cannot be tested.
82
    else:
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
83
        bug_tags = BUG_TAGS
84
    bug_tasks = target.searchTasks(
85
        status=BUG_STATUSES, importance=BUG_IMPORTANCES,
86
        tags=bug_tags, tags_combinator='All')
87
    for bug_task in bug_tasks:
940.1.17 by Curtis Hovey
Add a message about why the task is Fix Released.
88
        # Avoid an extra network call to get the bug report.
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
89
        bug_id = bug_task.self_link.split('/')[-1]
90
        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.
91
    return bugs
92
93
94
def get_reason(bugs, args):
940.1.16 by Curtis Hovey
Added doc strings.
95
    """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.
96
    if not bugs:
97
        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.
98
    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
99
    if args.pull_request is None:
100
        return 1, 'Blocked waiting on {}'.format(fixes_ids)
587.2.5 by Curtis Hovey
Added test for get_reason.
101
    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.
102
    comments = get_json(uri)
940.3.1 by Martin Packman
Make the check_blockers script more suitable for human usage
103
    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.
104
        for comment in comments:
595 by Curtis Hovey
Ignore comments by jujubot because its explanation about what need fixing
105
            user = comment['user']
606 by Curtis Hovey
Reject comments with Juju bot in them because that implies a reply or someone
106
            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
107
                continue
607 by Curtis Hovey
Added __JFDI__ to allow engineers to force a merge.
108
            if '__JFDI__' in comment['body']:
109
                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.
110
            for fid in fixes_ids:
111
                if fid in comment['body']:
112
                    return 0, 'Matches {}'.format(fid)
113
        else:
114
            return 1, 'Does not match {}'.format(fixes_ids)
115
    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.
116
117
940.1.17 by Curtis Hovey
Add a message about why the task is Fix Released.
118
def update_bugs(bugs, branch, build, dry_run=False):
940.1.21 by Curtis Hovey
Fix status name.
119
    """Update the critical blocker+ci bugs for the branch to Fix Released."""
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
120
    changes = []
940.1.13 by Curtis Hovey
Added tests for update_bugs.
121
    for bug_id, bug_task in bugs.items():
940.1.18 by Curtis Hovey
Revise text formatting based on the bugtask.title format.
122
        changes.append('Updated %s' % bug_task.title)
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
123
        bug_task.status = 'Fix Released'
124
        if not dry_run:
125
            bug_task.lp_save()
940.1.17 by Curtis Hovey
Add a message about why the task is Fix Released.
126
            subject = 'Fix Released in juju-core %s' % branch
127
            content = (
128
                'Juju-CI verified that this issue is %s:\n'
129
                '    http://reports.vapour.ws/releases/%s' % (subject, build))
130
            bug_task.bug.newMessage(subject=subject, content=content)
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
131
    changes = '\n'.join(changes)
132
    return 0, changes
940.1.9 by Curtis Hovey
Added test for check_blockers.py main call chain.
133
134
135
def main(argv):
136
    args = parse_args(argv)
940.1.11 by Curtis Hovey
Pass lp to get_lp_bugs and update_bugs.
137
    lp = get_lp('check_blockers', credentials_file=args.credentials_file)
940.1.2 by Curtis Hovey
Op name is "check".
138
    if args.command == 'check':
940.1.11 by Curtis Hovey
Pass lp to get_lp_bugs and update_bugs.
139
        bugs = get_lp_bugs(lp, args.branch, with_ci=False)
940.1.1 by Curtis Hovey
Move th check blockers args into a "check" subcommand.
140
        code, reason = get_reason(bugs, args)
141
        print(reason)
940.1.4 by Curtis Hovey
Added credentials=None to get_json() and make -c universal.
142
    elif args.command == 'update':
940.1.11 by Curtis Hovey
Pass lp to get_lp_bugs and update_bugs.
143
        bugs = get_lp_bugs(lp, args.branch, with_ci=True)
940.1.17 by Curtis Hovey
Add a message about why the task is Fix Released.
144
        code, changes = update_bugs(
145
            bugs, args.branch, args.build, dry_run=args.dry_run)
940.1.10 by Curtis Hovey
Switch to launchpadlib (very invasive)
146
        print(changes)
587.2.1 by Curtis Hovey
Added an outline of a script that can explain why a branch cannot be merged.
147
    return code
148
149
150
if __name__ == '__main__':
940.1.9 by Curtis Hovey
Added test for check_blockers.py main call chain.
151
    sys.exit(main(sys.argv[1:]))