Merge lp:~powersj/jenkins-launchpad-plugin/add-pipeline-report into lp:jenkins-launchpad-plugin

Proposed by Joshua Powers
Status: Merged
Merged at revision: 136
Proposed branch: lp:~powersj/jenkins-launchpad-plugin/add-pipeline-report
Merge into: lp:jenkins-launchpad-plugin
Diff against target: 176 lines (+119/-0)
3 files modified
jlp/jenkinsutils.py (+30/-0)
tests/__init__.py (+53/-0)
tests/test_jenkinsutils.py (+36/-0)
To merge this branch: bzr merge lp:~powersj/jenkins-launchpad-plugin/add-pipeline-report
Reviewer Review Type Date Requested Status
Francis Ginther Approve
Review via email: mp+326246@code.launchpad.net

Commit message

Add Pipeline reporting

This will go through the stages of a pipeline and report the outcomes of
each stage. It will stop if a failure is found because Jenkins
incorrectly will report all stages after a failure as a success.

To post a comment you must log in.
Revision history for this message
Francis Ginther (fginther) wrote :

I'm not able to immediately test this out, but I think this code will generate exceptions with the workflow plugins are not installed.

A few unit tests would also be appreciated.

review: Needs Fixing
Revision history for this message
Joshua Powers (powersj) :
135. By Joshua Powers

Catch 404 and add tests to check for matrix, pipeline, and freestyle jobs.

Revision history for this message
Joshua Powers (powersj) wrote :

Can you give this one another look?

Revision history for this message
Francis Ginther (fginther) wrote :

I think it's better to catch the exception only where the code is expected to raise it. Otherwise this looks good. Thanks for the tests too.

review: Needs Fixing
136. By Joshua Powers

Restrict try/except to only the is_pipeline_job

Revision history for this message
Joshua Powers (powersj) wrote :

I wasn't sure on the urllib2 import (wasn't sure of the ordering), otherwise restricted the try/catch as requested. Thanks!

Revision history for this message
Francis Ginther (fginther) wrote :

