~mrazik/jenkins-launchpad-plugin/rebuild_link

1 by Martin Mrazik
initial commit
1
from launchpadlib.launchpad import Launchpad
2
from launchpadlib.credentials import UnencryptedFileCredentialStore
3
from credentials import AuthorizeRequestTokenWithConsole
12.4.1 by Allan LeSage
Minor organizational suggestions.
4
from mergeproposalreview import MergeProposalReview
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
5
from settings import (ALLOWED_USERS, LAUNCHPAD_REVIEW_TYPE, JENKINS_URL,
6
                      JENKINS_PASSWORD, JENKINS_USER, LAUNCHPAD_LOGIN,
7
                      PUBLIC_JENKINS_URL, URLS_TO_HIDE, CREDENTIAL_STORE_PATH,
8
                      LP_ENV, LP_APP, logger)
27.2.1 by Martin Mrazik
Log unathorized access instead of printing python stack traces
9
from lazr.restfulclient.errors import Unauthorized
6 by Martin Mrazik
better error handling when starting a job fails + getting rid of urllib
10
import jenkins
30.1.4 by Martin Mrazik
removed unnecessary import and improved the tests a bit
11
from xml.dom.minidom import parseString
1 by Martin Mrazik
initial commit
12
import re
31.1.3 by Martin Mrazik
correct imports
13
from jsonjenkins import JSONJenkins
63 by Martin Mrazik
adding a rebuild link
14
from textwrap import dedent
12.7.1 by Allan LeSage
Per sergiusens' advice, correct getMergeProposalAttributes missing changes and eliminate hard-coded lognames.
15
70 by Martin Mrazik
ppe8 fix
16
1 by Martin Mrazik
initial commit
17
class LaunchpadAgent():
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
18
    _launchpad = None
1 by Martin Mrazik
initial commit
19
20
    def __init__(self):
21
        self._launchpad = self.login()
22
22.2.5 by Martin Mrazik
correct calling of launchpadAgent functions
23
    def get_launchpad(self):
24
        return self._launchpad
25
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
26
    def change_mp_status(self, mp, status, message, subject,
12.2.1 by Martin Mrazik
Getting rid of one jenkins message during the autolanding process.
27
                         revision, vote=None):
12.3.2 by Martin Mrazik
MP locking now implemented outside of launchpad to get rid of excess votes
28
        mp.setStatus(status=status, revid=revision)
12.2.1 by Martin Mrazik
Getting rid of one jenkins message during the autolanding process.
29
        if vote:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
30
            mp.createComment(review_type=LAUNCHPAD_REVIEW_TYPE,
31
                             subject=subject, content=message, vote=vote)
12.3.7 by Martin Mrazik
Refactoring into class + fixed unit tests
32
            review = MergeProposalReview(mp)
33
            review.unlock()
12.2.1 by Martin Mrazik
Getting rid of one jenkins message during the autolanding process.
34
        else:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
35
            mp.createComment(review_type=LAUNCHPAD_REVIEW_TYPE,
12.2.1 by Martin Mrazik
Getting rid of one jenkins message during the autolanding process.
36
                             subject=subject, content=message)
37
1 by Martin Mrazik
initial commit
38
    def get_merge_proposal(self, branch_name, merge_proposal_link):
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
39
        lp_branch = branch_name.replace('https://code.launchpad.net/', 'lp:')
40
        lp_branch = lp_branch.replace('https://code.staging.launchpad.net/',
41
                            'lp://staging/')
42
        logger.debug('fetching branch: ' + lp_branch)
1 by Martin Mrazik
initial commit
43
        branch = self._launchpad.branches.getByUrl(url=lp_branch)
44
        if not branch:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
45
            logger.debug('branch "' + branch_name + '" doesn\'t exist')
1 by Martin Mrazik
initial commit
46
            return None
47
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
48
        logger.debug('mp_link: ' + merge_proposal_link)
49
1 by Martin Mrazik
initial commit
50
        for mp in branch.landing_targets:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
51
            logger.debug('mp.web_link: ' + mp.web_link)
1 by Martin Mrazik
initial commit
52
            if mp.web_link == merge_proposal_link:
53
                return mp
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
54
1 by Martin Mrazik
initial commit
55
        return None
56
57
    def get_launchpad_user(self):
58
        return self._launchpad.people(LAUNCHPAD_LOGIN)
59
60
    @staticmethod
61
    def get_branch_from_mp(merge_proposal):
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
62
        m = re.search('(.*)\+merge/[0-9]+$', merge_proposal)
1 by Martin Mrazik
initial commit
63
        if m:
64
            return m.group(1)
65
        return None
66
30.1.1 by Martin Mrazik
If the project is published then change the URL to public jenkins
67
    @staticmethod
68
    def is_job_published(job):
69
        if not job:
70
            logger.debug('is_job_published: no job specified')
71
            return False
72
30.1.2 by Martin Mrazik
specifing the right JENKINS_URL from settings
73
        jenkins_server = jenkins.Jenkins(JENKINS_URL,
30.1.1 by Martin Mrazik
If the project is published then change the URL to public jenkins
74
                                         JENKINS_USER, JENKINS_PASSWORD)
75
        try:
76
            config = jenkins_server.get_job_config(job)
77
            dom = parseString(config)
78
            elements = dom.getElementsByTagName(
79
                            'hudson.plugins.build__publisher.BuildPublisher')
30.1.5 by Martin Mrazik
Fixing some issues revelead by code review
80
            # if the above element was found then the job is configured to be
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
81
            # published. Return true if the length of the array is > 0 and
30.1.5 by Martin Mrazik
Fixing some issues revelead by code review
82
            # False otherwise
83
            return elements.length > 0
30.1.1 by Martin Mrazik
If the project is published then change the URL to public jenkins
84
        except:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
85
            logger.info('Unable to fetch or parse job configuration for: ' +
30.1.5 by Martin Mrazik
Fixing some issues revelead by code review
86
                         job)
30.1.1 by Martin Mrazik
If the project is published then change the URL to public jenkins
87
            return False
30.1.5 by Martin Mrazik
Fixing some issues revelead by code review
88
1 by Martin Mrazik
initial commit
89
    @staticmethod
90
    def hide_jenkins_url(url):
30.1.1 by Martin Mrazik
If the project is published then change the URL to public jenkins
91
        job = None
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
92
        # Match the jenkins JOB_URL and extract the IP/hostname
30.1.5 by Martin Mrazik
Fixing some issues revelead by code review
93
        # (^http[s]?://.*) and the name of jenkins job.
94
        # The following format is expected
95
        # http://10.0.0.1:8080/job/my-jenkins-job/7/
96
        m = re.match('^(http[s]?://.*)/job/([^/]*)/.*$', url)
30.1.1 by Martin Mrazik
If the project is published then change the URL to public jenkins
97
        if m:
98
            jenkins_ip = m.group(1)
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
99
            job = m.group(2)
30.1.5 by Martin Mrazik
Fixing some issues revelead by code review
100
        # if there was a match in the regexp above then both
101
        # job and jenkins_ip must be set. It is enough to check just for job
30.1.1 by Martin Mrazik
If the project is published then change the URL to public jenkins
102
        if job and LaunchpadAgent.is_job_published(job):
103
            return url.replace(jenkins_ip, PUBLIC_JENKINS_URL)
104
1 by Martin Mrazik
initial commit
105
        for ip, replacement in URLS_TO_HIDE:
106
            url = url.replace(ip, replacement)
107
        return url
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
108
1 by Martin Mrazik
initial commit
109
    @staticmethod
110
    def login():
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
111
        credential_store = UnencryptedFileCredentialStore(
112
                                                         CREDENTIAL_STORE_PATH)
113
        authorization_engine = AuthorizeRequestTokenWithConsole(LP_ENV, LP_APP)
114
        return Launchpad.login_with(LP_APP, LP_ENV,
115
                                    credential_store=credential_store,
116
                                    authorization_engine=authorization_engine)
117
1 by Martin Mrazik
initial commit
118
50.2.1 by Martin Mrazik
Check for commit message before starting the build
119
class UpdateMergeProposalException(Exception):
120
    pass
121
122
123
def build_state(args):
124
    """Return a tuple containing merge proposal state, message, and
125
    subject."""
126
127
    if 'merge_failed' in args and args['merge_failed']:
128
        return ('Needs review',
129
                "FAILED: Autolanding.\n" +
130
                "Merging failed. More details in the following jenkins job:\n",
131
                "Re: [Merge] autolanding failed (merge failed)")
132
    elif 'push_failed' in args and args['push_failed']:
133
        return ('Needs review',
134
                "FAILED: Autolanding.\n" +
135
                "Pushing failed. More details in the following jenkins job:\n",
136
                "Re: [Merge] autolanding failed (push failed)")
137
    elif 'landing_failed' in args and args['landing_failed']:
138
        return ('Needs review',
139
                "FAILED: Autolanding.\n" +
140
                "More details in the following jenkins job:\n",
141
                "Re: [Merge] autolanding failed")
142
    elif 'merged' in args and args['merged']:
143
        return ('Merged',
144
                "PASSED: Autolanding.\nBranch merged.",
145
                "Re: [Merge] branch merged")
146
    elif 'commit_message_empty' in args and args['commit_message_empty']:
147
        return ('Needs review',
148
                "FAILED: Autolanding.\nNo commit message was specified.\n",
149
                "Re: [Merge] autolanding failed",)
150
    elif 'unapproved_changes' in args and args['unapproved_changes']:
151
        return ('Needs review',
152
                "FAILED: Autolanding.\n" +
153
                "Unapproved changes made after approval.\n",
154
                "Re: [Merge] autolanding failed",)
58.1.1 by Martin Mrazik
one more try to fix the ghost-revision issue
155
    elif 'invalid_revid' in args and args['invalid_revid']:
156
        return ('Needs review',
157
                "FAILED: Autolanding.\n" +
58.1.2 by Martin Mrazik
added a hint about permissions
158
                "Approved revid is not set in launchpad " +
159
                "(maybe a permission problem?).\n",
58.1.1 by Martin Mrazik
one more try to fix the ghost-revision issue
160
                "Re: [Merge] autolanding failed",)
50.2.1 by Martin Mrazik
Check for commit message before starting the build
161
    else:
162
        raise UpdateMergeProposalException("unrecognized parameters")
163
164
27.1.3 by Jenkins User
moving approve/disapprove/needs fixing to a class constant and fixing all the occurences
165
class LaunchpadVote():
166
    APPROVE = 'Approve'
167
    DISAPPROVE = 'Disapprove'
168
    NEEDS_FIXING = 'Needs Fixing'
1 by Martin Mrazik
initial commit
169
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
170
1 by Martin Mrazik
initial commit
171
class LaunchpadTrigger():
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
172
    _launchpad = None
1 by Martin Mrazik
initial commit
173
    _launchpad_user = None
31.1.2 by Martin Mrazik
Console links for VoteOnMergeProposal as well
174
    _json_jenkins = None
1 by Martin Mrazik
initial commit
175
176
    def __init__(self):
22.2.5 by Martin Mrazik
correct calling of launchpadAgent functions
177
        self._launchpadAgent = LaunchpadAgent()
178
        self._launchpad = self._launchpadAgent.get_launchpad()
3 by Martin Mrazik
fixed wrong indenting
179
        self._launchpad_user = self._launchpad.people(LAUNCHPAD_LOGIN)
1 by Martin Mrazik
initial commit
180
181
    def get_launchpad_user(self):
182
        return self._launchpad_user
183
22.2.1 by Martin Mrazik
first attempt to migrate autoland.sh to autoland.py
184
    def get_branch_from_mp(self, merge_proposal):
22.2.5 by Martin Mrazik
correct calling of launchpadAgent functions
185
        return self._launchpadAgent.get_branch_from_mp(merge_proposal)
22.2.1 by Martin Mrazik
first attempt to migrate autoland.sh to autoland.py
186
187
    def hide_jenkins_url(self, url):
22.2.5 by Martin Mrazik
correct calling of launchpadAgent functions
188
        return self._launchpadAgent.hide_jenkins_url(url)
22.2.1 by Martin Mrazik
first attempt to migrate autoland.sh to autoland.py
189
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
190
    def change_mp_status(self, mp, status, message, subject,
22.2.1 by Martin Mrazik
first attempt to migrate autoland.sh to autoland.py
191
                         revision, vote=None):
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
192
        return self._launchpadAgent.change_mp_status(mp, status, message,
193
                                            subject, revision, vote)
22.2.1 by Martin Mrazik
first attempt to migrate autoland.sh to autoland.py
194
22.2.6 by Martin Mrazik
adding get_merge_proposal
195
    def get_merge_proposal(self, branch_name, merge_proposal_link):
196
        return self._launchpadAgent.get_merge_proposal(branch_name,
197
                                            merge_proposal_link)
198
31.1.2 by Martin Mrazik
Console links for VoteOnMergeProposal as well
199
    def approve_mp(self, mp, revision, build_url):
31.1.9 by Martin Mrazik
Fixed few whitespace issues
200
        state = 'PASSED: Continuous integration, rev:' + str(revision)
31.1.2 by Martin Mrazik
Console links for VoteOnMergeProposal as well
201
        logger.debug(state)
68 by Martin Mrazik
not adding the rebuild link when doing autolanding (approve/disapprove_mp methods are being called when autolanding)
202
        content = self.format_message_for_mp_update(build_url,
69 by Martin Mrazik
correct handling of autolanding
203
                                                    state + "\n")
31.1.2 by Martin Mrazik
Console links for VoteOnMergeProposal as well
204
        mp.createComment(review_type=LAUNCHPAD_REVIEW_TYPE,
205
                         vote=LaunchpadVote.APPROVE, subject=state,
67 by Martin Mrazik
fixed a forgotten custom formatting + fixed the tests
206
                         content=content)
31.1.2 by Martin Mrazik
Console links for VoteOnMergeProposal as well
207
        review = MergeProposalReview(mp)
208
        review.unlock()
209
210
    def disapprove_mp(self, mp, revision, build_url):
31.1.9 by Martin Mrazik
Fixed few whitespace issues
211
        state = 'FAILED: Continuous integration, rev:' + str(revision)
31.1.2 by Martin Mrazik
Console links for VoteOnMergeProposal as well
212
        logger.debug(state)
68 by Martin Mrazik
not adding the rebuild link when doing autolanding (approve/disapprove_mp methods are being called when autolanding)
213
        content = self.format_message_for_mp_update(build_url,
69 by Martin Mrazik
correct handling of autolanding
214
                                                    state + "\n")
31.1.2 by Martin Mrazik
Console links for VoteOnMergeProposal as well
215
        mp.createComment(review_type=LAUNCHPAD_REVIEW_TYPE,
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
216
                         vote=LaunchpadVote.NEEDS_FIXING, subject=state,
67 by Martin Mrazik
fixed a forgotten custom formatting + fixed the tests
217
                         content=content)
31.1.2 by Martin Mrazik
Console links for VoteOnMergeProposal as well
218
        review = MergeProposalReview(mp)
219
        review.unlock()
220
31.1.11 by Martin Mrazik
renamed get_json_jenkins to _get_json_jenkins
221
    def _get_json_jenkins(self, url):
31.1.2 by Martin Mrazik
Console links for VoteOnMergeProposal as well
222
        if not self._json_jenkins:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
223
            self._json_jenkins = JSONJenkins(url, JENKINS_USER,
31.1.2 by Martin Mrazik
Console links for VoteOnMergeProposal as well
224
                                             JENKINS_PASSWORD)
225
        return self._json_jenkins
226
227
    def get_executed_test_runs(self, url):
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
228
        jenkins = self._get_json_jenkins(url)
31.1.2 by Martin Mrazik
Console links for VoteOnMergeProposal as well
229
        job = jenkins.get_json_data(url)
230
        ret = "\nExecuted test runs:"
231
        for run in job['runs']:
31.1.7 by Martin Mrazik
workarounding some jenkins weirdness when there are more runs than reality
232
            if run['number'] == job['number']:
233
                result = jenkins.get_json_data(run['url'])['result']
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
234
                ret = (ret + "\n    " + result + ': ' +
31.1.7 by Martin Mrazik
workarounding some jenkins weirdness when there are more runs than reality
235
                       self.hide_jenkins_url(run['url']) + 'console')
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
236
        return ret
31.1.2 by Martin Mrazik
Console links for VoteOnMergeProposal as well
237
68 by Martin Mrazik
not adding the rebuild link when doing autolanding (approve/disapprove_mp methods are being called when autolanding)
238
    def format_message_for_mp_update(self, build_url, message=None,
239
                                     include_rebuild_link=True):
63 by Martin Mrazik
adding a rebuild link
240
        formatted_message = dedent('''\
64 by Martin Mrazik
removed coverity
241
                {message}{build_url}{executed_test_runs}
68 by Martin Mrazik
not adding the rebuild link when doing autolanding (approve/disapprove_mp methods are being called when autolanding)
242
                ''')
243
        if include_rebuild_link:
244
                formatted_message = formatted_message + dedent('''\
66 by Martin Mrazik
adding a newline for better readability
245
63 by Martin Mrazik
adding a rebuild link
246
                Click here to trigger a rebuild:
247
                {build_url}/rebuild/?
248
                ''')
44.3.1 by Martin Mrazik
refactoring message generation for autolanding/ci so both messages are consistent
249
        if not message:
250
            message = ''
50.2.1 by Martin Mrazik
Check for commit message before starting the build
251
        if not build_url:
252
            build_url_message = ''
253
            executed_test_runs_message = ''
254
        else:
255
            build_url_message = self.hide_jenkins_url(build_url)
256
            executed_test_runs_message = self.get_executed_test_runs(
257
                                                      build_url)
44.3.1 by Martin Mrazik
refactoring message generation for autolanding/ci so both messages are consistent
258
        formatted_message = formatted_message.format(
44.3.3 by Martin Mrazik
fixed comments from MP
259
                message=message,
50.2.1 by Martin Mrazik
Check for commit message before starting the build
260
                build_url=build_url_message,
261
                executed_test_runs=executed_test_runs_message)
44.3.1 by Martin Mrazik
refactoring message generation for autolanding/ci so both messages are consistent
262
        return formatted_message
263
4 by Martin Mrazik
fixed few minor things
264
    def get_latest_review(self, mp):
1 by Martin Mrazik
initial commit
265
        revision = 0
266
        launchpad_user = self.get_launchpad_user()
267
        for comment in mp.all_comments:
268
            if comment.author.name == launchpad_user.name:
269
                if comment.vote_tag == LAUNCHPAD_REVIEW_TYPE:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
270
                    m = re.search(
271
                        '^(PASSED|FAILED): Continuous integration, rev:(\d+)',
272
                        comment.message_body)
1 by Martin Mrazik
initial commit
273
                    if m and (int(m.group(2)) > revision):
274
                        revision = int(m.group(2))
275
        return revision
276
277
    def latest_candidate_validated(self, mp):
4 by Martin Mrazik
fixed few minor things
278
        latest_review = self.get_latest_review(mp)
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
279
        logger.debug('Latest_review is revision: ' + str(latest_review))
1 by Martin Mrazik
initial commit
280
        if latest_review >= mp.source_branch.revision_count:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
281
            logger.debug('Skipping this MP. Current revision: ' +
12.5.1 by Martin Mrazik
replacing prints by logging
282
                           str(mp.source_branch.revision_count))
1 by Martin Mrazik
initial commit
283
            return True
284
        return False
285
286
    def testing_in_progress(self, mp):
12.3.7 by Martin Mrazik
Refactoring into class + fixed unit tests
287
        review = MergeProposalReview(mp)
288
        if review.is_locked():
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
289
            logger.debug('Skipping this MP. It is curently being ' +
12.5.1 by Martin Mrazik
replacing prints by logging
290
                  'tested by Jenkins.')
12.3.2 by Martin Mrazik
MP locking now implemented outside of launchpad to get rid of excess votes
291
            return True
292
        return False
1 by Martin Mrazik
initial commit
293
35.1.1 by Martin Mrazik
Trigger autolanding if the MP is Approved by trusted person
294
    def user_allowed_to_trigger_jobs(self, lp_user):
1 by Martin Mrazik
initial commit
295
        allowed_people = ALLOWED_USERS
35.1.1 by Martin Mrazik
Trigger autolanding if the MP is Approved by trusted person
296
        if lp_user.name in allowed_people:
1 by Martin Mrazik
initial commit
297
            return True
35.1.1 by Martin Mrazik
Trigger autolanding if the MP is Approved by trusted person
298
        for membership in lp_user.memberships_details:
1 by Martin Mrazik
initial commit
299
            if membership.team.name in allowed_people:
300
                return True
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
301
        logger.debug('User "' + lp_user.name +
12.5.1 by Martin Mrazik
replacing prints by logging
302
                      '" not allowed to trigger jobs')
1 by Martin Mrazik
initial commit
303
        return False
304
305
    def start_jenkins_job(self, jenkins_url, jenkins_job, mp):
56.1.1 by Martin Mrazik
jenkins should authenticate when triggering jobs
306
            j = jenkins.Jenkins(jenkins_url, JENKINS_USER, JENKINS_PASSWORD)
6 by Martin Mrazik
better error handling when starting a job fails + getting rid of urllib
307
            jenkins_params = {
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
308
                     'landing_candidate': 'bzr+ssh://' + LAUNCHPAD_LOGIN +
309
                     '@bazaar.launchpad.net/' + mp.source_branch.unique_name,
310
                     'merge_proposal': mp.web_link,
311
                     'candidate_revision': mp.source_branch.revision_count
12.3.2 by Martin Mrazik
MP locking now implemented outside of launchpad to get rid of excess votes
312
                             }
7 by Martin Mrazik
ignore non-buildable jobs
313
314
            job_info = j.get_job_info(jenkins_job)
315
            if not job_info['buildable']:
27.2.7 by Martin Mrazik
Fixed typo (Jobs vs Job) + removed unused variable (error)
316
                logger.debug('Job is not buildable (probably disabled)')
7 by Martin Mrazik
ignore non-buildable jobs
317
                return False
318
27.2.4 by Martin Mrazik
moving the logic to the right place (start_jenkins_job) + proper return value handling
319
            try:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
320
                mp.nominateReviewer(reviewer=self.get_launchpad_user(),
27.2.4 by Martin Mrazik
moving the logic to the right place (start_jenkins_job) + proper return value handling
321
                                    review_type=LAUNCHPAD_REVIEW_TYPE)
27.2.7 by Martin Mrazik
Fixed typo (Jobs vs Job) + removed unused variable (error)
322
            except Unauthorized:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
323
                logger.info('You are not authorized to review this merge ' +
27.2.4 by Martin Mrazik
moving the logic to the right place (start_jenkins_job) + proper return value handling
324
                            'proposal. CI process will now be stopped.')
325
                return False
326
12.3.7 by Martin Mrazik
Refactoring into class + fixed unit tests
327
            review = MergeProposalReview(mp)
328
            review.lock()
6 by Martin Mrazik
better error handling when starting a job fails + getting rid of urllib
329
            try:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
330
                logger.debug('Starting job: ' + str(jenkins_params))
6 by Martin Mrazik
better error handling when starting a job fails + getting rid of urllib
331
                j.build_job(jenkins_job, jenkins_params)
332
            except jenkins.JenkinsException:
12.6.1 by Allan LeSage
Everyone shares a logging config.
333
                logger.debug('Starting a job failed')
6 by Martin Mrazik
better error handling when starting a job fails + getting rid of urllib
334
                message = "ERROR: failed to start a jenkins job"
335
                mp.createComment(review_type=LAUNCHPAD_REVIEW_TYPE,
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
336
                                 vote='Disapprove', subject=message,
6 by Martin Mrazik
better error handling when starting a job fails + getting rid of urllib
337
                                 content=message)
1 by Martin Mrazik
initial commit
338
339
    def trigger_ci_building(self, mps, jenkins_job, jenkins_url):
27.2.5 by Martin Mrazik
propper ret value handling
340
        result = True
1 by Martin Mrazik
initial commit
341
        for mp in mps:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
342
35.1.1 by Martin Mrazik
Trigger autolanding if the MP is Approved by trusted person
343
            if not self.user_allowed_to_trigger_jobs(mp.registrant):
1 by Martin Mrazik
initial commit
344
                continue
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
345
1 by Martin Mrazik
initial commit
346
            if self.testing_in_progress(mp):
347
                continue
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
348
1 by Martin Mrazik
initial commit
349
            if self.latest_candidate_validated(mp):
350
                continue
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
351
27.2.5 by Martin Mrazik
propper ret value handling
352
            ret = self.start_jenkins_job(jenkins_url, jenkins_job, mp)
353
            result = ret and result
354
        return result
1 by Martin Mrazik
initial commit
355
356
    def unapproved_prerequisite_exists(self, mp):
357
        prereq = mp.prerequisite_branch
358
        target_branch = mp.target_branch
359
        if prereq:
360
            #x is a merge-proposal object
361
            merges = [x for x in prereq.landing_targets
362
                      if x.target_branch.web_link == target_branch.web_link and
363
                      x.queue_status != u'Superseded']
364
            if len(merges) == 0:
12.6.1 by Allan LeSage
Everyone shares a logging config.
365
                logger.debug('No proposals of prerequisite branch exists')
1 by Martin Mrazik
initial commit
366
                return True
367
            elif len(merges) > 1:
22.1.1 by Martin Mrazik
Fixed few typos + created tests to avoid similar regressions
368
                logger.debug('Too many proposals exists for prerequisite')
1 by Martin Mrazik
initial commit
369
                return True
370
            elif len(merges) == 1:
371
                if merges[0].queue_status != u'Merged':
12.6.1 by Allan LeSage
Everyone shares a logging config.
372
                    logger.debug('Prerequisite not yet merged')
1 by Martin Mrazik
initial commit
373
                    return True
374
        #TODO
375
        # skip also branches with changes made after approval
376
        return False
377
50.2.1 by Martin Mrazik
Check for commit message before starting the build
378
    def is_commit_message_set(self, mp, jenkins_job, jenkins_url):
379
        jenkins = self._get_json_jenkins(jenkins_url)
50.2.3 by Martin Mrazik
fixed url
380
        job_config = jenkins.get_json_data(jenkins_url + '/job/' +
381
                                           jenkins_job + '/')
50.2.1 by Martin Mrazik
Check for commit message before starting the build
382
        use_description = False
383
        try:
384
            for param in job_config['property'][1]['parameterDefinitions']:
385
                if param['name'] == 'use_description_for_commit' and \
386
                   param['defaultParameterValue']['value']:
387
                        use_description = True
388
                        break
389
        except:
50.2.2 by Martin Mrazik
added tests for is_commit_message_set
390
            logger.debug("Unable to get \"use_description_for_commit\" " +
50.2.1 by Martin Mrazik
Check for commit message before starting the build
391
                    "configuration for %s" % (jenkins_job))
50.2.4 by Martin Mrazik
added some debugging output
392
        logger.debug('Job is configured to fallback to description: %s' %
393
                     (use_description))
50.2.1 by Martin Mrazik
Check for commit message before starting the build
394
        return bool(self.get_commit_message(mp, use_description))
395
1 by Martin Mrazik
initial commit
396
    def trigger_al_building(self, mps, jenkins_job, jenkins_url):
27.2.5 by Martin Mrazik
propper ret value handling
397
        result = True
1 by Martin Mrazik
initial commit
398
        for mp in mps:
44.1.1 by Martin Mrazik
pep8izing
399
            #ignore this merge proposal if both registrant and reviewer
35.1.4 by Martin Mrazik
added logging when registrand and reviewer are both not allowed to run jobs and also changed the if statement to another equivalent form
400
            #are not trusted (aka belong to canonical)
35.1.1 by Martin Mrazik
Trigger autolanding if the MP is Approved by trusted person
401
            #check if mp.reviewer actually exists just to be sure (it should
402
            # as we are autolanding and thus the MP needs to be approved)
403
            if mp.reviewer is None:
404
                continue
35.1.4 by Martin Mrazik
added logging when registrand and reviewer are both not allowed to run jobs and also changed the if statement to another equivalent form
405
            if not (self.user_allowed_to_trigger_jobs(mp.registrant) or
406
                self.user_allowed_to_trigger_jobs(mp.reviewer)):
44.1.1 by Martin Mrazik
pep8izing
407
                logger.debug('Skiping this merge proposal as neither ' +
35.1.4 by Martin Mrazik
added logging when registrand and reviewer are both not allowed to run jobs and also changed the if statement to another equivalent form
408
                             'registrant nor reviewer is allowed to ' +
409
                             'trigger jenkins jobs')
1 by Martin Mrazik
initial commit
410
                continue
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
411
50.2.1 by Martin Mrazik
Check for commit message before starting the build
412
            if not self.is_commit_message_set(mp, jenkins_job, jenkins_url):
50.2.4 by Martin Mrazik
added some debugging output
413
                logger.debug('No commit message was set. Interrupting ' +
414
                             'autolanding for "%s".' % (mp.web_link))
50.2.1 by Martin Mrazik
Check for commit message before starting the build
415
                mp_state, message, subject = build_state(
416
                                               {'commit_message_empty': True})
417
                self.change_mp_status(mp, mp_state, message, subject,
418
                        mp.source_branch.revision_count,
419
                        LaunchpadVote.NEEDS_FIXING)
420
                continue
421
1 by Martin Mrazik
initial commit
422
            if self.testing_in_progress(mp):
423
                continue
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
424
1 by Martin Mrazik
initial commit
425
            if self.unapproved_prerequisite_exists(mp):
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
426
                logger.debug('Unapproved prerequisite exists for ' + str(mp))
1 by Martin Mrazik
initial commit
427
                continue
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
428
429
            logger.debug('Going to trigger merge: ' + str(mp.source_branch))
27.2.3 by Martin Mrazik
Unauthorized handling for al jobs as well
430
27.2.5 by Martin Mrazik
propper ret value handling
431
            ret = self.start_jenkins_job(jenkins_url, jenkins_job, mp)
432
            result = ret and result
433
        return result
27.2.6 by Martin Mrazik
missing newline
434
1 by Martin Mrazik
initial commit
435
    def get_merge_proposals(self, branch_name, state):
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
436
        branch = self._launchpad.branches.getByUrl(url=branch_name)
1 by Martin Mrazik
initial commit
437
        if not branch:
35.1.2 by Martin Mrazik
Explicit export + pep8 compliance
438
            logger.debug('Branch "' + branch_name + '" doesn\'t exist')
1 by Martin Mrazik
initial commit
439
            return []
22.1.1 by Martin Mrazik
Fixed few typos + created tests to avoid similar regressions
440
        mps = []
1 by Martin Mrazik
initial commit
441
        for mp in branch.landing_candidates:
442
            if mp.queue_status in state:
443
                mps.append(mp)
444
        return mps
445
22.2.1 by Martin Mrazik
first attempt to migrate autoland.sh to autoland.py
446
    def get_reviews(self, mp):
447
        reviews = []
448
        for vote in mp.votes:
449
            if vote.comment:
450
                reviews.append('%s;%s' % (vote.reviewer.display_name,
451
                                          vote.comment.vote))
452
        if len(reviews) == 0:
453
            return None
454
455
        return reviews
456
50.2.1 by Martin Mrazik
Check for commit message before starting the build
457
    def get_commit_message(self, mp, use_description):
458
        message = None
459
        if mp.commit_message:
460
            message = mp.commit_message
461
        elif use_description and mp.description:
462
            message = mp.description
463
        return message
464
22.2.13 by Martin Mrazik
removed closeBugs.py; improved error handling
465
    def close_bugs(self, mp):
466
        for b in mp.source_branch.linked_bugs_collection:
467
            for task in b.bug_tasks_collection:
37.2.1 by Martin Mrazik
fixing bug when unrelated bug tasks were set to fix commited
468
                if task.target.web_link == mp.target_branch.project.web_link:
22.2.13 by Martin Mrazik
removed closeBugs.py; improved error handling
469
                    task.status = 'Fix Committed'
470
                    task.lp_save()