Merge lp:~hashar/python-jenkins/trunk into lp:~python-jenkins-developers/python-jenkins/trunk
- trunk
- Merge into trunk
Proposed by
Antoine "hashar" Musso
Status: | Merged |
---|---|
Merged at revision: | 17 |
Proposed branch: | lp:~hashar/python-jenkins/trunk |
Merge into: | lp:~python-jenkins-developers/python-jenkins/trunk |
Diff against target: |
988 lines (+256/-329) 9 files modified
.pep8 (+2/-0) doc/Makefile (+27/-4) doc/source/api.rst (+8/-0) doc/source/conf.py (+23/-16) doc/source/example.rst (+22/-0) doc/source/index.rst (+7/-201) doc/source/install.rst (+24/-0) jenkins/__init__.py (+142/-107) setup.py (+1/-1) |
To merge this branch: | bzr merge lp:~hashar/python-jenkins/trunk |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Page | Approve | ||
Review via email: mp+158768@code.launchpad.net |
Commit message
fix pep8 issues and refactor sphinx documentation.
Description of the change
My place had a rainy saturday so I decided to do some maintenance work on python-jenkins. This branch has:
- r17: fix pep8 errors on the original code
pep8 is a python standard that is always nice to stick to. Lot of other developers are expecting that style. I haven't tested the changes though :(
- r18: overhaul the sphinx documentation
The module had documentation in both an index.rst and inlined in the jenkins/__init__.py file. The revision drop index.rst manually maintained doc in favor of the inlined one. I have synced a couple of outdated doc blocks. See commit message for details :-]
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file '.pep8' |
2 | --- .pep8 1970-01-01 00:00:00 +0000 |
3 | +++ .pep8 2013-04-13 22:12:24 +0000 |
4 | @@ -0,0 +1,2 @@ |
5 | +[pep8] |
6 | +ignore = E221,E501 |
7 | |
8 | === modified file 'doc/Makefile' |
9 | --- doc/Makefile 2011-09-04 00:00:30 +0000 |
10 | +++ doc/Makefile 2013-04-13 22:12:24 +0000 |
11 | @@ -5,14 +5,16 @@ |
12 | SPHINXOPTS = |
13 | SPHINXBUILD = sphinx-build |
14 | PAPER = |
15 | -BUILDDIR = _build |
16 | +BUILDDIR = build |
17 | |
18 | # Internal variables. |
19 | PAPEROPT_a4 = -D latex_paper_size=a4 |
20 | PAPEROPT_letter = -D latex_paper_size=letter |
21 | -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . |
22 | +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source |
23 | +# the i18n builder cannot share the environment and doctrees with the others |
24 | +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source |
25 | |
26 | -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest |
27 | +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext |
28 | |
29 | help: |
30 | @echo "Please use \`make <target>' where <target> is one of" |
31 | @@ -29,6 +31,9 @@ |
32 | @echo " latexpdf to make LaTeX files and run them through pdflatex" |
33 | @echo " text to make text files" |
34 | @echo " man to make manual pages" |
35 | + @echo " texinfo to make Texinfo files" |
36 | + @echo " info to make Texinfo files and run them through makeinfo" |
37 | + @echo " gettext to make PO message catalogs" |
38 | @echo " changes to make an overview of all changed/added/deprecated items" |
39 | @echo " linkcheck to check all external links for integrity" |
40 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" |
41 | @@ -100,7 +105,7 @@ |
42 | latexpdf: |
43 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex |
44 | @echo "Running LaTeX files through pdflatex..." |
45 | - make -C $(BUILDDIR)/latex all-pdf |
46 | + $(MAKE) -C $(BUILDDIR)/latex all-pdf |
47 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." |
48 | |
49 | text: |
50 | @@ -113,6 +118,24 @@ |
51 | @echo |
52 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." |
53 | |
54 | +texinfo: |
55 | + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo |
56 | + @echo |
57 | + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." |
58 | + @echo "Run \`make' in that directory to run these through makeinfo" \ |
59 | + "(use \`make info' here to do that automatically)." |
60 | + |
61 | +info: |
62 | + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo |
63 | + @echo "Running Texinfo files through makeinfo..." |
64 | + make -C $(BUILDDIR)/texinfo info |
65 | + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." |
66 | + |
67 | +gettext: |
68 | + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale |
69 | + @echo |
70 | + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." |
71 | + |
72 | changes: |
73 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes |
74 | @echo |
75 | |
76 | === added directory 'doc/build' |
77 | === added directory 'doc/source' |
78 | === added file 'doc/source/api.rst' |
79 | --- doc/source/api.rst 1970-01-01 00:00:00 +0000 |
80 | +++ doc/source/api.rst 2013-04-13 22:12:24 +0000 |
81 | @@ -0,0 +1,8 @@ |
82 | +:title: API reference |
83 | + |
84 | +API reference |
85 | +============= |
86 | + |
87 | +.. automodule:: jenkins |
88 | + :members: |
89 | + :undoc-members: |
90 | |
91 | === renamed file 'doc/conf.py' => 'doc/source/conf.py' |
92 | --- doc/conf.py 2011-09-04 00:00:30 +0000 |
93 | +++ doc/source/conf.py 2013-04-13 22:12:24 +0000 |
94 | @@ -3,7 +3,8 @@ |
95 | # Python Jenkins documentation build configuration file, created by |
96 | # sphinx-quickstart on Sat Sep 3 16:24:58 2011. |
97 | # |
98 | -# This file is execfile()d with the current directory set to its containing dir. |
99 | +# This file is execfile()d with the current directory set to its containing |
100 | +# dir. |
101 | # |
102 | # Note that not all possible configuration values are present in this |
103 | # autogenerated file. |
104 | @@ -11,25 +12,31 @@ |
105 | # All configuration values have a default; values that are commented out |
106 | # serve to show the default. |
107 | |
108 | -import sys, os |
109 | - |
110 | -import sys, os |
111 | -sys.path.insert(0, os.path.abspath('..')) |
112 | +import os |
113 | +import sys |
114 | |
115 | # If extensions (or modules to document with autodoc) are in another directory, |
116 | # add these directories to sys.path here. If the directory is relative to the |
117 | # documentation root, use os.path.abspath to make it absolute, like shown here. |
118 | #sys.path.insert(0, os.path.abspath('.')) |
119 | +sys.path.insert(0, os.path.abspath('../..')) |
120 | +sys.path.insert(0, os.path.abspath('../../jenkins')) |
121 | |
122 | -# -- General configuration ----------------------------------------------------- |
123 | +# -- General configuration ---------------------------------------------------- |
124 | |
125 | # If your documentation needs a minimal Sphinx version, state it here. |
126 | #needs_sphinx = '1.0' |
127 | |
128 | -# Add any Sphinx extension module names here, as strings. They can be extensions |
129 | -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. |
130 | +# Add any Sphinx extension module names here, as strings. They can be |
131 | +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. |
132 | extensions = ['sphinx.ext.autodoc'] |
133 | |
134 | +# Also document __init__ |
135 | +autoclass_content = 'both' |
136 | + |
137 | +# Change that to 'alphabetical' if you want |
138 | +autodoc_member_order = 'bysource' |
139 | + |
140 | # Add any paths that contain templates here, relative to this directory. |
141 | templates_path = ['_templates'] |
142 | |
143 | @@ -69,7 +76,7 @@ |
144 | # directories to ignore when looking for source files. |
145 | exclude_patterns = ['_build'] |
146 | |
147 | -# The reST default role (used for this markup: `text`) to use for all documents. |
148 | +# The reST default role (used for this markup: `text`) to use for all documents |
149 | #default_role = None |
150 | |
151 | # If true, '()' will be appended to :func: etc. cross-reference text. |
152 | @@ -90,7 +97,7 @@ |
153 | #modindex_common_prefix = [] |
154 | |
155 | |
156 | -# -- Options for HTML output --------------------------------------------------- |
157 | +# -- Options for HTML output -------------------------------------------------- |
158 | |
159 | # The theme to use for HTML and HTML Help pages. See the documentation for |
160 | # a list of builtin themes. |
161 | @@ -123,7 +130,7 @@ |
162 | # Add any paths that contain custom static files (such as style sheets) here, |
163 | # relative to this directory. They are copied after the builtin static files, |
164 | # so a file named "default.css" will overwrite the builtin "default.css". |
165 | -html_static_path = ['_static'] |
166 | +#html_static_path = ['_static'] |
167 | |
168 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, |
169 | # using the given strftime format. |
170 | @@ -170,7 +177,7 @@ |
171 | htmlhelp_basename = 'PythonJenkinsdoc' |
172 | |
173 | |
174 | -# -- Options for LaTeX output -------------------------------------------------- |
175 | +# -- Options for LaTeX output ------------------------------------------------- |
176 | |
177 | # The paper size ('letter' or 'a4'). |
178 | #latex_paper_size = 'letter' |
179 | @@ -179,10 +186,10 @@ |
180 | #latex_font_size = '10pt' |
181 | |
182 | # Grouping the document tree into LaTeX files. List of tuples |
183 | -# (source start file, target name, title, author, documentclass [howto/manual]). |
184 | +# (source start file, target name, title, author, documentclass [howto/manual]) |
185 | latex_documents = [ |
186 | - ('index', 'PythonJenkins.tex', u'Python Jenkins Documentation', |
187 | - u'Ken Conley, James Page, Tully Foote, Matthew Gertner', 'manual'), |
188 | + ('index', 'PythonJenkins.tex', u'Python Jenkins Documentation', |
189 | + u'Ken Conley, James Page, Tully Foote, Matthew Gertner', 'manual'), |
190 | ] |
191 | |
192 | # The name of an image file (relative to this directory) to place at the top of |
193 | @@ -209,7 +216,7 @@ |
194 | #latex_domain_indices = True |
195 | |
196 | |
197 | -# -- Options for manual page output -------------------------------------------- |
198 | +# -- Options for manual page output ------------------------------------------- |
199 | |
200 | # One entry per manual page. List of tuples |
201 | # (source start file, name, description, authors, manual section). |
202 | |
203 | === added file 'doc/source/example.rst' |
204 | --- doc/source/example.rst 1970-01-01 00:00:00 +0000 |
205 | +++ doc/source/example.rst 2013-04-13 22:12:24 +0000 |
206 | @@ -0,0 +1,22 @@ |
207 | +Example usage |
208 | +============= |
209 | + |
210 | +Example usage:: |
211 | + |
212 | + j = jenkins.Jenkins('http://your_url_here', 'username', 'password') |
213 | + j.get_jobs() |
214 | + j.create_job('empty', jenkins.EMPTY_CONFIG_XML) |
215 | + j.disable_job('empty') |
216 | + j.copy_job('empty', 'empty_copy') |
217 | + j.enable_job('empty_copy') |
218 | + j.reconfig_job('empty_copy', jenkins.RECONFIG_XML) |
219 | + |
220 | + j.delete_job('empty') |
221 | + j.delete_job('empty_copy') |
222 | + |
223 | + # build a parameterized job |
224 | + j.build_job('api-test', {'param1': 'test value 1', 'param2': 'test value 2'}) |
225 | + build_info = j.get_build_info('build_name', next_build_number) |
226 | + print(build_info) |
227 | + |
228 | +Look at the :doc:`api` for more details. |
229 | |
230 | === renamed file 'doc/index.rst' => 'doc/source/index.rst' |
231 | --- doc/index.rst 2012-03-01 18:03:09 +0000 |
232 | +++ doc/source/index.rst 2013-04-13 22:12:24 +0000 |
233 | @@ -5,213 +5,19 @@ |
234 | <http://jenkins-ci.org/>`_ continuous integration server. It is useful |
235 | for creating and managing jobs as well as build nodes. |
236 | |
237 | -Example usage:: |
238 | - |
239 | - j = jenkins.Jenkins('http://your_url_here', 'username', 'password') |
240 | - j.get_jobs() |
241 | - j.create_job('empty', jenkins.EMPTY_CONFIG_XML) |
242 | - j.disable_job('empty') |
243 | - j.copy_job('empty', 'empty_copy') |
244 | - j.enable_job('empty_copy') |
245 | - j.reconfig_job('empty_copy', jenkins.RECONFIG_XML) |
246 | - |
247 | - j.delete_job('empty') |
248 | - j.delete_job('empty_copy') |
249 | - |
250 | - # build a parameterized job |
251 | - j.build_job('api-test', {'param1': 'test value 1', 'param2': 'test value 2'}) |
252 | - build_info = j.get_build_info('build_name', next_build_number) |
253 | - print(build_info) |
254 | +Table of content: |
255 | + |
256 | +.. toctree:: |
257 | + :maxdepth: 2 |
258 | + :glob: |
259 | + |
260 | + * |
261 | |
262 | Python Jenkins development is hosted on Launchpad: https://launchpad.net/python-jenkins |
263 | |
264 | -Installing |
265 | -========== |
266 | - |
267 | -``pip``:: |
268 | - |
269 | - pip install python-jenkins |
270 | - |
271 | -``easy_install``:: |
272 | - |
273 | - easy_install python-jenkins |
274 | - |
275 | -Ubuntu Oneiric or later:: |
276 | - |
277 | - apt-get install python-jenkins |
278 | - |
279 | - |
280 | -API documentation |
281 | -================= |
282 | - |
283 | -.. class:: JenkinsException |
284 | - |
285 | - General exception type for jenkins-API-related failures. |
286 | - |
287 | -.. class:: Jenkins(url, [username=None, [password=None]]) |
288 | - |
289 | - Create handle to Jenkins instance. |
290 | - |
291 | - All methods will raise :class:`JenkinsException` on failure. |
292 | - |
293 | - :param username: Server username, ``str`` |
294 | - :param password: Server password, ``str`` |
295 | - :param url: URL of Jenkins server, ``str`` |
296 | - |
297 | - |
298 | - .. method:: get_jobs(self) |
299 | - |
300 | - Get list of jobs running. Each job is a dictionary with |
301 | - 'name', 'url', and 'color' keys. |
302 | - |
303 | - :returns: list of jobs, ``[ { str: str} ]`` |
304 | - |
305 | - .. method:: job_exists(name) |
306 | - |
307 | - :param name: Name of Jenkins job, ``str`` |
308 | - :returns: ``True`` if Jenkins job exists |
309 | - |
310 | - .. method:: build_job(name, [parameters=None, [token=None]]) |
311 | - |
312 | - Trigger build job. |
313 | - |
314 | - :param parameters: parameters for job, or ``None``, ``dict`` |
315 | - |
316 | - .. method:: build_job_url(name, [parameters=None, [token=None]]) |
317 | - |
318 | - Get URL to trigger build job. Authenticated setups may require configuring a token on the server side. |
319 | - |
320 | - :param parameters: parameters for job, or None., ``dict`` |
321 | - :param token: (optional) token for building job, ``str`` |
322 | - :returns: URL for building job |
323 | - |
324 | - .. method:: create_job(name, config_xml) |
325 | - |
326 | - Create a new Jenkins job |
327 | - |
328 | - :param name: Name of Jenkins job, ``str`` |
329 | - :param config_xml: config file text, ``str`` |
330 | - |
331 | - .. method:: copy_job(from_name, to_name) |
332 | - |
333 | - Copy a Jenkins job |
334 | - |
335 | - :param from_name: Name of Jenkins job to copy from, ``str`` |
336 | - :param to_name: Name of Jenkins job to copy to, ``str`` |
337 | - |
338 | - .. method:: delete_job(name) |
339 | - |
340 | - Delete Jenkins job permanently. |
341 | - |
342 | - :param name: Name of Jenkins job, ``str`` |
343 | - |
344 | - .. method:: enable_job(name) |
345 | - |
346 | - Enable Jenkins job. |
347 | - |
348 | - :param name: Name of Jenkins job, ``str`` |
349 | - |
350 | - .. method:: disable_job(name) |
351 | - |
352 | - Disable Jenkins job. To re-enable, call :meth:`Jenkins.enable_job`. |
353 | - |
354 | - :param name: Name of Jenkins job, ``str`` |
355 | - |
356 | - .. method:: get_build_info(name, number) |
357 | - |
358 | - Get build information dictionary. |
359 | - |
360 | - :param name: Job name, ``str`` |
361 | - :param name: Build number, ``int`` |
362 | - :returns: dictionary of build information |
363 | - |
364 | - .. method:: get_job_config(name) -> str |
365 | - |
366 | - Get configuration XML of existing Jenkins job. |
367 | - |
368 | - :param name: Name of Jenkins job, ``str`` |
369 | - :returns: Job configuration XML |
370 | - |
371 | - .. method:: get_job_info(name) |
372 | - |
373 | - Get job information dictionary. |
374 | - |
375 | - :param name: Job name, ``str`` |
376 | - :returns: dictionary of job information |
377 | - |
378 | - .. method:: debug_job_info(job_name) |
379 | - |
380 | - Print out job info in more readable format |
381 | - |
382 | - .. method:: reconfig_job(name, config_xml) |
383 | - |
384 | - Change configuration of existing Jenkins job. To create a new job, see :meth:`Jenkins.create_job`. |
385 | - |
386 | - :param name: Name of Jenkins job, ``str`` |
387 | - :param config_xml: New XML configuration, ``str`` |
388 | - |
389 | - .. method:: get_node_info(name) -> dict |
390 | - |
391 | - Get node information dictionary |
392 | - |
393 | - :param name: Node name, ``str`` |
394 | - :returns: Dictionary of node info, ``dict`` |
395 | - |
396 | - .. method:: node_exists(name) -> bool |
397 | - |
398 | - :param name: Name of Jenkins node, ``str`` |
399 | - :returns: ``True`` if Jenkins node exists |
400 | - |
401 | - .. method:: create_node(name, [numExecutors=2, [nodeDescription=None, [remoteFS='/var/lib/jenkins', [labels=None, [exclusive=False]]]]]) |
402 | - |
403 | - :param name: name of node to create, ``str`` |
404 | - :param numExecutors: number of executors for node, ``int`` |
405 | - :param nodeDescription: Description of node, ``str`` |
406 | - :param remoteFS: Remote filesystem location to use, ``str`` |
407 | - :param labels: Labels to associate with node, ``str`` |
408 | - :param exclusive: Use this node for tied jobs only, ``bool`` |
409 | - |
410 | - .. method:: delete_node(name) |
411 | - |
412 | - Delete Jenkins node permanently. |
413 | - |
414 | - :param name: Name of Jenkins node, ``str`` |
415 | - |
416 | - .. method:: get_queue_info(self) |
417 | - |
418 | - :returns: list of job dictionaries, ``[dict]`` |
419 | - |
420 | - Example:: |
421 | - |
422 | - >>> queue_info = j.get_queue_info() |
423 | - >>> print(queue_info[0]) |
424 | - {u'task': {u'url': u'http://your_url/job/my_job/', u'color': u'aborted_anime', u'name': u'my_job'}, u'stuck': False, u'actions': [{u'causes': [{u'shortDescription': u'Started by timer'}]}], u'buildable': False, u'params': u'', u'buildableStartMilliseconds': 1315087293316, u'why': u'Build #2,532 is already in progress (ETA:10 min)', u'blocked': True} |
425 | - |
426 | - .. method:: get_info(self) |
427 | - |
428 | - Get information on this Master. This information |
429 | - includes job list and view information. |
430 | - |
431 | - :returns: dictionary of information about Master, ``dict`` |
432 | - |
433 | - Example:: |
434 | - |
435 | - >>> info = j.get_info() |
436 | - >>> jobs = info['jobs'] |
437 | - >>> print(jobs[0]) |
438 | - {u'url': u'http://your_url_here/job/my_job/', u'color': u'blue', u'name': u'my_job'} |
439 | - |
440 | - |
441 | - .. method:: jenkins_open(req) |
442 | - |
443 | - Utility routine for opening an HTTP request to a Jenkins server. This should only be used |
444 | - to extends the :class:`Jenkins` API. |
445 | - |
446 | - |
447 | Indices and tables |
448 | ================== |
449 | |
450 | * :ref:`genindex` |
451 | * :ref:`modindex` |
452 | * :ref:`search` |
453 | - |
454 | |
455 | === added file 'doc/source/install.rst' |
456 | --- doc/source/install.rst 1970-01-01 00:00:00 +0000 |
457 | +++ doc/source/install.rst 2013-04-13 22:12:24 +0000 |
458 | @@ -0,0 +1,24 @@ |
459 | +:title: Installing |
460 | + |
461 | +Installing |
462 | +========== |
463 | + |
464 | +The module is known to pip and Debian based distribution as |
465 | +``python-jenkins``. |
466 | + |
467 | +``pip``:: |
468 | + |
469 | + pip install python-jenkins |
470 | + |
471 | +``easy_install``:: |
472 | + |
473 | + easy_install python-jenkins |
474 | + |
475 | +The module has been packaged since Ubuntu Oneiric (11.10):: |
476 | + |
477 | + apt-get install python-jenkins |
478 | + |
479 | +For developpement purpose you can get a fake module installed on your system |
480 | +that will point to your working copy. Simply use:: |
481 | + |
482 | + python setup.py develop |
483 | |
484 | === modified file 'jenkins/__init__.py' |
485 | --- jenkins/__init__.py 2012-06-25 11:48:32 +0000 |
486 | +++ jenkins/__init__.py 2013-04-13 22:12:24 +0000 |
487 | @@ -38,57 +38,45 @@ |
488 | # Matthew Gertner <matthew.gertner@gmail.com> |
489 | |
490 | ''' |
491 | -Python API for Jenkins |
492 | - |
493 | -Examples:: |
494 | - |
495 | - j = jenkins.Jenkins('http://your_url_here', 'username', 'password') |
496 | - j.get_jobs() |
497 | - j.create_job('empty', jenkins.EMPTY_CONFIG_XML) |
498 | - j.disable_job('empty') |
499 | - j.copy_job('empty', 'empty_copy') |
500 | - j.enable_job('empty_copy') |
501 | - j.reconfig_job('empty_copy', jenkins.RECONFIG_XML) |
502 | - |
503 | - j.delete_job('empty') |
504 | - j.delete_job('empty_copy') |
505 | - |
506 | - # build a parameterized job |
507 | - j.build_job('api-test', {'param1': 'test value 1', 'param2': 'test value 2'}) |
508 | +.. module:: jenkins |
509 | + :platform: Unix, Windows |
510 | + :synopsis: Python API to interact with Jenkins |
511 | + |
512 | +See examples at :doc:`example` |
513 | ''' |
514 | |
515 | -import sys |
516 | +#import sys |
517 | import urllib2 |
518 | import urllib |
519 | import base64 |
520 | -import traceback |
521 | +#import traceback |
522 | import json |
523 | import httplib |
524 | |
525 | -LAUNCHER_SSH = 'hudson.plugins.sshslaves.SSHLauncher' |
526 | -LAUNCHER_COMMAND = 'hudson.slaves.CommandLauncher' |
527 | +LAUNCHER_SSH = 'hudson.plugins.sshslaves.SSHLauncher' |
528 | +LAUNCHER_COMMAND = 'hudson.slaves.CommandLauncher' |
529 | LAUNCHER_WINDOWS_SERVICE = 'hudson.os.windows.ManagedWindowsServiceLauncher' |
530 | |
531 | -INFO = 'api/json' |
532 | -JOB_INFO = 'job/%(name)s/api/json?depth=0' |
533 | -Q_INFO = 'queue/api/json?depth=0' |
534 | +INFO = 'api/json' |
535 | +JOB_INFO = 'job/%(name)s/api/json?depth=0' |
536 | +Q_INFO = 'queue/api/json?depth=0' |
537 | CANCEL_QUEUE = 'queue/item/%(number)s/cancelQueue' |
538 | -CREATE_JOB = 'createItem?name=%(name)s' #also post config.xml |
539 | -CONFIG_JOB = 'job/%(name)s/config.xml' |
540 | -DELETE_JOB = 'job/%(name)s/doDelete' |
541 | -ENABLE_JOB = 'job/%(name)s/enable' |
542 | -DISABLE_JOB = 'job/%(name)s/disable' |
543 | -COPY_JOB = 'createItem?name=%(to_name)s&mode=copy&from=%(from_name)s' |
544 | -BUILD_JOB = 'job/%(name)s/build' |
545 | -STOP_BUILD = 'job/%(name)s/%(number)s/stop' |
546 | +CREATE_JOB = 'createItem?name=%(name)s' # also post config.xml |
547 | +CONFIG_JOB = 'job/%(name)s/config.xml' |
548 | +DELETE_JOB = 'job/%(name)s/doDelete' |
549 | +ENABLE_JOB = 'job/%(name)s/enable' |
550 | +DISABLE_JOB = 'job/%(name)s/disable' |
551 | +COPY_JOB = 'createItem?name=%(to_name)s&mode=copy&from=%(from_name)s' |
552 | +BUILD_JOB = 'job/%(name)s/build' |
553 | +STOP_BUILD = 'job/%(name)s/%(number)s/stop' |
554 | BUILD_WITH_PARAMS_JOB = 'job/%(name)s/buildWithParameters' |
555 | -BUILD_INFO = 'job/%(name)s/%(number)d/api/json?depth=0' |
556 | +BUILD_INFO = 'job/%(name)s/%(number)d/api/json?depth=0' |
557 | |
558 | |
559 | CREATE_NODE = 'computer/doCreateItem?%s' |
560 | DELETE_NODE = 'computer/%(name)s/doDelete' |
561 | -NODE_INFO = 'computer/%(name)s/api/json?depth=0' |
562 | -NODE_TYPE = 'hudson.slaves.DumbSlave$DescriptorImpl' |
563 | +NODE_INFO = 'computer/%(name)s/api/json?depth=0' |
564 | +NODE_TYPE = 'hudson.slaves.DumbSlave$DescriptorImpl' |
565 | TOGGLE_OFFLINE = 'computer/%(name)s/toggleOffline?offlineMessage=%(msg)s' |
566 | |
567 | #for testing only |
568 | @@ -118,33 +106,41 @@ |
569 | <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding> |
570 | <triggers class='vector'/> |
571 | <concurrentBuild>false</concurrentBuild> |
572 | -<builders> |
573 | - <jenkins.tasks.Shell> |
574 | - <command>export FOO=bar</command> |
575 | - </jenkins.tasks.Shell> |
576 | - </builders> |
577 | +<builders> |
578 | + <jenkins.tasks.Shell> |
579 | + <command>export FOO=bar</command> |
580 | + </jenkins.tasks.Shell> |
581 | + </builders> |
582 | <publishers/> |
583 | <buildWrappers/> |
584 | </project>''' |
585 | |
586 | + |
587 | class JenkinsException(Exception): |
588 | ''' |
589 | General exception type for jenkins-API-related failures. |
590 | ''' |
591 | pass |
592 | |
593 | + |
594 | def auth_headers(username, password): |
595 | ''' |
596 | - Simple implementation of HTTP Basic Authentication. Returns the 'Authentication' header value. |
597 | + Simple implementation of HTTP Basic Authentication. Returns the |
598 | + 'Authentication' header value. |
599 | ''' |
600 | return 'Basic ' + base64.encodestring('%s:%s' % (username, password))[:-1] |
601 | |
602 | + |
603 | class Jenkins(object): |
604 | |
605 | def __init__(self, url, username=None, password=None): |
606 | ''' |
607 | Create handle to Jenkins instance. |
608 | |
609 | + All methods will raise :class:`JenkinsException` on failure. |
610 | + |
611 | + :param username: Server username, ``str`` |
612 | + :param password: Server password, ``str`` |
613 | :param url: URL of Jenkins server, ``str`` |
614 | ''' |
615 | if url[-1] == '/': |
616 | @@ -164,15 +160,17 @@ |
617 | :returns: dictionary of job information |
618 | ''' |
619 | try: |
620 | - response = self.jenkins_open(urllib2.Request(self.server + JOB_INFO%locals())) |
621 | + response = self.jenkins_open(urllib2.Request( |
622 | + self.server + JOB_INFO % locals())) |
623 | if response: |
624 | return json.loads(response) |
625 | else: |
626 | - raise JenkinsException('job[%s] does not exist'%name) |
627 | + raise JenkinsException('job[%s] does not exist' % name) |
628 | except urllib2.HTTPError: |
629 | - raise JenkinsException('job[%s] does not exist'%name) |
630 | + raise JenkinsException('job[%s] does not exist' % name) |
631 | except ValueError: |
632 | - raise JenkinsException("Could not parse JSON info for job[%s]"%name) |
633 | + raise JenkinsException( |
634 | + "Could not parse JSON info for job[%s]" % name) |
635 | |
636 | def debug_job_info(self, job_name): |
637 | ''' |
638 | @@ -183,17 +181,21 @@ |
639 | |
640 | def jenkins_open(self, req): |
641 | ''' |
642 | - Utility routine for opening an HTTP request to a Jenkins server. This should only be used |
643 | - to extends the :class:`Jenkins` API. |
644 | + Utility routine for opening an HTTP request to a Jenkins server. This |
645 | + should only be used to extends the :class:`Jenkins` API. |
646 | ''' |
647 | try: |
648 | if self.auth: |
649 | req.add_header('Authorization', self.auth) |
650 | return urllib2.urlopen(req).read() |
651 | except urllib2.HTTPError, e: |
652 | - # Jenkins's funky authentication means its nigh impossible to distinguish errors. |
653 | + # Jenkins's funky authentication means its nigh impossible to |
654 | + # distinguish errors. |
655 | if e.code in [401, 403, 500]: |
656 | - raise JenkinsException('Error in request. Possibly authentication failed [%s]'%(e.code)) |
657 | + raise JenkinsException( |
658 | + 'Error in request.' + |
659 | + 'Possibly authentication failed [%s]' % (e.code) |
660 | + ) |
661 | # right now I'm getting 302 infinites on a successful delete |
662 | |
663 | def get_build_info(self, name, number): |
664 | @@ -214,15 +216,21 @@ |
665 | {u'building': False, u'changeSet': {u'items': [{u'date': u'2011-12-19T18:01:52.540557Z', u'msg': u'test', u'revision': 66, u'user': u'unknown', u'paths': [{u'editType': u'edit', u'file': u'/branches/demo/index.html'}]}], u'kind': u'svn', u'revisions': [{u'module': u'http://eaas-svn01.i3.level3.com/eaas', u'revision': 66}]}, u'builtOn': u'', u'description': None, u'artifacts': [{u'relativePath': u'dist/eaas-87-2011-12-19_18-01-57.war', u'displayPath': u'eaas-87-2011-12-19_18-01-57.war', u'fileName': u'eaas-87-2011-12-19_18-01-57.war'}, {u'relativePath': u'dist/eaas-87-2011-12-19_18-01-57.war.zip', u'displayPath': u'eaas-87-2011-12-19_18-01-57.war.zip', u'fileName': u'eaas-87-2011-12-19_18-01-57.war.zip'}], u'timestamp': 1324317717000, u'number': 87, u'actions': [{u'parameters': [{u'name': u'SERVICE_NAME', u'value': u'eaas'}, {u'name': u'PROJECT_NAME', u'value': u'demo'}]}, {u'causes': [{u'userName': u'anonymous', u'shortDescription': u'Started by user anonymous'}]}, {}, {}, {}], u'id': u'2011-12-19_18-01-57', u'keepLog': False, u'url': u'http://eaas-jenkins01.i3.level3.com:9080/job/build_war/87/', u'culprits': [{u'absoluteUrl': u'http://eaas-jenkins01.i3.level3.com:9080/user/unknown', u'fullName': u'unknown'}], u'result': u'SUCCESS', u'duration': 8826, u'fullDisplayName': u'build_war #87'} |
666 | ''' |
667 | try: |
668 | - response = self.jenkins_open(urllib2.Request(self.server + BUILD_INFO%locals())) |
669 | + response = self.jenkins_open(urllib2.Request( |
670 | + self.server + BUILD_INFO % locals())) |
671 | if response: |
672 | return json.loads(response) |
673 | else: |
674 | - raise JenkinsException('job[%s] number[%d] does not exist'%(name, number)) |
675 | + raise JenkinsException('job[%s] number[%d] does not exist' |
676 | + % (name, number)) |
677 | except urllib2.HTTPError: |
678 | - raise JenkinsException('job[%s] number[%d] does not exist'%(name, number)) |
679 | + raise JenkinsException('job[%s] number[%d] does not exist' |
680 | + % (name, number)) |
681 | except ValueError: |
682 | - raise JenkinsException('Could not parse JSON info for job[%s] number[%d]'%(name, number)) |
683 | + raise JenkinsException( |
684 | + 'Could not parse JSON info for job[%s] number[%d]' |
685 | + % (name, number) |
686 | + ) |
687 | |
688 | def get_queue_info(self): |
689 | ''' |
690 | @@ -233,7 +241,9 @@ |
691 | >>> print(queue_info[0]) |
692 | {u'task': {u'url': u'http://your_url/job/my_job/', u'color': u'aborted_anime', u'name': u'my_job'}, u'stuck': False, u'actions': [{u'causes': [{u'shortDescription': u'Started by timer'}]}], u'buildable': False, u'params': u'', u'buildableStartMilliseconds': 1315087293316, u'why': u'Build #2,532 is already in progress (ETA:10 min)', u'blocked': True} |
693 | ''' |
694 | - return json.loads(self.jenkins_open(urllib2.Request(self.server + Q_INFO)))['items'] |
695 | + return json.loads(self.jenkins_open( |
696 | + urllib2.Request(self.server + Q_INFO) |
697 | + ))['items'] |
698 | |
699 | def cancel_queue(self, number): |
700 | ''' |
701 | @@ -259,17 +269,22 @@ |
702 | >>> info = j.get_info() |
703 | >>> jobs = info['jobs'] |
704 | >>> print(jobs[0]) |
705 | - {u'url': u'http://your_url_here/job/my_job/', u'color': u'blue', u'name': u'my_job'} |
706 | + {u'url': u'http://your_url_here/job/my_job/', u'color': u'blue', |
707 | + u'name': u'my_job'} |
708 | |
709 | """ |
710 | try: |
711 | - return json.loads(self.jenkins_open(urllib2.Request(self.server + INFO))) |
712 | + return json.loads(self.jenkins_open( |
713 | + urllib2.Request(self.server + INFO))) |
714 | except urllib2.HTTPError: |
715 | - raise JenkinsException("Error communicating with server[%s]"%self.server) |
716 | + raise JenkinsException("Error communicating with server[%s]" |
717 | + % self.server) |
718 | except httplib.BadStatusLine: |
719 | - raise JenkinsException("Error communicating with server[%s]"%self.server) |
720 | + raise JenkinsException("Error communicating with server[%s]" |
721 | + % self.server) |
722 | except ValueError: |
723 | - raise JenkinsException("Could not parse JSON info for server[%s]"%self.server) |
724 | + raise JenkinsException("Could not parse JSON info for server[%s]" |
725 | + % self.server) |
726 | |
727 | def get_jobs(self): |
728 | """ |
729 | @@ -288,20 +303,22 @@ |
730 | :param to_name: Name of Jenkins job to copy to, ``str`` |
731 | ''' |
732 | self.get_job_info(from_name) |
733 | - self.jenkins_open(urllib2.Request(self.server + COPY_JOB%locals(), '')) |
734 | + self.jenkins_open(urllib2.Request( |
735 | + self.server + COPY_JOB % locals(), '')) |
736 | if not self.job_exists(to_name): |
737 | - raise JenkinsException('create[%s] failed'%(to_name)) |
738 | + raise JenkinsException('create[%s] failed' % (to_name)) |
739 | |
740 | def delete_job(self, name): |
741 | ''' |
742 | Delete Jenkins job permanently. |
743 | - |
744 | + |
745 | :param name: Name of Jenkins job, ``str`` |
746 | ''' |
747 | self.get_job_info(name) |
748 | - self.jenkins_open(urllib2.Request(self.server + DELETE_JOB%locals(), '')) |
749 | + self.jenkins_open(urllib2.Request( |
750 | + self.server + DELETE_JOB % locals(), '')) |
751 | if self.job_exists(name): |
752 | - raise JenkinsException('delete[%s] failed'%(name)) |
753 | + raise JenkinsException('delete[%s] failed' % (name)) |
754 | |
755 | def enable_job(self, name): |
756 | ''' |
757 | @@ -310,7 +327,8 @@ |
758 | :param name: Name of Jenkins job, ``str`` |
759 | ''' |
760 | self.get_job_info(name) |
761 | - self.jenkins_open(urllib2.Request(self.server + ENABLE_JOB%locals(), '')) |
762 | + self.jenkins_open(urllib2.Request( |
763 | + self.server + ENABLE_JOB % locals(), '')) |
764 | |
765 | def disable_job(self, name): |
766 | ''' |
767 | @@ -319,7 +337,8 @@ |
768 | :param name: Name of Jenkins job, ``str`` |
769 | ''' |
770 | self.get_job_info(name) |
771 | - self.jenkins_open(urllib2.Request(self.server + DISABLE_JOB%locals(), '')) |
772 | + self.jenkins_open(urllib2.Request( |
773 | + self.server + DISABLE_JOB % locals(), '')) |
774 | |
775 | def job_exists(self, name): |
776 | ''' |
777 | @@ -340,12 +359,13 @@ |
778 | :param config_xml: config file text, ``str`` |
779 | ''' |
780 | if self.job_exists(name): |
781 | - raise JenkinsException('job[%s] already exists'%(name)) |
782 | + raise JenkinsException('job[%s] already exists' % (name)) |
783 | |
784 | headers = {'Content-Type': 'text/xml'} |
785 | - self.jenkins_open(urllib2.Request(self.server + CREATE_JOB%locals(), config_xml, headers)) |
786 | + self.jenkins_open(urllib2.Request( |
787 | + self.server + CREATE_JOB % locals(), config_xml, headers)) |
788 | if not self.job_exists(name): |
789 | - raise JenkinsException('create[%s] failed'%(name)) |
790 | + raise JenkinsException('create[%s] failed' % (name)) |
791 | |
792 | def get_job_config(self, name): |
793 | ''' |
794 | @@ -360,20 +380,22 @@ |
795 | |
796 | def reconfig_job(self, name, config_xml): |
797 | ''' |
798 | - Change configuration of existing Jenkins job. To create a new job, see :meth:`Jenkins.create_job`. |
799 | + Change configuration of existing Jenkins job. To create a new job, see |
800 | + :meth:`Jenkins.create_job`. |
801 | |
802 | :param name: Name of Jenkins job, ``str`` |
803 | :param config_xml: New XML configuration, ``str`` |
804 | ''' |
805 | self.get_job_info(name) |
806 | headers = {'Content-Type': 'text/xml'} |
807 | - reconfig_url = self.server + CONFIG_JOB%locals() |
808 | + reconfig_url = self.server + CONFIG_JOB % locals() |
809 | self.jenkins_open(urllib2.Request(reconfig_url, config_xml, headers)) |
810 | |
811 | def build_job_url(self, name, parameters=None, token=None): |
812 | ''' |
813 | - Get URL to trigger build job. Authenticated setups may require configuring a token on the server side. |
814 | - |
815 | + Get URL to trigger build job. Authenticated setups may require |
816 | + configuring a token on the server side. |
817 | + |
818 | :param parameters: parameters for job, or None., ``dict`` |
819 | :param token: (optional) token for building job, ``str`` |
820 | :returns: URL for building job |
821 | @@ -381,21 +403,26 @@ |
822 | if parameters: |
823 | if token: |
824 | parameters['token'] = token |
825 | - return self.server + BUILD_WITH_PARAMS_JOB%locals() + '?' + urllib.urlencode(parameters) |
826 | + return (self.server + BUILD_WITH_PARAMS_JOB % locals() + |
827 | + '?' + urllib.urlencode(parameters)) |
828 | elif token: |
829 | - return self.server + BUILD_JOB%locals() + '?' + urllib.urlencode({'token': token}) |
830 | + return (self.server + BUILD_JOB % locals() + |
831 | + '?' + urllib.urlencode({'token': token})) |
832 | else: |
833 | - return self.server + BUILD_JOB%locals() |
834 | + return self.server + BUILD_JOB % locals() |
835 | |
836 | def build_job(self, name, parameters=None, token=None): |
837 | ''' |
838 | Trigger build job. |
839 | - |
840 | + |
841 | + :param name: name of job |
842 | :param parameters: parameters for job, or ``None``, ``dict`` |
843 | + :param token: Jenkins API token |
844 | ''' |
845 | if not self.job_exists(name): |
846 | - raise JenkinsException('no such job[%s]'%(name)) |
847 | - return self.jenkins_open(urllib2.Request(self.build_job_url(name, parameters, token))) |
848 | + raise JenkinsException('no such job[%s]' % (name)) |
849 | + return self.jenkins_open(urllib2.Request( |
850 | + self.build_job_url(name, parameters, token))) |
851 | |
852 | def stop_build(self, name, number): |
853 | ''' |
854 | @@ -414,15 +441,17 @@ |
855 | :returns: Dictionary of node info, ``dict`` |
856 | ''' |
857 | try: |
858 | - response = self.jenkins_open(urllib2.Request(self.server + NODE_INFO%locals())) |
859 | + response = self.jenkins_open(urllib2.Request( |
860 | + self.server + NODE_INFO % locals())) |
861 | if response: |
862 | return json.loads(response) |
863 | else: |
864 | - raise JenkinsException('node[%s] does not exist'%name) |
865 | + raise JenkinsException('node[%s] does not exist' % name) |
866 | except urllib2.HTTPError: |
867 | - raise JenkinsException('node[%s] does not exist'%name) |
868 | + raise JenkinsException('node[%s] does not exist' % name) |
869 | except ValueError: |
870 | - raise JenkinsException("Could not parse JSON info for node[%s]"%name) |
871 | + raise JenkinsException("Could not parse JSON info for node[%s]" |
872 | + % name) |
873 | |
874 | def node_exists(self, name): |
875 | ''' |
876 | @@ -438,38 +467,40 @@ |
877 | def delete_node(self, name): |
878 | ''' |
879 | Delete Jenkins node permanently. |
880 | - |
881 | + |
882 | :param name: Name of Jenkins node, ``str`` |
883 | ''' |
884 | self.get_node_info(name) |
885 | - self.jenkins_open(urllib2.Request(self.server + DELETE_NODE%locals(), '')) |
886 | + self.jenkins_open(urllib2.Request( |
887 | + self.server + DELETE_NODE % locals(), '')) |
888 | if self.node_exists(name): |
889 | - raise JenkinsException('delete[%s] failed'%(name)) |
890 | - |
891 | + raise JenkinsException('delete[%s] failed' % (name)) |
892 | |
893 | def disable_node(self, name, msg=''): |
894 | ''' |
895 | Disable a node |
896 | - |
897 | + |
898 | :param name: Jenkins node name, ``str`` |
899 | :param msg: Offline message, ``str`` |
900 | ''' |
901 | info = self.get_node_info(name) |
902 | if info['offline']: |
903 | return |
904 | - self.jenkins_open(urllib2.Request(self.server + TOGGLE_OFFLINE%locals())) |
905 | + self.jenkins_open(urllib2.Request( |
906 | + self.server + TOGGLE_OFFLINE % locals())) |
907 | |
908 | def enable_node(self, name): |
909 | ''' |
910 | Enable a node |
911 | - |
912 | + |
913 | :param name: Jenkins node name, ``str`` |
914 | ''' |
915 | info = self.get_node_info(name) |
916 | if not info['offline']: |
917 | return |
918 | msg = '' |
919 | - self.jenkins_open(urllib2.Request(self.server + TOGGLE_OFFLINE%locals())) |
920 | + self.jenkins_open(urllib2.Request( |
921 | + self.server + TOGGLE_OFFLINE % locals())) |
922 | |
923 | def create_node(self, name, numExecutors=2, nodeDescription=None, |
924 | remoteFS='/var/lib/jenkins', labels=None, exclusive=False, |
925 | @@ -485,7 +516,7 @@ |
926 | :param launcher_params: Additional parameters for the launcher, ``dict`` |
927 | ''' |
928 | if self.node_exists(name): |
929 | - raise JenkinsException('node[%s] already exists'%(name)) |
930 | + raise JenkinsException('node[%s] already exists' % (name)) |
931 | |
932 | mode = 'NORMAL' |
933 | if exclusive: |
934 | @@ -494,25 +525,29 @@ |
935 | launcher_params['stapler-class'] = launcher |
936 | |
937 | inner_params = { |
938 | - 'name' : name, |
939 | - 'nodeDescription' : nodeDescription, |
940 | - 'numExecutors' : numExecutors, |
941 | - 'remoteFS' : remoteFS, |
942 | - 'labelString' : labels, |
943 | - 'mode' : mode, |
944 | - 'type' : NODE_TYPE, |
945 | - 'retentionStrategy' : { 'stapler-class' : 'hudson.slaves.RetentionStrategy$Always' }, |
946 | - 'nodeProperties' : { 'stapler-class-bag' : 'true' }, |
947 | - 'launcher' : launcher_params |
948 | + 'name': name, |
949 | + 'nodeDescription': nodeDescription, |
950 | + 'numExecutors': numExecutors, |
951 | + 'remoteFS': remoteFS, |
952 | + 'labelString': labels, |
953 | + 'mode': mode, |
954 | + 'type': NODE_TYPE, |
955 | + 'retentionStrategy': { |
956 | + 'stapler-class': |
957 | + 'hudson.slaves.RetentionStrategy$Always' |
958 | + }, |
959 | + 'nodeProperties': {'stapler-class-bag': 'true'}, |
960 | + 'launcher': launcher_params |
961 | } |
962 | |
963 | params = { |
964 | - 'name' : name, |
965 | - 'type' : NODE_TYPE, |
966 | - 'json' : json.dumps(inner_params) |
967 | + 'name': name, |
968 | + 'type': NODE_TYPE, |
969 | + 'json': json.dumps(inner_params) |
970 | } |
971 | |
972 | - self.jenkins_open(urllib2.Request(self.server + CREATE_NODE%urllib.urlencode(params))) |
973 | + self.jenkins_open(urllib2.Request( |
974 | + self.server + CREATE_NODE % urllib.urlencode(params))) |
975 | |
976 | if not self.node_exists(name): |
977 | - raise JenkinsException('create[%s] failed'%(name)) |
978 | + raise JenkinsException('create[%s] failed' % (name)) |
979 | |
980 | === modified file 'setup.py' |
981 | --- setup.py 2012-05-17 15:35:18 +0000 |
982 | +++ setup.py 2013-04-13 22:12:24 +0000 |
983 | @@ -9,4 +9,4 @@ |
984 | author_email='kwc@willowgarage.com', |
985 | url='http://launchpad.net/python-jenkins', |
986 | packages=['jenkins'], |
987 | - ) |
988 | + ) |
Works for me - thanks!