This looks good now, approve.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'jlp/jenkinsutils.py'
2--- jlp/jenkinsutils.py 2017-06-23 19:03:15 +0000
3+++ jlp/jenkinsutils.py 2018-01-26 18:56:00 +0000
4@@ -3,6 +3,7 @@
5 from xml.dom.minidom import parseString
6 import jenkins
7 import urlparse
8+import urllib2
9 import launchpadutils
10 from textwrap import dedent
11 from . import get_json_jenkins
12@@ -408,6 +409,33 @@
13 return 'activeConfigurations' in data
14
15
16+def get_pipeline_builds(jenkins, job_url):
17+ """Get a list of stages for pipeline jobs."""
18+ job = jenkins.get_json_data(job_url + 'wfapi', append_api=False)
19+ data = []
20+ for stage in job['stages']:
21+ data.append({'result': stage['status'],
22+ 'output': stage['name']})
23+ if stage['status'] == 'FAILED':
24+ break
25+
26+ return data
27+
28+
29+def is_pipeline_job(jenkins, job_url):
30+ """ Check if a job is a pipeline job."""
31+ try:
32+ data = jenkins.get_json_data(job_url + 'wfapi',
33+ append_api=False)
34+ except urllib2.HTTPError:
35+ data = None
36+
37+ if data:
38+ return 'stages' in data
39+ else:
40+ return False
41+
42+
43 def parse_jenkins_url(url):
44 """ Return a touple of jenkins_url, job_name and build_number given
45 a jenkins url """
46@@ -437,6 +465,8 @@
47 builds = []
48 if is_matrix_job(jenkins, jenkins_url + job_name):
49 builds = builds + get_multiconfig_builds(jenkins, url)
50+ elif is_pipeline_job(jenkins, url):
51+ builds = builds + get_pipeline_builds(jenkins, url)
52 else:
53 downstream_builds = get_downstream_builds(jenkins,
54 jenkins_url,
55
56=== modified file 'tests/__init__.py'
57--- tests/__init__.py 2016-01-27 15:09:31 +0000
58+++ tests/__init__.py 2018-01-26 18:56:00 +0000
59@@ -50,6 +50,55 @@
60 'artifacts': [{'relativePath': 'results/coverity/CID_10895.html'},
61 {'relativePath': 'results/coverity/CID_10896.html'}]}
62
63+ job_pipeline_data = {
64+ "_links": {
65+ "self": {
66+ "href": "/job/job-pipeline/223/wfapi/describe"
67+ },
68+ "changesets": {
69+ "href": "/job/job-pipeline/223/wfapi/changesets"
70+ }
71+ },
72+ "id": "223",
73+ "name": "#223",
74+ "status": "SUCCESS",
75+ "startTimeMillis": 1516836240699,
76+ "endTimeMillis": 0,
77+ "durationMillis": 0,
78+ "queueDurationMillis": 251,
79+ "pauseDurationMillis": 0,
80+ "stages": [
81+ {
82+ "_links": {
83+ "self": {
84+ "href": "/job/job-pipeline/223/execution/node/6/wfapi/describe"
85+ }
86+ },
87+ "id": "6",
88+ "name": "Checkout",
89+ "execNode": "",
90+ "status": "SUCCESS",
91+ "startTimeMillis": 1516836240950,
92+ "durationMillis": 10949,
93+ "pauseDurationMillis": 0
94+ },
95+ {
96+ "_links": {
97+ "self": {
98+ "href": "/job/job-pipeline/223/execution/node/12/wfapi/describe"
99+ }
100+ },
101+ "id": "12",
102+ "name": "Unit & Style Tests",
103+ "execNode": "",
104+ "status": "SUCCESS",
105+ "startTimeMillis": 1516836251932,
106+ "durationMillis": 221722,
107+ "pauseDurationMillis": 0
108+ }
109+ ]
110+ }
111+
112 job_freestyle_build_data = {
113 u'builds': [{u'actions': [{u'causes':
114 [{u'upstreamBuild': 3,
115@@ -120,6 +169,8 @@
116 return self.job_multiconfig_data
117 elif url == 'http://10.0.0.1:8080/job/job-multiconfig/3':
118 return self.job_multiconfig_build3_data
119+ elif url == 'http://10.0.0.1:8080/job/job-pipeline/wfapi':
120+ return self.job_pipeline_data
121 elif url == 'http://10.0.0.1:8080/job/job-freestyle':
122 return self.job_freestyle_data
123 elif url == 'http://10.0.0.1:8080/job/freestyle-downstream':
124@@ -175,3 +226,5 @@
125 'number,url,result,actions[causes[upstreamBuild,' +\
126 'upstreamProject]]]]':
127 return self.job_freestyle_triggered_build_data
128+ elif url == 'http://10.0.0.1:8080/404':
129+ return None
130
131=== modified file 'tests/test_jenkinsutils.py'
132--- tests/test_jenkinsutils.py 2017-02-18 13:28:31 +0000
133+++ tests/test_jenkinsutils.py 2018-01-26 18:56:00 +0000
134@@ -625,6 +625,42 @@
135 self.assertEqual(ret, False)
136
137
138+class JenkinsUtilsIsPipelineJob(unittest.TestCase):
139+
140+ def setUp(self):
141+ self.jenkins = JenkinsJSONData()
142+
143+ def test_matrix_job(self):
144+ ret = jenkinsutils.is_pipeline_job(
145+ self.jenkins,
146+ 'http://10.0.0.1:8080/job/job-multiconfig/')
147+ self.assertEqual(ret, False)
148+
149+ def test_matrix_job_with_build_number(self):
150+ ret = jenkinsutils.is_pipeline_job(
151+ self.jenkins,
152+ 'http://10.0.0.1:8080/job/job-multiconfig/32')
153+ self.assertEqual(ret, False)
154+
155+ def test_non_matrix_job(self):
156+ ret = jenkinsutils.is_pipeline_job(
157+ self.jenkins,
158+ 'http://10.0.0.1:8080/job/job-freestyle/')
159+ self.assertEqual(ret, False)
160+
161+ def test_pipeline_job(self):
162+ ret = jenkinsutils.is_pipeline_job(
163+ self.jenkins,
164+ 'http://10.0.0.1:8080/job/job-pipeline/')
165+ self.assertEqual(ret, True)
166+
167+ def test_404_job(self):
168+ ret = jenkinsutils.is_pipeline_job(
169+ self.jenkins,
170+ 'http://10.0.0.1:8080/404')
171+ self.assertEqual(ret, False)
172+
173+
174 class JenkinsUtilsGetExecutedBuilds(unittest.TestCase):
175 def setUp(self):
176 self.jenkins = JenkinsJSONData()

Subscribers

People subscribed via source and target branches

to all changes: