jenkinsapi-0.2.16/0000755000175000017500000000000012262651621012214 5ustar ahs3ahs3jenkinsapi-0.2.16/circle.yaml0000644000175000017500000000052012262651567014347 0ustar ahs3ahs3## Custom test configuration for CircleCI machine: timezone: Europe/London # Set the timezone ## Customize dependencies dependencies: post: - python setup.py test --dry-run - easy_install coverage ## Customize test commands test: override: - nosetests jenkinsapi_tests --with-coverage --cover-html --with-xunit jenkinsapi-0.2.16/examples/0000755000175000017500000000000012262651567014043 5ustar ahs3ahs3jenkinsapi-0.2.16/examples/how_to/0000755000175000017500000000000012262651567015342 5ustar ahs3ahs3jenkinsapi-0.2.16/examples/how_to/query_a_build.py0000644000175000017500000000152312262651567020541 0ustar ahs3ahs3from jenkinsapi.view import View from jenkinsapi.jenkins import Jenkins J = Jenkins('http://localhost:8080') print J.items() j = J['foo'] j = J.get_job("foo") b = j.get_last_build() print b mjn = b.get_master_job_name() print(mjn) EMPTY_JOB_CONFIG = '''\ false true false false false false ''' new_job = J.create_job(name='foo_job', config=EMPTY_JOB_CONFIG) jenkinsapi-0.2.16/examples/how_to/create_a_job.py0000644000175000017500000000053012262651567020307 0ustar ahs3ahs3import logging logging.basicConfig() from jenkinsapi.jenkins import Jenkins from pkg_resources import resource_string J = Jenkins('http://localhost:8080') jobName = 'foo_job2' xml = resource_string('examples', 'addjob.xml') print xml j = J.create_job(jobname=jobName, config=xml) j2 = J[jobName] print j # Delete job J.delete_job(jobName) jenkinsapi-0.2.16/examples/how_to/get_config.py0000644000175000017500000000035012262651567020016 0ustar ahs3ahs3""" An example of how to use JenkinsAPI to fetch the config XML of a job. """ from jenkinsapi.jenkins import Jenkins J = Jenkins('http://localhost:8080') jobName = 'create_fwrgmkbbzk' config = J[jobName].get_config() print config jenkinsapi-0.2.16/examples/how_to/search_artifacts.py0000644000175000017500000000042212262651567021217 0ustar ahs3ahs3from jenkinsapi.api import search_artifacts jenkinsurl = "http://localhost:8080/jenkins" jobid = "test1" artifact_ids = ["test1.txt", "test2.txt"] # I need a build that contains all of these result = search_artifacts(jenkinsurl, jobid, artifact_ids) print((repr(result))) jenkinsapi-0.2.16/examples/how_to/create_views.py0000644000175000017500000000231512262651567020375 0ustar ahs3ahs3import logging from jenkinsapi.view import View from jenkinsapi.jenkins import Jenkins log_level = getattr(logging, 'DEBUG') logging.basicConfig(level=log_level) logger = logging.getLogger() jenkins_url = "http://192.168.1.64:8080/" #jenkins_url = "http://localhost:7080/" api = Jenkins(jenkins_url) # Create ListView in main view logger.info('Attempting to create new view') test_view_name = 'SimpleListView' new_view = api.views.create(test_view_name) logger.info('new_view is %s' % new_view) if new_view is None: logger.error('View was not created') else: logger.info('View has been created') logger.info('Attempting to create view that already exists') if not api.views.create(test_view_name): logger.info('View was not created') else: logger.error('View has been created') logger.info('Attempting to delete view that already exists') del api.views[test_view_name] if test_view_name in api.views: logger.error('View was not deleted') else: logger.info('View has been deleted') logger.info('Attempting to delete view that does not exist') del api.views[test_view_name] if not test_view_name in api.views: logger.error('View has been deleted') else: logger.info('View was not deleted') jenkinsapi-0.2.16/examples/how_to/create_nested_views.py0000644000175000017500000000406212262651567021740 0ustar ahs3ahs3# This example requires NestedViews plugin to be installed in Jenkins # You need to have at least one job in your Jenkins to see views import logging from pkg_resources import resource_string from jenkinsapi.view import View from jenkinsapi.views import Views from jenkinsapi.jenkins import Jenkins log_level = getattr(logging, 'DEBUG') logging.basicConfig(level=log_level) logger = logging.getLogger() jenkins_url = "http://127.0.0.1:8080/" api = Jenkins(jenkins_url) jobName = 'foo_job2' xml = resource_string('examples', 'addjob.xml') j = api.create_job(jobname=jobName, config=xml) # Create ListView in main view logger.info('Attempting to create new nested view') top_view = api.views.create('TopView', Views.NESTED_VIEW) logger.info('top_view is %s' % top_view) if top_view is None: logger.error('View was not created') else: logger.info('View has been created') print 'top_view.views=', top_view.views.keys() logger.info('Attempting to create view inside nested view') sub_view = top_view.views.create('SubView') if sub_view is None: logger.info('View was not created') else: logger.error('View has been created') logger.info('Attempting to delete sub_view') del top_view.views['SubView'] if 'SubView' in top_view.views: logger.error('SubView was not deleted') else: logger.info('SubView has been deleted') # Another way of creating sub view # This way sub view will have jobs in it logger.info('Attempting to create view with jobs inside nested view') top_view.views['SubView'] = jobName if 'SubView' not in top_view.views: logger.error('View was not created') else: logger.info('View has been created') logger.info('Attempting to delete sub_view') del top_view.views['SubView'] if 'SubView' in top_view.views: logger.error('SubView was not deleted') else: logger.info('SubView has been deleted') logger.info('Attempting to delete top view') del api.views['TopView'] if 'TopView' not in api.views: logger.info('View has been deleted') else: logger.error('View was not deleted') # Delete job that we created api.delete_job(jobName) jenkinsapi-0.2.16/examples/how_to/search_artifact_by_regexp.py0000644000175000017500000000043412262651567023103 0ustar ahs3ahs3from jenkinsapi.api import search_artifact_by_regexp import re jenkinsurl = "http://localhost:8080/jenkins" jobid = "test1" artifact_regexp = re.compile("test1\.txt") # A file name I want. result = search_artifact_by_regexp(jenkinsurl, jobid, artifact_regexp) print((repr(result))) jenkinsapi-0.2.16/examples/how_to/get_version_info_from_last_good_build.py0000644000175000017500000000063412262651567025513 0ustar ahs3ahs3""" Extract version information from the latest build. """ from jenkinsapi.jenkins import Jenkins def getSCMInfroFromLatestGoodBuild(url, jobName, username=None, password=None): J = Jenkins(url, username, password) job = J[jobName] lgb = job.get_last_good_build() return lgb.get_revision() if __name__ == '__main__': print getSCMInfroFromLatestGoodBuild('http://localhost:8080', 'fooJob') jenkinsapi-0.2.16/examples/how_to/readme.rst0000644000175000017500000000020012262651567017321 0ustar ahs3ahs3"How To..." examples ==================== This directory contains a set of examples or recipes for common tasks in JenkinsAPI. jenkinsapi-0.2.16/examples/__init__.py0000644000175000017500000000000012262651567016142 0ustar ahs3ahs3jenkinsapi-0.2.16/examples/low_level/0000755000175000017500000000000012262651567016033 5ustar ahs3ahs3jenkinsapi-0.2.16/examples/low_level/copy_a_job.py0000644000175000017500000000123012262651567020505 0ustar ahs3ahs3""" A lower-level implementation of copying a job in Jenkins """ import requests from jenkinsapi.jenkins import Jenkins from pkg_resources import resource_string from jenkinsapi_tests.test_utils.random_strings import random_string J = Jenkins('http://localhost:8080') jobName = random_string() jobName2 = '%s_2' % jobName url = 'http://localhost:8080/createItem?from=%s&name=%s&mode=copy' % (jobName, jobName2) xml = resource_string('examples', 'addjob.xml') j = J.create_job(jobname=jobName, config=xml) h = {'Content-Type': 'application/x-www-form-urlencoded'} response = requests.post(url, data='dysjsjsjs', headers=h) print response.text.encode('UTF-8') jenkinsapi-0.2.16/examples/low_level/create_a_view_low_level.py0000644000175000017500000000105712262651567023255 0ustar ahs3ahs3""" A low level example: This is how JenkinsAPI creates views """ import requests import json url = 'http://localhost:8080/createView' str_view_name = "blahblah123" params = {} # {'name': str_view_name} headers = {'Content-Type': 'application/x-www-form-urlencoded'} data = { "name": str_view_name, "mode": "hudson.model.ListView", "Submit": "OK", "json": json.dumps({"name": str_view_name, "mode": "hudson.model.ListView"}) } # Try 1 result = requests.post(url, params=params, data=data, headers=headers) print result.text.encode('UTF-8') jenkinsapi-0.2.16/examples/low_level/login_with_auth.py0000644000175000017500000000031612262651567021571 0ustar ahs3ahs3""" A lower level example of how we login with authentication """ from jenkinsapi import jenkins J = jenkins.Jenkins("http://localhost:8080", username="sal", password="foobar") J.poll() print J.items() jenkinsapi-0.2.16/examples/low_level/example_param_build.py0000644000175000017500000000100612262651567022374 0ustar ahs3ahs3import json import requests def foo(): """ A low level example of how JenkinsAPI runs a parameterized build """ toJson = {'parameter': [{'name': 'B', 'value': 'xyz'}]} url = 'http://localhost:8080/job/ddd/build' # url = 'http://localhost:8000' headers = {'Content-Type': 'application/x-www-form-urlencoded'} form = {'json': json.dumps(toJson)} response = requests.post(url, data=form, headers=headers) print response.text.encode('UTF-8') if __name__ == '__main__': foo() jenkinsapi-0.2.16/examples/low_level/readme.rst0000644000175000017500000000057312262651567020027 0ustar ahs3ahs3Low-Level Examples ================== These examoples are intended to explain how JenkinsAPI performs certain functions. While developing JenkinsAPI I created a number of small scripts like these in order to figure out the correct way to communicate. Ive retained a number of these as they provide some insights into how the various interfaces that Jenkins provides can be used.jenkinsapi-0.2.16/examples/addjob.xml0000644000175000017500000000101612262651567016006 0ustar ahs3ahs3 false true false false false false jenkinsapi-0.2.16/jenkins_build.sh0000644000175000017500000000074212262651567015404 0ustar ahs3ahs3#! /bin/bash if ! command -v virtualenv >/dev/null 2>&1; then echo "You should install virtualenv, check http://www.virtualenv.org/" exit 1 fi virtualenv . source bin/activate python setup.py develop easy_install nose easy_install mock easy_install requests easy_install coverage test -z "$WORKSPACE" && WORKSPACE="`pwd`" nosetests jenkinsapi_tests --with-xunit --with-coverage --cover-html --cover-html-dir="$WORKSPACE/coverage_report" --cover-package=jenkinsapi --verbose jenkinsapi-0.2.16/build.properties0000644000175000017500000000000512262651567015435 0ustar ahs3ahs3buildjenkinsapi-0.2.16/doc/0000755000175000017500000000000012262651567012772 5ustar ahs3ahs3jenkinsapi-0.2.16/doc/.gitignore0000644000175000017500000000004312262651567014757 0ustar ahs3ahs3/ html /docs_html.zip /html /build jenkinsapi-0.2.16/doc/build.properties0000644000175000017500000000011712262651567016206 0ustar ahs3ahs3docs.zipfile.filename=docs_html.zip docs.html.dir=html docs.source.dir=sourcejenkinsapi-0.2.16/doc/build.xml0000644000175000017500000000235612262651567014621 0ustar ahs3ahs3 jenkinsapi-0.2.16/doc/source/0000755000175000017500000000000012262651567014272 5ustar ahs3ahs3jenkinsapi-0.2.16/doc/source/.gitignore0000644000175000017500000000012012262651567016253 0ustar ahs3ahs3/jenkinsapi.utils.rst /jenkinsapi.command_line.rst /jenkinsapi.rst /modules.rst jenkinsapi-0.2.16/doc/source/api.rst0000644000175000017500000000010212262651567015566 0ustar ahs3ahs3User API ======== .. automodule:: jenkinsapi.api :members:jenkinsapi-0.2.16/doc/source/rules_for_contributors.rst0000644000175000017500000000263512262651567021647 0ustar ahs3ahs3Rules for Contributors ====================== The JenkinsAPI project welcomes contributions via GitHub. Please bear in mind the following guidelines when preparing your pull-request. Python compatibility -------------------- The project currently targets Python 2.6 and Python 2.7. Support for Python 3.x will be introduced soon. Please do not add any features which will break our supported Python 2.x versions or make it harder for us to migrate to Python 3.x Test Driven Development ----------------------- Please do not submit pull requests without tests. That's really important. Our project is all about test-driven development. It would be embarrasing if our project failed because of a lack of tests! You might want to follow a typical test driven development cycle: http://en.wikipedia.org/wiki/Test-driven_development Put simply: Write your tests first and only implement features required to make your tests pass. Do not let your implementation get ahead of your tests. Features implemented without tests will be removed. Unmaintained features (which break because of changes in Jenkins) will also be removed. Check the CI status before comitting ------------------------------------ We have a Travis CI account - please verify that your branch works before making a pull request. Any problems? ------------- If you are stuck on something, please post to the issue tracker. Do not contact the developers directly. jenkinsapi-0.2.16/doc/source/build.rst0000644000175000017500000000007612262651567016126 0ustar ahs3ahs3Build ===== .. automodule:: jenkinsapi.build :members:jenkinsapi-0.2.16/doc/source/_static/0000755000175000017500000000000012262651567015720 5ustar ahs3ahs3jenkinsapi-0.2.16/doc/source/_static/tmp.txt0000644000175000017500000000000012262651567017247 0ustar ahs3ahs3jenkinsapi-0.2.16/doc/source/conf.py0000644000175000017500000001760112262651567015576 0ustar ahs3ahs3# -*- coding: utf-8 -*- # # JenkinsAPI documentation build configuration file, created by # sphinx-quickstart on Mon Jan 09 16:35:17 2012. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import logging import jenkinsapi import pkg_resources dist = pkg_resources.working_set.by_key[jenkinsapi.__name__] VERSION = RELEASE = dist.version if __name__ == "__main__": logging.basicConfig() log = logging.getLogger(__name__) # CHANGE THIS PROJECT_NAME = 'JenkinsAPI' PROJECT_AUTHORS = "Salim Fadhley, Ramon van Alteren, Ruslan Lutsenko" PROJECT_EMAILS = 'salimfadhley@gmail.com, ramon@vanalteren.nl, ruslan.lutcenko@gmail.com' PROJECT_URL = "https://github.com/salimfadhley/jenkinsapi" SHORT_DESCRIPTION = 'A Python API for accessing resources on a Jenkins continuous-integration server.' # CHANGE THIS # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u' JenkinsAPI' copyright = u'2012, %s' % PROJECT_AUTHORS # The version info for the project you're documenting, acts as replacement for # built documents. # # The short X.Y version. version = VERSION # The full version, including alpha/beta/rc tags. release = VERSION # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. show_authors = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'JenkinsAPIdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'JenkinsAPI.tex', u'JenkinsAPI Documentation', u'xxx', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'jenkinsapi', u' JenkinsAPI Documentation', [u'xxx'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'JenkinsAPI', u'JenkinsAPI Documentation', u'xxx', 'JenkinsAPI', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' jenkinsapi-0.2.16/doc/source/_templates/0000755000175000017500000000000012262651567016427 5ustar ahs3ahs3jenkinsapi-0.2.16/doc/source/_templates/tmp.txt0000644000175000017500000000000012262651567017756 0ustar ahs3ahs3jenkinsapi-0.2.16/doc/source/index.rst0000644000175000017500000001312512262651567016135 0ustar ahs3ahs3JenkinsAPI ========== Jenkins is the market leading continuous integration system, originally created by Kohsuke Kawaguchi. This API makes Jenkins even easier to use by providing an easy to use conventional python interface. Jenkins (and It's predecessor Hudson) are fantastic projects - but they are somewhat Java-centric. Thankfully the designers have provided an excellent and complete REST interface. This library wraps up that interface as more conventional python objects in order to make most Jenkins oriented tasks simpler. This library can help you: * Query the test-results of a completed build * Get a objects representing the latest builds of a job * Search for artifacts by simple criteria * Block until jobs are complete * Install artifacts to custom-specified directory structures * Username/password auth support for jenkins instances with auth turned on * Search for builds by subversion revision * Add, remove and query jenkins slaves Sections ======== .. toctree:: :maxdepth: 2 examples Known bugs ---------- [ ] Currently incompatible with Jenkins > 1.518. Job deletion operations fail unless Cross-Site scripting protection is disabled. For other issues, please refer to the support URL below. Important Links --------------- Support & bug-reportst: https://github.com/salimfadhley/jenkinsapi/issues?direction=desc&sort=comments&state=open Project source code: github: https://github.com/salimfadhley/jenkinsapi Project documentation: https://jenkinsapi.readthedocs.org/en/latest/ Releases: http://pypi.python.org/pypi/jenkinsapi Installation ------------- Egg-files for this project are hosted on PyPi. Most Python users should be able to use pip or setuptools to automatically install this project. Most users can do the following: :: pip install jenkinsapi Or.. :: easy_install jenkinsapi Examples -------- JenkinsAPI is intended to map the objects in Jenkins (e.g. Builds, Views, Jobs) into easily managed Python objects:: Python 2.7.4 (default, Apr 19 2013, 18:28:01) [GCC 4.7.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import jenkinsapi >>> from jenkinsapi.jenkins import Jenkins >>> J = Jenkins('http://localhost:8080') >>> J.keys() # Jenkins objects appear to be dict-like, mapping keys (job-names) to ['foo', 'test_jenkinsapi'] >>> J['test_jenkinsapi'] >>> J['test_jenkinsapi'].get_last_good_build() JenkinsAPI lets you query the state of a running Jenkins server. It also allows you to change configuration and automate minor tasks on nodes and jobs. You can use Jenkins to get information about recently completed builds. For example, you can get the revision number of the last succsessful build in order to trigger some kind of release process.:: from jenkinsapi.jenkins import Jenkins def getSCMInfroFromLatestGoodBuild(url, jobName, username=None, password=None): J = Jenkins(url, username, password) job = J[jobName] lgb = job.get_last_good_build() return lgb.get_revision() if __name__ == '__main__': print getSCMInfroFromLatestGoodBuild('http://localhost:8080', 'fooJob') When used with the Git source-control system line 20 will print out something like '8b4f4e6f6d0af609bb77f95d8fb82ff1ee2bba0d' - which looks suspiciously like a Git revision number. Project Authors =============== * Salim Fadhley (sal@stodge.org) * Ramon van Alteren (ramon@vanalteren.nl) * Ruslan Lutsenko (ruslan.lutcenko@gmail.com) Plus many others, please see the README file for a more complete list of contributors and how to contact them. Extending and Improving JenkinsAPI ================================== JenkinsAPI is a pure-python project and can be improved with almost any programmer's text-editor or IDE. I'd recomend the following project layout which has been shown to work with both SublimeText2 and Eclipse/PyDev * Make sure that pip and virtualenv are installed on your computer. On most Linux systems these can be installed directly by the OS package-manager. * Create a new virtualenv for the project:: virtualenv jenkinsapi * Change to the new directory and check out the project code into the **src** subdirectory cd jenkinsapi git clone https://github.com/salimfadhley/jenkinsapi.git src * Activate your jenkinsapi virtual environment:: cd bin source activate * Install the jenkinsapi project in 'developer mode' - this step will automatically download all of the project's dependancies:: cd ../src python setup.py develop * Test the project - this step will automatically download and install the project's test-only dependancies. Having these installed will be helpful during development:: python setup.py test * Set up your IDE/Editor configuration - the **misc** folder contains configuration for Sublime Text 2. I hope in time that other developers will contribute useful configurations for their favourite development tools. Testing ------- The project maintainers welcome any code-contributions. Please conside the following when you contribute code back to the project: * All contrubutions should come as github pull-requests. Please do not send code-snippets in email or as attachments to issues. * Please take a moment to clearly describe the intended goal of your pull-request. * Please ensure that any new feature is covered by a unit-test Package Contents ================ .. toctree:: jenkinsapi jenkinsapi.command_line jenkinsapi.utils modules Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` jenkinsapi-0.2.16/doc/source/artifact.rst0000644000175000017500000000010712262651567016617 0ustar ahs3ahs3Artifact ======== .. automodule:: jenkinsapi.artifact :members:jenkinsapi-0.2.16/doc/source/using_jenkinsapi.rst0000644000175000017500000000225412262651567020367 0ustar ahs3ahs3Using Jenkins API ================= JenkinsAPI lets you query the state of a running Jenkins server. It also allows you to change configuration and automate minor tasks on nodes and jobs. Example 1: Getting version information from a completed build ------------------------------------------------------------- This is a typical use of JenkinsAPI - it was the very first use I had in mind when the project was first built: In a continuous-integration environment you want to be able to programatically detect the version-control information of the last succsessful build in order to trigger some kind of release process.:: from jenkinsapi.jenkins import Jenkins def getSCMInfroFromLatestGoodBuild(url, jobName, username=None, password=None): J = Jenkins(url, username, password) job = J[jobName] lgb = job.get_last_good_build() return lgb.get_revision() if __name__ == '__main__': print getSCMInfroFromLatestGoodBuild('http://localhost:8080', 'fooJob') When used with the Git source-control system line 20 will print out something like '8b4f4e6f6d0af609bb77f95d8fb82ff1ee2bba0d' - which looks suspiciously like a Git revision number. jenkinsapi-0.2.16/license.txt0000644000175000017500000000221212262651567014405 0ustar ahs3ahs3Copyright (c) 2012 Salim Fadhley For additional contributors please see the README.rst file The MIT License (MIT) ===================== Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jenkinsapi-0.2.16/README.rst0000644000175000017500000001234312262651567013717 0ustar ahs3ahs3jenkinsapi ========== .. image:: https://badge.fury.io/py/jenkinsapi.png :target: http://badge.fury.io/py/jenkinsapi .. image:: https://travis-ci.org/salimfadhley/jenkinsapi.png?branch=master :target: https://travis-ci.org/salimfadhley/jenkinsapi .. image:: https://pypip.in/d/jenkinsapi/badge.png :target: https://crate.io/packages/jenkinsapi/ About this library ------------------- Jenkins is the market leading continuous integration system, originally created by Kohsuke Kawaguchi. Jenkins (and It's predecessor Hudson) are useful projects for automating common development tasks (e.g. unit-testing, production batches) - but they are somewhat Java-centric. Thankfully the designers have provided an excellent and complete REST interface. This library wraps up that interface as more conventional python objects in order to make many Jenkins oriented tasks easier to automate. This library can help you: * Query the test-results of a completed build * Get a objects representing the latest builds of a job * Search for artefacts by simple criteria * Block until jobs are complete * Install artefacts to custom-specified directory structures * username/password auth support for jenkins instances with auth turned on * Ability to search for builds by subversion revision * Ability to add/remove/query Jenkins slaves * Ability to add/remove/modify Jenkins views Known bugs ---------- [ ] Currently incompatible with Jenkins > 1.518. Job deletion operations fail unless Cross-Site scripting protection is disabled. For other issues, please refer to the support URL below. Important Links --------------- Support & bug-reportst: https://github.com/salimfadhley/jenkinsapi/issues?direction=desc&sort=comments&state=open Project source code: github: https://github.com/salimfadhley/jenkinsapi Project documentation: https://jenkinsapi.readthedocs.org/en/latest/ Releases: http://pypi.python.org/pypi/jenkinsapi Installation ------------- Egg-files for this project are hosted on PyPi. Most Python users should be able to use pip or setuptools to automatically install this project. Using Pip or Setuptools ^^^^^^^^^^^^^^^^^^^^^^^ Most users can do the following: .. code-block:: bash pip install jenkinsapi Or: .. code-block:: bash easy_install jenkinsapi Both of these techniques can be combined with virtualenv to create an application-specific installation. Using your operating-system's package manager ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ubuntu users can now use apt to install this package: .. code-block:: bash apt-get install python-jenkinsapi Beware that this technique will get a somewhat older version of Jenkinsapi. Example ------- JenkinsAPI is intended to map the objects in Jenkins (e.g. Builds, Views, Jobs) into easily managed Python objects: .. code-block:: pycon >>> import jenkinsapi >>> from jenkinsapi.jenkins import Jenkins >>> J = Jenkins('http://localhost:8080') >>> J.version 1.542 >>> J.keys() # Jenkins objects appear to be dict-like, mapping keys (job-names) to ['foo', 'test_jenkinsapi'] >>> J['test_jenkinsapi'] >>> J['test_jenkinsapi'].get_last_good_build() ... Testing ------- If you have installed the test dependencies on your system already, you can run the testsuite with the following command: .. code-block:: bash python setup.py test Otherwise using a virtualenv is recommended. Setuptools will automatically fetch missing test dependencies: .. code-block:: bash virtualenv source .venv/bin/active (venv) python setup.py test Project Contributors -------------------- * Salim Fadhley (sal@stodge.org) * Ramon van Alteren (ramon@vanalteren.nl) * Ruslan Lutsenko (ruslan.lutcenko@gmail.com) * Cleber J Santos (cleber@simplesconsultoria.com.br) * William Zhang (jollychang@douban.com) * Victor Garcia (bravejolie@gmail.com) * Bradley Harris (bradley@ninelb.com) * Aleksey Maksimov (ctpeko3a@gmail.com) * Kyle Rockman (kyle.rockman@mac.com) * Sascha Peilicke (saschpe@gmx.de) * David Johansen (david@makewhat.is) Please do not contact these contributors directly for support questions! Use the GitHub tracker instead. License -------- The MIT License (MIT): Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jenkinsapi-0.2.16/jenkinsapi/0000755000175000017500000000000012262651567014360 5ustar ahs3ahs3jenkinsapi-0.2.16/jenkinsapi/result.py0000644000175000017500000000115112262651567016246 0ustar ahs3ahs3""" Module for jenkinsapi Result """ class Result(object): """ Result class """ def __init__(self, **kwargs): self.__dict__.update(kwargs) def __str__(self): return "%s %s %s" % (self.className, self.name, self.status) def __repr__(self): module_name = self.__class__.__module__ class_name = self.__class__.__name__ self_str = str(self) return "<%s.%s %s>" % (module_name, class_name, self_str) def identifier(self): """ Calculate an ID for this object. """ return "%s.%s" % (self.className, self.name) jenkinsapi-0.2.16/jenkinsapi/config.py0000644000175000017500000000011712262651567016176 0ustar ahs3ahs3""" Jenkins configuration """ JENKINS_API = r"api/python" LOAD_TIMEOUT = 30 jenkinsapi-0.2.16/jenkinsapi/.gitignore0000644000175000017500000000001512262651567016344 0ustar ahs3ahs3/__pycache__ jenkinsapi-0.2.16/jenkinsapi/api.py0000644000175000017500000001754112262651567015513 0ustar ahs3ahs3""" This module is a collection of helpful, high-level functions for automating common tasks. Many of these functions were designed to be exposed to the command-line, hence they have simple string arguments. """ import os import time import logging from urllib2 import urlparse from jenkinsapi import constants from jenkinsapi.jenkins import Jenkins from jenkinsapi.artifact import Artifact from jenkinsapi.custom_exceptions import ArtifactsMissing, TimeOut, BadURL log = logging.getLogger(__name__) def get_latest_test_results(jenkinsurl, jobname, username=None, password=None): """ A convenience function to fetch down the very latest test results from a jenkins job. """ latestbuild = get_latest_build(jenkinsurl, jobname, username=username, password=password) res = latestbuild.get_resultset() return res def get_latest_build(jenkinsurl, jobname, username=None, password=None): """ A convenience function to fetch down the very latest test results from a jenkins job. """ jenkinsci = Jenkins(jenkinsurl, username=username, password=password) job = jenkinsci[jobname] return job.get_last_build() def get_latest_complete_build(jenkinsurl, jobname, username=None, password=None): """ A convenience function to fetch down the very latest test results from a jenkins job. """ jenkinsci = Jenkins(jenkinsurl, username=username, password=password) job = jenkinsci[jobname] return job.get_last_completed_build() def get_build(jenkinsurl, jobname, build_no, username=None, password=None): """ A convenience function to fetch down the test results from a jenkins job by build number. """ jenkinsci = Jenkins(jenkinsurl, username=username, password=password) job = jenkinsci[jobname] return job.get_build(build_no) def get_artifacts(jenkinsurl, jobid=None, build_no=None, username=None, password=None): """ Find all the artifacts for the latest build of a job. """ jenkinsci = Jenkins(jenkinsurl, username=username, password=password) job = jenkinsci[jobid] if build_no: build = job.get_build(build_no) else: build = job.get_last_good_build() artifacts = build.get_artifact_dict() log.info(msg="Found %i artifacts in '%s'" % (len(artifacts.keys()), build_no)) return artifacts def search_artifacts(jenkinsurl, jobid, artifact_ids=None, username=None, password=None): """ Search the entire history of a jenkins job for a list of artifact names. If same_build is true then ensure that all artifacts come from the same build of the job """ if len(artifact_ids) == 0 or artifact_ids is None: return [] jenkinsci = Jenkins(jenkinsurl, username=username, password=password) job = jenkinsci[jobid] build_ids = job.get_build_ids() for build_id in build_ids: build = job.get_build(build_id) artifacts = build.get_artifact_dict() if set(artifact_ids).issubset(set(artifacts.keys())): return dict((a, artifacts[a]) for a in artifact_ids) missing_artifacts = set(artifact_ids) - set(artifacts.keys()) log.debug(msg="Artifacts %s missing from %s #%i" % (", ".join(missing_artifacts), jobid, build_id)) #noinspection PyUnboundLocalVariable raise ArtifactsMissing(missing_artifacts) def grab_artifact(jenkinsurl, jobid, artifactid, targetdir, username=None, password=None): """ Convenience method to find the latest good version of an artifact and save it to a target directory. Directory is made automatically if not exists. """ artifacts = get_artifacts(jenkinsurl, jobid, username=username, password=password) artifact = artifacts[artifactid] if not os.path.exists(targetdir): os.makedirs(targetdir) artifact.save_to_dir(targetdir) def block_until_complete(jenkinsurl, jobs, maxwait=12000, interval=30, raise_on_timeout=True, username=None, password=None): """ Wait until all of the jobs in the list are complete. """ assert maxwait > 0 assert maxwait > interval assert interval > 0 obj_jenkins = Jenkins(jenkinsurl, username=username, password=password) obj_jobs = [obj_jenkins[jid] for jid in jobs] for time_left in xrange(maxwait, 0, -interval): still_running = [j for j in obj_jobs if j.is_queued_or_running()] if not still_running: return str_still_running = ", ".join('"%s"' % str(a) for a in still_running) log.warn(msg="Waiting for jobs %s to complete. Will wait another %is" % (str_still_running, time_left)) time.sleep(interval) if raise_on_timeout: #noinspection PyUnboundLocalVariable raise TimeOut("Waited too long for these jobs to complete: %s" % str_still_running) def get_view_from_url(url, username=None, password=None): """ Factory method """ matched = constants.RE_SPLIT_VIEW_URL.search(url) if not matched: raise BadURL("Cannot parse URL %s" % url) jenkinsurl, view_name = matched.groups() jenkinsci = Jenkins(jenkinsurl, username=username, password=password) return jenkinsci.views[view_name] def get_nested_view_from_url(url, username=None, password=None): """ Returns View based on provided URL. Convenient for nested views. """ matched = constants.RE_SPLIT_VIEW_URL.search(url) if not matched: raise BadURL("Cannot parse URL %s" % url) jenkinsci = Jenkins(matched.group(0), username=username, password=password) return jenkinsci.get_view_by_url(url) def install_artifacts(artifacts, dirstruct, installdir, basestaticurl): """ Install the artifacts. """ assert basestaticurl.endswith("/"), "Basestaticurl should end with /" installed = [] for reldir, artifactnames in dirstruct.items(): destdir = os.path.join(installdir, reldir) if not os.path.exists(destdir): log.warn(msg="Making install directory %s" % destdir) os.makedirs(destdir) else: assert os.path.isdir(destdir) for artifactname in artifactnames: destpath = os.path.abspath(os.path.join(destdir, artifactname)) if artifactname in artifacts.keys(): # The artifact must be loaded from jenkins theartifact = artifacts[artifactname] else: # It's probably a static file, # we can get it from the static collection staticurl = urlparse.urljoin(basestaticurl, artifactname) theartifact = Artifact(artifactname, staticurl) theartifact.save(destpath) installed.append(destpath) return installed def search_artifact_by_regexp(jenkinsurl, jobid, artifactRegExp, username=None, password=None): ''' Search the entire history of a hudson job for a build which has an artifact whose name matches a supplied regular expression. Return only that artifact. @param jenkinsurl: The base URL of the jenkins server @param jobid: The name of the job we are to search through @param artifactRegExp: A compiled regular expression object (not a re-string) @param username: Jenkins login user name, optional @param password: Jenkins login password, optional ''' job = Jenkins(jenkinsurl, username=username, password=password) j = job[jobid] build_ids = j.get_build_ids() for build_id in build_ids: build = j.get_build(build_id) artifacts = build.get_artifact_dict() for name, art in artifacts.iteritems(): md_match = artifactRegExp.search(name) if md_match: return art raise ArtifactsMissing() jenkinsapi-0.2.16/jenkinsapi/artifact.py0000644000175000017500000000763712262651567016544 0ustar ahs3ahs3""" Artifacts can be used to represent data created as a side-effect of running a Jenkins build. Artifacts are files which are associated with a single build. A build can have any number of artifacts associated with it. This module provides a class called Artifact which allows you to download objects from the server and also access them as a stream. """ import os import logging import hashlib from jenkinsapi.fingerprint import Fingerprint from jenkinsapi.custom_exceptions import ArtifactBroken log = logging.getLogger(__name__) class Artifact(object): """ Represents a single Jenkins artifact, usually some kind of file generated as a by-product of executing a Jenkins build. """ def __init__(self, filename, url, build): self.filename = filename self.url = url self.build = build def save(self, fspath): """ Save the artifact to an explicit path. The containing directory must exist. Returns a reference to the file which has just been writen to. :param fspath: full pathname including the filename, str :return: filepath """ log.info(msg="Saving artifact @ %s to %s" % (self.url, fspath)) if not fspath.endswith(self.filename): log.warn(msg="Attempt to change the filename of artifact %s on save." % self.filename) if os.path.exists(fspath): if self.build: try: if self._verify_download(fspath): log.info(msg="Local copy of %s is already up to date." % self.filename) return fspath except ArtifactBroken: log.info("Jenkins artifact could not be identified.") else: log.info("This file did not originate from Jenkins, so cannot check.") else: log.info("Local file is missing, downloading new.") filepath = self._do_download(fspath) try: self._verify_download(filepath) except ArtifactBroken: log.warning("fingerprint of the downloaded artifact could not be verified") return fspath def get_jenkins_obj(self): return self.build.get_jenkins_obj() def get_data(self): """ Grab the text of the artifact """ response = self.get_jenkins_obj().requester.get_and_confirm_status(self.url) return response.content def _do_download(self, fspath): """ Download the the artifact to a path. """ with open(fspath, "wb") as out: out.write(self.get_data()) return fspath def _verify_download(self, fspath): """ Verify that a downloaded object has a valid fingerprint. """ local_md5 = self._md5sum(fspath) fp = Fingerprint(self.build.job.jenkins.baseurl, local_md5, self.build.job.jenkins) return fp.validate_for_build(os.path.basename(fspath), self.build.job.name, self.build.buildno) def _md5sum(self, fspath, chunksize=2 ** 20): """ A MD5 hashing function intended to produce the same results as that used by Jenkins. """ md5 = hashlib.md5() try: with open(fspath, 'rb') as f: for chunk in iter(lambda: f.read(chunksize), ''): md5.update(chunk) except: raise return md5.hexdigest() def save_to_dir(self, dirpath): """ Save the artifact to a folder. The containing directory must be exist, but use the artifact's default filename. """ assert os.path.exists(dirpath) assert os.path.isdir(dirpath) outputfilepath = os.path.join(dirpath, self.filename) return self.save(outputfilepath) def __repr__(self): """ Produce a handy repr-string. """ return """<%s.%s %s>""" % (self.__class__.__module__, self.__class__.__name__, self.url) jenkinsapi-0.2.16/jenkinsapi/utils/0000755000175000017500000000000012262651567015520 5ustar ahs3ahs3jenkinsapi-0.2.16/jenkinsapi/utils/__init__.py0000644000175000017500000000004212262651567017625 0ustar ahs3ahs3""" Module __init__ for utils """ jenkinsapi-0.2.16/jenkinsapi/utils/krb_requester.py0000644000175000017500000000247312262651567020755 0ustar ahs3ahs3""" Kerberos aware Requester """ from jenkinsapi.utils.requester import Requester from requests_kerberos import HTTPKerberosAuth, OPTIONAL #pylint: disable=W0222 class KrbRequester(Requester): """ A class which carries out HTTP requests with Kerberos/GSSAPI authentication. """ def __init__(self, ssl_verify=None, baseurl=None, mutual_auth=OPTIONAL): """ :param ssl_verify: flag indicating if server certificate in HTTPS requests should be verified :param baseurl: Jenkins' base URL :param mutual_auth: type of mutual authentication, use one of REQUIRED, OPTIONAL or DISABLED from requests_kerberos package """ args = {} if ssl_verify: args["ssl_verify"] = ssl_verify if baseurl: args["baseurl"] = baseurl super(KrbRequester, self).__init__(**args) self.mutual_auth = mutual_auth def get_request_dict(self, url, params, data, headers): req_dict = Requester( self, url=url, params=params, data=data, headers=headers ) if self.mutual_auth: auth = HTTPKerberosAuth(self.mutual_auth) else: auth = HTTPKerberosAuth() req_dict['auth'] = auth return req_dict jenkinsapi-0.2.16/jenkinsapi/utils/requester.py0000644000175000017500000001135612262651567020117 0ustar ahs3ahs3""" Module for jenkinsapi requester (which is a wrapper around python-requests) """ import requests import urlparse from jenkinsapi.custom_exceptions import JenkinsAPIException # import logging # # these two lines enable debugging at httplib level (requests->urllib3->httplib) # # you will see the REQUEST, including HEADERS and DATA, and RESPONSE with HEADERS but without DATA. # # the only thing missing will be the response.body which is not logged. # import httplib # httplib.HTTPConnection.debuglevel = 1 # logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests # logging.getLogger().setLevel(logging.DEBUG) # requests_log = logging.getLogger("requests.packages.urllib3") # requests_log.setLevel(logging.DEBUG) # requests_log.propagate = True class Requester(object): """ A class which carries out HTTP requests. You can replace this class with one of your own implementation if you require some other way to access Jenkins. This default class can handle simple authentication only. """ VALID_STATUS_CODES = [200, ] def __init__(self, username=None, password=None, ssl_verify=True, baseurl=None): if username: assert password, 'Cannot set a username without a password!' self.base_scheme = urlparse.urlsplit(baseurl).scheme if baseurl else None self.username = username self.password = password self.ssl_verify = ssl_verify def get_request_dict(self, params=None, data=None, files=None, headers=None): requestKwargs = {} if self.username: requestKwargs['auth'] = (self.username, self.password) if params: assert isinstance( params, dict), 'Params must be a dict, got %s' % repr(params) requestKwargs['params'] = params if headers: assert isinstance( headers, dict), 'headers must be a dict, got %s' % repr(headers) requestKwargs['headers'] = headers requestKwargs['verify'] = self.ssl_verify if not data is None: # It may seem odd, but some Jenkins operations require posting # an empty string. requestKwargs['data'] = data if files: requestKwargs['files'] = files return requestKwargs def _update_url_scheme(self, url): """ Updates scheme of given url to the one used in Jenkins baseurl. """ if self.base_scheme and not url.startswith("%s://" % self.base_scheme): url_split = urlparse.urlsplit(url) url = urlparse.urlunsplit( [ self.base_scheme, url_split.netloc, url_split.path, url_split.query, url_split.fragment ] ) return url def get_url(self, url, params=None, headers=None): requestKwargs = self.get_request_dict(params=params, headers=headers) return requests.get(self._update_url_scheme(url), **requestKwargs) def post_url(self, url, params=None, data=None, files=None, headers=None): requestKwargs = self.get_request_dict(params=params, data=data, files=files, headers=headers) return requests.post(self._update_url_scheme(url), **requestKwargs) def post_xml_and_confirm_status(self, url, params=None, data=None, valid=None): headers = {'Content-Type': 'text/xml'} return self.post_and_confirm_status(url, params=params, data=data, headers=headers, valid=valid) def post_and_confirm_status(self, url, params=None, data=None, files=None, headers=None, valid=None): valid = valid or self.VALID_STATUS_CODES assert isinstance(data, ( str, dict)), \ "Unexpected type of parameter 'data': %s. Expected (str, dict)" % type(data) if not headers and not files: headers = {'Content-Type': 'application/x-www-form-urlencoded'} response = self.post_url(url, params, data, files, headers) if not response.status_code in valid: raise JenkinsAPIException('Operation failed. url={0}, data={1}, headers={2}, status={3}, text={4}'.format( response.url, data, headers, response.status_code, response.text.encode('UTF-8'))) return response def get_and_confirm_status(self, url, params=None, headers=None, valid=None): valid = valid or self.VALID_STATUS_CODES response = self.get_url(url, params, headers) if not response.status_code in valid: raise JenkinsAPIException('Operation failed. url={0}, headers={1}, status={2}, text={3}'.format( response.url, headers, response.status_code, response.text.encode('UTF-8'))) return response jenkinsapi-0.2.16/jenkinsapi/invocation.py0000644000175000017500000000756212262651567017115 0ustar ahs3ahs3""" Module for Jenkinsapi Invocation object """ import time import datetime from jenkinsapi.custom_exceptions import UnknownQueueItem, TimeOut class Invocation(object): """ Represents the state and consequences of a single attempt to start a job. This class provides a context manager which is intended to watch the state of the job before and after the invoke. It will detect whether a process got queued, launched or whether nothing at all happened. An instance of this object will be returned by job.invoke() """ def __init__(self, job): self.job = job self.initial_builds = None self.queue_item = None self.build_number = None def __enter__(self): """ Start watching the job """ self.job.poll() self.initial_builds = set(self.job.get_build_dict().keys()) def __exit__(self, type_, value, traceback): """ Finish watching the job - it will track which new queue items or builds have been created as a consequence of invoking the job. """ self.job.poll() newly_created_builds = set(self.job.get_build_dict().keys()) if newly_created_builds: self.build_number = newly_created_builds.pop() else: try: self.queue_item = self.job.get_queue_item() self.build_number = self.job.get_next_build_number() except UnknownQueueItem: pass def get_build_number(self): """ If this job is building or complete then provide it's build-number """ self.job.poll() self.build_number = self.job.get_last_buildnumber() return self.build_number def get_build(self): return self.job[self.get_build_number()] def block_until_not_queued(self, timeout, delay): # self.__block(lambda: self.is_queued(), False, timeout, delay) self.__block(self.is_queued, False, timeout, delay) def block_until_completed(self, timeout, delay): # self.__block(lambda: self.is_running(), False, timeout, delay) self.__block(self.is_running, False, timeout, delay) @staticmethod def __block(fn, expectation, timeout, delay=2): startTime = datetime.datetime.now() endTime = startTime + datetime.timedelta(seconds=timeout) while True: if fn() == expectation: break else: time.sleep(delay) if datetime.datetime.now() > endTime: raise TimeOut() def block(self, until='completed', timeout=200, delay=2): """ Block this item until a condition is met. Setting until to 'running' blocks the item until it is running (i.e. it's no longer queued) """ assert until in ['completed', 'not_queued'], 'Unknown block condition: %s' % until self.block_until_not_queued(timeout, delay) if until == 'completed': self.block_until_completed(timeout, delay) def stop(self): """ Stop this item, whether it is on the queue or blocked. """ self.get_build().stop() def is_queued(self): """ Returns True if this item is on the queue """ return self.job.is_queued() def is_running(self): """ Returns True if this item is executing now Returns False if this item has completed or has not yet executed. """ try: return self.get_build().is_running() except KeyError: # This item has not yet executed return False def is_queued_or_running(self): return self.is_queued() or self.is_running() def get_queue_item(self): """ If the item is queued it will return that QueueItem, otherwise it will raise an exception. """ return self.queue_item jenkinsapi-0.2.16/jenkinsapi/job.py0000644000175000017500000005153112262651567015511 0ustar ahs3ahs3""" Module for jenkinsapi Job """ import json import logging import urlparse import xml.etree.ElementTree as ET from collections import defaultdict from time import sleep from jenkinsapi.build import Build from jenkinsapi.invocation import Invocation from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.queue import QueueItem from jenkinsapi.mutable_jenkins_thing import MutableJenkinsThing from jenkinsapi.custom_exceptions import ( NoBuildData, NotConfiguredSCM, NotFound, NotInQueue, NotSupportSCM, WillNotBuild, UnknownQueueItem, ) SVN_URL = './scm/locations/hudson.scm.SubversionSCM_-ModuleLocation/remote' GIT_URL = './scm/userRemoteConfigs/hudson.plugins.git.UserRemoteConfig/url' HG_URL = './scm/source' GIT_BRANCH = './scm/branches/hudson.plugins.git.BranchSpec/name' HG_BRANCH = './scm/branch' log = logging.getLogger(__name__) class Job(JenkinsBase, MutableJenkinsThing): """ Represents a jenkins job A job can hold N builds which are the actual execution environments """ def __init__(self, url, name, jenkins_obj): self.name = name self.jenkins = jenkins_obj self._revmap = None self._config = None self._element_tree = None self._scm_map = { 'hudson.scm.SubversionSCM': 'svn', 'hudson.plugins.git.GitSCM': 'git', 'hudson.plugins.mercurial.MercurialSCM': 'hg', 'hudson.scm.NullSCM': 'NullSCM' } self._scmurlmap = { 'svn': lambda element_tree: list(element_tree.findall(SVN_URL)), 'git': lambda element_tree: list(element_tree.findall(GIT_URL)), 'hg': lambda element_tree: list(element_tree.find(HG_URL)), None: lambda element_tree: [] } self._scmbranchmap = { 'svn': lambda element_tree: [], 'git': lambda element_tree: list(element_tree.findall(GIT_BRANCH)), 'hg': lambda element_tree: list(element_tree.find(HG_BRANCH)), None: lambda element_tree: [] } JenkinsBase.__init__(self, url) def __str__(self): return self._data["name"] def get_description(self): return self._data["description"] def get_jenkins_obj(self): return self.jenkins def _poll(self): data = JenkinsBase._poll(self) # jenkins loads only the first 100 builds, load more if needed data = self._add_missing_builds(data) return data # pylint: disable=E1123 # Unexpected keyword arg 'params' def _add_missing_builds(self, data): '''Query Jenkins to get all builds of the job in the data object. Jenkins API loads the first 100 builds and thus may not contain all builds information. This method checks if all builds are loaded in the data object and updates it with the missing builds if needed.''' if not (data.get("builds") and data.get("firstBuild")): return data # do not call _buildid_for_type here: it would poll and do an infinite loop oldest_loaded_build_number = data["builds"][-1]["number"] first_build_number = data["firstBuild"]["number"] all_builds_loaded = (oldest_loaded_build_number == first_build_number) if all_builds_loaded: return data api_url = self.python_api_url(self.baseurl) response = self.get_data(api_url, params={'tree': 'allBuilds[number,url]'}) data['builds'] = response['allBuilds'] return data def _get_config_element_tree(self): """ The ElementTree objects creation is unnecessary, it can be a singleton per job """ if self._config is None: self.load_config() if self._element_tree is None: self._element_tree = ET.fromstring(self._config) return self._element_tree def get_build_triggerurl(self, build_params=None, files=None): if build_params or files: return "%s/buildWithParameters" % self.baseurl return "%s/build" % self.baseurl @staticmethod def _mk_json_from_build_parameters(build_params): """ Build parameters must be submitted in a particular format - Key-Value pairs would be far too simple, no no! Watch and read on and behold! """ assert isinstance( build_params, dict), 'Build parameters must be a dict' return {'parameter': [ {'name': k, 'value': v} for k, v in build_params.iteritems() ]} @staticmethod def mk_json_from_build_parameters(build_params): to_json_structure = Job._mk_json_from_build_parameters(build_params) return json.dumps(to_json_structure) def invoke(self, securitytoken=None, block=False, skip_if_running=False, invoke_pre_check_delay=3, invoke_block_delay=15, build_params=None, cause=None, files=None): assert isinstance(invoke_pre_check_delay, (int, float)) assert isinstance(invoke_block_delay, (int, float)) assert isinstance(block, bool) assert isinstance(skip_if_running, bool) # Create a new invocation instance invocation = Invocation(self) # Either copy the params dict or make a new one. build_params = build_params and dict( build_params.items()) or {} # Via POSTed JSON params = {} # Via Get string with invocation: if len(self.get_params_list()) == 0: if self.is_queued(): raise WillNotBuild('%s is already queued' % repr(self)) elif self.is_running(): if skip_if_running: log.warn( "Will not request new build because %s is already running", self.name) else: log.warn( "Will re-schedule %s even though it is already running", self.name) elif self.has_queued_build(build_params): msg = 'A build with these parameters is already queued.' raise WillNotBuild(msg) log.info("Attempting to start %s on %s", self.name, repr( self.get_jenkins_obj())) url = self.get_build_triggerurl(build_params, files) if cause: build_params['cause'] = cause if securitytoken: params['token'] = securitytoken data = build_params response = self.jenkins.requester.post_and_confirm_status( url, data=data, params=params, files=files, valid=[200, 201] ) response = response if invoke_pre_check_delay > 0: log.info( "Waiting for %is to allow Jenkins to catch up", invoke_pre_check_delay) sleep(invoke_pre_check_delay) if block: total_wait = 0 while self.is_queued(): log.info( "Waited %is for %s to begin...", total_wait, self.name) sleep(invoke_block_delay) total_wait += invoke_block_delay if self.is_running(): running_build = self.get_last_build() running_build.block_until_complete( delay=invoke_pre_check_delay) return invocation def _buildid_for_type(self, buildtype): """Gets a buildid for a given type of build""" self.poll() KNOWNBUILDTYPES = [ "lastStableBuild", "lastSuccessfulBuild", "lastBuild", "lastCompletedBuild", "firstBuild", "lastFailedBuild"] assert buildtype in KNOWNBUILDTYPES, 'Unknown build info type: %s' % buildtype if not self._data.get(buildtype): raise NoBuildData(buildtype) return self._data[buildtype]["number"] def get_first_buildnumber(self): """ Get the numerical ID of the first build. """ return self._buildid_for_type("firstBuild") def get_last_stable_buildnumber(self): """ Get the numerical ID of the last stable build. """ return self._buildid_for_type("lastStableBuild") def get_last_good_buildnumber(self): """ Get the numerical ID of the last good build. """ return self._buildid_for_type("lastSuccessfulBuild") def get_last_failed_buildnumber(self): """ Get the numerical ID of the last good build. """ return self._buildid_for_type(buildtype="lastFailedBuild") def get_last_buildnumber(self): """ Get the numerical ID of the last build. """ return self._buildid_for_type("lastBuild") def get_last_completed_buildnumber(self): """ Get the numerical ID of the last complete build. """ return self._buildid_for_type("lastCompletedBuild") def get_build_dict(self): if "builds" not in self._data: raise NoBuildData(repr(self)) builds = self._data["builds"] last_build = self._data['lastBuild'] if builds and last_build and builds[0]['number'] != last_build['number']: builds = [last_build] + builds # FIXME SO how is this supposed to work if build is false-y? # I don't think that builds *can* be false here, so I don't # understand the test above. return dict((build["number"], build["url"]) for build in builds) def get_revision_dict(self): """ Get dictionary of all revisions with a list of buildnumbers (int) that used that particular revision """ revs = defaultdict(list) if 'builds' not in self._data: raise NoBuildData(repr(self)) for buildnumber in self.get_build_ids(): revs[self.get_build( buildnumber).get_revision()].append(buildnumber) return revs def get_build_ids(self): """ Return a sorted list of all good builds as ints. """ return reversed(sorted(self.get_build_dict().keys())) def get_next_build_number(self): """ Return the next build number that Jenkins will assign. """ return self._data.get('nextBuildNumber', 0) def get_last_stable_build(self): """ Get the last stable build """ bn = self.get_last_stable_buildnumber() return self.get_build(bn) def get_last_good_build(self): """ Get the last good build """ bn = self.get_last_good_buildnumber() return self.get_build(bn) def get_last_build(self): """ Get the last build """ bn = self.get_last_buildnumber() return self.get_build(bn) def get_first_build(self): bn = self.get_first_buildnumber() return self.get_build(bn) def get_last_build_or_none(self): """ Get the last build or None if there is no builds """ try: return self.get_last_build() except NoBuildData: return None def get_last_completed_build(self): """ Get the last build regardless of status """ bn = self.get_last_completed_buildnumber() return self.get_build(bn) def get_buildnumber_for_revision(self, revision, refresh=False): """ :param revision: subversion revision to look for, int :param refresh: boolean, whether or not to refresh the revision -> buildnumber map :return: list of buildnumbers, [int] """ if self.get_scm_type() == 'svn' and not isinstance(revision, int): revision = int(revision) if self._revmap is None or refresh: self._revmap = self.get_revision_dict() try: return self._revmap[revision] except KeyError: raise NotFound("Couldn't find a build with that revision") def get_build(self, buildnumber): assert type(buildnumber) == int url = self.get_build_dict()[buildnumber] return Build(url, buildnumber, job=self) def __getitem__(self, buildnumber): return self.get_build(buildnumber) def __len__(self): return len(self.get_build_dict()) def is_queued_or_running(self): return self.is_queued() or self.is_running() def is_queued(self): self.poll() return self._data["inQueue"] def get_queue_item(self): """ Return a QueueItem if this object is in a queue, otherwise raise an exception """ if not self.is_queued(): raise UnknownQueueItem() return QueueItem(self.jenkins, **self._data['queueItem']) def is_running(self): self.poll() try: build = self.get_last_build_or_none() if build is not None: return build.is_running() except NoBuildData: log.info( "No build info available for %s, assuming not running.", str(self)) return False def get_config(self): '''Returns the config.xml from the job''' response = self.jenkins.requester.get_and_confirm_status( "%(baseurl)s/config.xml" % self.__dict__) return response.text def load_config(self): self._config = self.get_config() def get_scm_type(self): element_tree = self._get_config_element_tree() scm_class = element_tree.find('scm').get('class') scm = self._scm_map.get(scm_class) if not scm: raise NotSupportSCM( "SCM class \"%s\" not supported by API, job \"%s\"" % (scm_class, self.name)) if scm == 'NullSCM': raise NotConfiguredSCM( "SCM does not configured, job \"%s\"" % self.name) return scm def get_scm_url(self): """ Get list of project SCM urls For some SCM's jenkins allow to configure and use number of SCM url's : return: list of SCM urls """ element_tree = self._get_config_element_tree() scm = self.get_scm_type() scm_url_list = [scm_url.text for scm_url in self._scmurlmap[ scm](element_tree)] return scm_url_list def get_scm_branch(self): """ Get list of SCM branches : return: list of SCM branches """ element_tree = self._get_config_element_tree() scm = self.get_scm_type() return [scm_branch.text for scm_branch in self._scmbranchmap[scm](element_tree)] def modify_scm_branch(self, new_branch, old_branch=None): """ Modify SCM ("Source Code Management") branch name for configured job. :param new_branch : new repository branch name to set. If job has multiple branches configured and "old_branch" not provided - method will allways modify first url. :param old_branch (optional): exact value of branch name to be replaced. For some SCM's jenkins allow set multiple branches per job this parameter intended to indicate which branch need to be modified """ element_tree = self._get_config_element_tree() scm = self.get_scm_type() scm_branch_list = self._scmbranchmap[scm](element_tree) if scm_branch_list and not old_branch: scm_branch_list[0].text = new_branch self.update_config(ET.tostring(element_tree)) else: for scm_branch in scm_branch_list: if scm_branch.text == old_branch: scm_branch.text = new_branch self.update_config(ET.tostring(element_tree)) def modify_scm_url(self, new_source_url, old_source_url=None): """ Modify SCM ("Source Code Management") url for configured job. :param new_source_url : new repository url to set. If job has multiple repositories configured and "old_source_url" not provided - method will allways modify first url. :param old_source_url (optional): for some SCM's jenkins allow set multiple repositories per job this parameter intended to indicate which repository need to be modified """ element_tree = self._get_config_element_tree() scm = self.get_scm_type() scm_url_list = self._scmurlmap[scm](element_tree) if scm_url_list and not old_source_url: scm_url_list[0].text = new_source_url self.update_config(ET.tostring(element_tree)) else: for scm_url in scm_url_list: if scm_url.text == old_source_url: scm_url.text = new_source_url self.update_config(ET.tostring(element_tree)) def get_config_xml_url(self): return '%s/config.xml' % self.baseurl def update_config(self, config): """ Update the config.xml to the job Also refresh the ElementTree object since the config has changed """ url = self.get_config_xml_url() if isinstance(config, unicode): config = str(config) response = self.jenkins.requester.post_url(url, params={}, data=config) self._element_tree = ET.fromstring(config) return response.text def get_downstream_jobs(self): """ Get all the possible downstream jobs :return List of Job """ downstream_jobs = [] try: for j in self._data['downstreamProjects']: downstream_jobs.append( self.get_jenkins_obj().get_job(j['name'])) except KeyError: return [] return downstream_jobs def get_downstream_job_names(self): """ Get all the possible downstream job names :return List of String """ downstream_jobs = [] try: for j in self._data['downstreamProjects']: downstream_jobs.append(j['name']) except KeyError: return [] return downstream_jobs def get_upstream_job_names(self): """ Get all the possible upstream job names :return List of String """ upstream_jobs = [] try: for j in self._data['upstreamProjects']: upstream_jobs.append(j['name']) except KeyError: return [] return upstream_jobs def get_upstream_jobs(self): """ Get all the possible upstream jobs :return List of Job """ upstream_jobs = [] try: for j in self._data['upstreamProjects']: upstream_jobs.append(self.get_jenkins_obj().get_job(j['name'])) except KeyError: return [] return upstream_jobs def is_enabled(self): self.poll() return self._data["color"] != 'disabled' def disable(self): '''Disable job''' url = "%s/disable" % self.baseurl return self.get_jenkins_obj().requester.post_url(url, data='') def enable(self): '''Enable job''' url = "%s/enable" % self.baseurl return self.get_jenkins_obj().requester.post_url(url, data='') def delete_from_queue(self): """ Delete a job from the queue only if it's enqueued :raise NotInQueue if the job is not in the queue """ if not self.is_queued(): raise NotInQueue() queue_id = self._data['queueItem']['id'] url = urlparse.urljoin(self.get_jenkins_obj().get_queue().baseurl, 'cancelItem?id=%s' % queue_id) self.get_jenkins_obj().requester.post_and_confirm_status(url, data='') return True def get_params(self): """ Get the parameters for this job. Format varies by parameter type. Here is an example string parameter: { 'type': 'StringParameterDefinition', 'description': 'Parameter description', 'defaultParameterValue': {'value': 'default value'}, 'name': 'FOO_BAR' } """ for action in self._data['actions']: try: for param in action['parameterDefinitions']: yield param except KeyError: continue def get_params_list(self): """ Gets the list of parameter names for this job. """ params = [] for param in self.get_params(): params.append(param['name']) return params def has_queued_build(self, build_params): """Returns True if a build with build_params is currently queued.""" queue = self.jenkins.get_queue() queued_builds = queue.get_queue_items_for_job(self.name) for build in queued_builds: if build.get_parameters() == build_params: return True return False jenkinsapi-0.2.16/jenkinsapi/node.py0000644000175000017500000000751312262651567015665 0ustar ahs3ahs3""" Module for jenkinsapi Node class """ from jenkinsapi.jenkinsbase import JenkinsBase import logging import urllib log = logging.getLogger(__name__) class Node(JenkinsBase): """ Class to hold information on nodes that are attached as slaves to the master jenkins instance """ def __init__(self, baseurl, nodename, jenkins_obj): """ Init a node object by providing all relevant pointers to it :param baseurl: basic url for querying information on a node :param nodename: hostname of the node :param jenkins_obj: ref to the jenkins obj :return: Node obj """ self.name = nodename self.jenkins = jenkins_obj JenkinsBase.__init__(self, baseurl) def get_jenkins_obj(self): return self.jenkins def __str__(self): return self.name def is_online(self): self.poll() return not self._data['offline'] def is_temporarily_offline(self): self.poll() return self._data['temporarilyOffline'] def is_jnlpagent(self): return self._data['jnlpAgent'] def is_idle(self): return self._data['idle'] def set_online(self): """ Set node online. Before change state verify client state: if node set 'offline' but 'temporarilyOffline' is not set - client has connection problems and AssertionError raised. If after run node state has not been changed raise AssertionError. """ self.poll() # Before change state check if client is connected if self._data['offline'] and not self._data['temporarilyOffline']: raise AssertionError("Node is offline and not marked as temporarilyOffline" + ", check client connection: " + "offline = %s , temporarilyOffline = %s" % (self._data['offline'], self._data['temporarilyOffline'])) elif self._data['offline'] and self._data['temporarilyOffline']: self.toggle_temporarily_offline() if self._data['offline']: raise AssertionError("The node state is still offline, check client connection:" + " offline = %s , temporarilyOffline = %s" % (self._data['offline'], self._data['temporarilyOffline'])) def set_offline(self, message="requested from jenkinsapi"): """ Set node offline. If after run node state has not been changed raise AssertionError. : param message: optional string explain why you are taking this node offline """ if not self._data['offline']: self.toggle_temporarily_offline(message) self.poll() if not self._data['offline']: raise AssertionError("The node state is still online:" + "offline = %s , temporarilyOffline = %s" % (self._data['offline'], self._data['temporarilyOffline'])) def toggle_temporarily_offline(self, message="requested from jenkinsapi"): """ Switches state of connected node (online/offline) and set 'temporarilyOffline' property (True/False) Calling the same method again will bring node status back. : param message: optional string can be used to explain why you are taking this node offline """ initial_state = self.is_temporarily_offline() url = self.baseurl + "/toggleOffline?offlineMessage=" + urllib.quote(message) html_result = self.jenkins.requester.get_and_confirm_status(url) self.poll() log.debug(html_result) state = self.is_temporarily_offline() if initial_state == state: raise AssertionError("The node state has not changed: temporarilyOffline = %s" % state) jenkinsapi-0.2.16/jenkinsapi/custom_exceptions.py0000644000175000017500000000405112262651567020505 0ustar ahs3ahs3""" Module for custom_exceptions specialized for jenkinsapi """ class JenkinsAPIException(Exception): """ Base class for all errors """ pass class NotFound(JenkinsAPIException): """ Resource cannot be found """ pass class ArtifactsMissing(NotFound): """ Cannot find a build with all of the required artifacts. """ pass class UnknownJob(KeyError, NotFound): """ Jenkins does not recognize the job requested. """ pass class UnknownView(KeyError, NotFound): """ Jenkins does not recognize the view requested. """ pass class UnknownNode(KeyError, NotFound): """ Jenkins does not recognize the node requested. """ pass class UnknownQueueItem(KeyError, NotFound): """ Jenkins does not recognize the requested queue item """ pass class NoBuildData(NotFound): """ A job has no build data. """ pass class ArtifactBroken(JenkinsAPIException): """ An artifact is broken, wrong """ pass class TimeOut(JenkinsAPIException): """ Some jobs have taken too long to complete. """ pass class WillNotBuild(JenkinsAPIException): """ Cannot trigger a new build. """ pass class NoResults(JenkinsAPIException): """ A build did not publish any results. """ pass class FailedNoResults(NoResults): """ A build did not publish any results because it failed """ pass class BadURL(ValueError, JenkinsAPIException): """ A URL appears to be broken """ pass class NotAuthorized(JenkinsAPIException): """Not Authorized to access resource""" # Usually thrown when we get a 403 returned pass class NotSupportSCM(JenkinsAPIException): """ It's a SCM that does not supported by current version of jenkinsapi """ pass class NotConfiguredSCM(JenkinsAPIException): """ It's a job that doesn't have configured SCM """ pass class NotInQueue(JenkinsAPIException): """ It's a job that is not in the queue """ pass jenkinsapi-0.2.16/jenkinsapi/build.py0000644000175000017500000003147612262651567016044 0ustar ahs3ahs3""" A jenkins build represents a single execution of a Jenkins Job. Builds can be thought of as the second level of the jenkins heirarchy beneath Jobs. Builds can have state, such as whether they are running or not. They can also have outcomes, such as wether they passed or failed. Build objects can be associated with Results and Artifacts.g """ import time import pytz import logging import warnings import datetime from time import sleep from jenkinsapi import config from jenkinsapi.artifact import Artifact from jenkinsapi.result_set import ResultSet from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.constants import STATUS_SUCCESS from jenkinsapi.custom_exceptions import NoResults log = logging.getLogger(__name__) class Build(JenkinsBase): """ Represents a jenkins build, executed in context of a job. """ STR_TOTALCOUNT = "totalCount" STR_TPL_NOTESTS_ERR = "%s has status %s, and does not have any test results" def __init__(self, url, buildno, job): assert type(buildno) == int self.buildno = buildno self.job = job JenkinsBase.__init__(self, url) def _poll(self): #For build's we need more information for downstream and upstream builds #so we override the poll to get at the extra data for build objects url = self.python_api_url(self.baseurl) + '?depth=2' return self.get_data(url) def __str__(self): return self._data['fullDisplayName'] @property def name(self): return str(self) def get_number(self): return self._data["number"] def get_status(self): return self._data["result"] def get_revision(self): vcs = self._data['changeSet']['kind'] or 'git' return getattr(self, '_get_%s_rev' % vcs, lambda: None)() def get_revision_branch(self): vcs = self._data['changeSet']['kind'] or 'git' return getattr(self, '_get_%s_rev_branch' % vcs, lambda: None)() def _get_svn_rev(self): warnings.warn("This untested function may soon be removed from Jenkinsapi.") maxRevision = 0 for repoPathSet in self._data["changeSet"]["revisions"]: maxRevision = max(repoPathSet["revision"], maxRevision) return maxRevision def _get_git_rev(self): # Sometimes we have None as part of actions. Filter those actions # which have lastBuiltRevision in them _actions = [x for x in self._data['actions'] if x and "lastBuiltRevision" in x] return _actions[0]["lastBuiltRevision"]["SHA1"] def _get_hg_rev(self): warnings.warn("This untested function may soon be removed from Jenkinsapi.") return [x['mercurialNodeName'] for x in self._data['actions'] if 'mercurialNodeName' in x][0] def _get_svn_rev_branch(self): raise NotImplementedError('_get_svn_rev_branch is not yet implemented') def _get_git_rev_branch(self): # Sometimes we have None as part of actions. Filter those actions # which have lastBuiltRevision in them _actions = [x for x in self._data['actions'] if x and "lastBuiltRevision" in x] return _actions[0]["lastBuiltRevision"]["branch"] def _get_hg_rev_branch(self): raise NotImplementedError('_get_hg_rev_branch is not yet implemented') def get_duration(self): return datetime.timedelta(milliseconds=self._data["duration"]) def get_artifacts(self): for afinfo in self._data["artifacts"]: url = "%s/artifact/%s" % (self.baseurl, afinfo["relativePath"]) af = Artifact(afinfo["fileName"], url, self) yield af def get_artifact_dict(self): return dict( (af.filename, af) for af in self.get_artifacts() ) def get_upstream_job_name(self): """ Get the upstream job name if it exist, None otherwise :return: String or None """ try: return self.get_actions()['causes'][0]['upstreamProject'] except KeyError: return None def get_upstream_job(self): """ Get the upstream job object if it exist, None otherwise :return: Job or None """ if self.get_upstream_job_name(): return self.get_jenkins_obj().get_job(self.get_upstream_job_name()) else: return None def get_upstream_build_number(self): """ Get the upstream build number if it exist, None otherwise :return: int or None """ try: return int(self.get_actions()['causes'][0]['upstreamBuild']) except KeyError: return None def get_upstream_build(self): """ Get the upstream build if it exist, None otherwise :return Build or None """ upstream_job = self.get_upstream_job() if upstream_job: return upstream_job.get_build(self.get_upstream_build_number()) else: return None def get_master_job_name(self): """ Get the master job name if it exist, None otherwise :return: String or None """ warnings.warn("This untested function may soon be removed from Jenkinsapi.") try: return self.get_actions()['parameters'][0]['value'] except KeyError: return None def get_master_job(self): """ Get the master job object if it exist, None otherwise :return: Job or None """ warnings.warn("This untested function may soon be removed from Jenkinsapi.") if self.get_master_job_name(): return self.get_jenkins_obj().get_job(self.get_master_job_name()) else: return None def get_master_build_number(self): """ Get the master build number if it exist, None otherwise :return: int or None """ warnings.warn("This untested function may soon be removed from Jenkinsapi.") try: return int(self.get_actions()['parameters'][1]['value']) except KeyError: return None def get_master_build(self): """ Get the master build if it exist, None otherwise :return Build or None """ warnings.warn("This untested function may soon be removed from Jenkinsapi.") master_job = self.get_master_job() if master_job: return master_job.get_build(self.get_master_build_number()) else: return None def get_downstream_jobs(self): """ Get the downstream jobs for this build :return List of jobs or None """ warnings.warn("This untested function may soon be removed from Jenkinsapi.") downstream_jobs = [] try: for job_name in self.get_downstream_job_names(): downstream_jobs.append(self.get_jenkins_obj().get_job(job_name)) return downstream_jobs except (IndexError, KeyError): return [] def get_downstream_job_names(self): """ Get the downstream job names for this build :return List of string or None """ # <<<<<<< HEAD # downstream_jobs_names = self.job.get_downstream_job_names() # fingerprint_data = self.get_data("%s?depth=2&tree=fingerprint[usage[name]]" \ # % self.python_api_url(self.baseurl)) # try: # fingerprints = fingerprint_data['fingerprint'][0] # return [ # f['name'] # for f in fingerprints['usage'] # if f['name'] in downstream_jobs_names # ] # ======= downstream_job_names = self.job.get_downstream_job_names() downstream_names = [] try: fingerprints = self._data["fingerprint"] for fingerprint in fingerprints: for job_usage in fingerprint['usage']: if job_usage['name'] in downstream_job_names: downstream_names.append(job_usage['name']) return downstream_names # >>>>>>> unstable except (IndexError, KeyError): return [] def get_downstream_builds(self): """ Get the downstream builds for this build :return List of Build or None """ # <<<<<<< HEAD # downstream_jobs_names = set(self.job.get_downstream_job_names()) # msg = "%s?depth=2&tree=fingerprint[usage[name,ranges[ranges[end,start]]]]" # fingerprint_data = self.get_data(msg % self.python_api_url(self.baseurl)) # try: # fingerprints = fingerprint_data['fingerprint'][0] # return [ # self.get_jenkins_obj().get_job(f['name']).get_build(f['ranges']['ranges'][0]['start']) # for f in fingerprints['usage'] # if f['name'] in downstream_jobs_names # ] # ======= downstream_job_names = self.get_downstream_job_names() downstream_builds = [] try: fingerprints = self._data["fingerprint"] for fingerprint in fingerprints: for job_usage in fingerprint['usage']: if job_usage['name'] in downstream_job_names: job = self.get_jenkins_obj().get_job(job_usage['name']) for job_range in job_usage['ranges']['ranges']: for build_id in range(job_range['start'], job_range['end']): downstream_builds.append(job.get_build(build_id)) return downstream_builds # >>>>>>> unstable except (IndexError, KeyError): return [] def get_matrix_runs(self): """ For a matrix job, get the individual builds for each matrix configuration :return: Generator of Build """ if "runs" in self._data: for rinfo in self._data["runs"]: yield Build(rinfo["url"], rinfo["number"], self.job) def is_running(self): """ Return a bool if running. """ self.poll() return self._data["building"] def block(self): while self.is_running(): time.sleep(1) def is_good(self): """ Return a bool, true if the build was good. If the build is still running, return False. """ return (not self.is_running()) and self._data["result"] == STATUS_SUCCESS def block_until_complete(self, delay=15): assert isinstance(delay, int) count = 0 while self.is_running(): total_wait = delay * count log.info(msg="Waited %is for %s #%s to complete" % (total_wait, self.job.name, self.name)) sleep(delay) count += 1 def get_jenkins_obj(self): return self.job.get_jenkins_obj() def get_result_url(self): """ Return the URL for the object which provides the job's result summary. """ url_tpl = r"%stestReport/%s" return url_tpl % (self._data["url"], config.JENKINS_API) def get_resultset(self): """ Obtain detailed results for this build. """ result_url = self.get_result_url() if self.STR_TOTALCOUNT not in self.get_actions(): raise NoResults("%s does not have any published results" % str(self)) buildstatus = self.get_status() if not self.get_actions()[self.STR_TOTALCOUNT]: raise NoResults(self.STR_TPL_NOTESTS_ERR % (str(self), buildstatus)) obj_results = ResultSet(result_url, build=self) return obj_results def has_resultset(self): """ Return a boolean, true if a result set is available. false if not. """ return self.STR_TOTALCOUNT in self.get_actions() def get_actions(self): all_actions = {} for dct_action in self._data["actions"]: if dct_action is None: continue all_actions.update(dct_action) return all_actions def get_timestamp(self): ''' Returns build timestamp in UTC ''' # Java timestamps are given in miliseconds since the epoch start! naive_timestamp = datetime.datetime(*time.gmtime(self._data['timestamp'] / 1000.0)[:6]) return pytz.utc.localize(naive_timestamp) def get_console(self): """ Return the current state of the text console. """ url = "%s/consoleText" % self.baseurl return self.job.jenkins.requester.get_url(url).content def stop(self): """ Stops the build execution if it's running :return boolean True if succeded False otherwise or the build is not running """ if self.is_running(): url = "%s/stop" % self.baseurl self.job.jenkins.requester.post_and_confirm_status(url, data='') return True return False jenkinsapi-0.2.16/jenkinsapi/constants.py0000644000175000017500000000064012262651567016746 0ustar ahs3ahs3""" Constants for jenkinsapi """ import re STATUS_FAIL = "FAIL" STATUS_ERROR = "ERROR" STATUS_ABORTED = "ABORTED" STATUS_REGRESSION = "REGRESSION" STATUS_SUCCESS = "SUCCESS" STATUS_FIXED = "FIXED" STATUS_PASSED = "PASSED" RESULTSTATUS_FAILURE = "FAILURE" RESULTSTATUS_FAILED = "FAILED" RESULTSTATUS_SKIPPED = "SKIPPED" STR_RE_SPLIT_VIEW = "(.*)/view/([^/]*)/?" RE_SPLIT_VIEW_URL = re.compile(STR_RE_SPLIT_VIEW) jenkinsapi-0.2.16/jenkinsapi/views.py0000644000175000017500000000636712262651567016103 0ustar ahs3ahs3""" Module for jenkinsapi Views """ import logging import json from jenkinsapi.view import View log = logging.getLogger(__name__) class Views(object): """ An abstraction on a Jenkins object's views """ LIST_VIEW = 'hudson.model.ListView' NESTED_VIEW = 'hudson.plugins.nested_view.NestedView' MY_VIEW = 'hudson.model.MyView' DASHBOARD_VIEW = 'hudson.plugins.view.dashboard.Dashboard' PIPELINE_VIEW = 'au.com.centrumsystems.hudson.plugin.buildpipeline.BuildPipelineView' def __init__(self, jenkins): self.jenkins = jenkins def __len__(self): return len(self.keys()) def __delitem__(self, view_name): if view_name == 'All': raise ValueError('Cannot delete this view: %s' % view_name) if view_name in self: self[view_name].delete() self.jenkins.poll() def __setitem__(self, view_name, job_names_list): new_view = self.create(view_name) if isinstance(job_names_list, str): job_names_list = [job_names_list] for job_name in job_names_list: if not new_view.add_job(job_name): # Something wrong - delete view del self[new_view] raise TypeError('Job %s does not exist in Jenkins' % job_name) def __getitem__(self, view_name): for row in self.jenkins._data.get('views', []): if row['name'] == view_name: return View(row['url'], row['name'], self.jenkins) def iteritems(self): """ Get the names & objects for all views """ self.jenkins.poll() for row in self.jenkins._data.get('views', []): name = row['name'] url = row['url'] yield name, View(url, name, self.jenkins) def __contains__(self, view_name): """ True if view_name is the name of a defined view """ return view_name in self.keys() def iterkeys(self): """ Get the names of all available views """ for row in self.jenkins._data.get('views', []): yield row['name'] def keys(self): """ Return a list of the names of all views """ return list(self.iterkeys()) def create(self, view_name, view_type=LIST_VIEW): """ Create a view :param view_name: name of new view, str :param person: Person name (to create personal view), str :return: new View obj or None if view was not created """ log.info(msg='Creating "%s" view "%s"' % (view_type, view_name)) #url = urlparse.urljoin(self.baseurl, "user/%s/my-views/" % person) if person else self.baseurl if view_name in self: log.warn(msg='View "%s" already exists' % view_name) return self[view_name] url = '%s/createView' % self.jenkins.baseurl headers = {'Content-Type': 'application/x-www-form-urlencoded'} data = { "name": view_name, "mode": view_type, "Submit": "OK", "json": json.dumps({"name": view_name, "mode": view_type}) } self.jenkins.requester.post_and_confirm_status(url, data=data, headers=headers) self.jenkins.poll() return self[view_name] jenkinsapi-0.2.16/jenkinsapi/result_set.py0000644000175000017500000000264412262651567017131 0ustar ahs3ahs3""" Module for jenkinsapi ResultSet """ from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.result import Result class ResultSet(JenkinsBase): """ Represents a result from a completed Jenkins run. """ def __init__(self, url, build): """ Init a resultset :param url: url for a build, str :param build: build obj """ self.build = build JenkinsBase.__init__(self, url) def get_jenkins_obj(self): return self.build.job.get_jenkins_obj() def __str__(self): return "Test Result for %s" % str(self.build) @property def name(self): return str(self) def keys(self): return [a[0] for a in self.iteritems()] def items(self): return [a for a in self.iteritems()] def iteritems(self): for suite in self._data.get("suites", []): for case in suite["cases"]: result = Result(**case) yield result.identifier(), result for report_set in self._data.get("childReports", []): for suite in report_set["result"]["suites"]: for case in suite["cases"]: result = Result(**case) yield result.identifier(), result def __len__(self): return len(self.items()) def __getitem__(self, key): self_as_dict = dict(self.iteritems()) return self_as_dict[key] jenkinsapi-0.2.16/jenkinsapi/__init__.py0000644000175000017500000000402712262651567016474 0ustar ahs3ahs3""" About this library ================== Jenkins is the market leading continuous integration system, originally created by Kohsuke Kawaguchi. This API makes Jenkins even easier to use by providing an easy to use conventional python interface. Jenkins (and It's predecessor Hudson) are fantastic projects - but they are somewhat Java-centric. Thankfully the designers have provided an excellent and complete REST interface. This library wraps up that interface as more conventional python objects in order to make most Jenkins oriented tasks simpler. This library can help you: * Query the test-results of a completed build * Get a objects representing the latest builds of a job * Search for artefacts by simple criteria * Block until jobs are complete * Install artefacts to custom-specified directory structures * username/password auth support for jenkins instances with auth turned on * Ability to search for builds by subversion revision * Ability to add/remove/query jenkins slaves Installing JenkinsAPI ===================== Egg-files for this project are hosted on PyPi. Most Python users should be able to use pip or distribute to automatically install this project. Most users can do the following: easy_install jenkinsapi If you'd like to install in multi-version mode: easy_install -m jenkinsapi Project Authors =============== * Salim Fadhley (sal@stodge.org) * Ramon van Alteren (ramon@vanalteren.nl) * Ruslan Lutsenko (ruslan.lutcenko@gmail.com) Current code lives on github: https://github.com/salimfadhley/jenkinsapi """ from jenkinsapi import ( # Modules command_line, utils, # Files api, artifact, build, config, constants, custom_exceptions, fingerprint, executors, executor, jenkins, jenkinsbase, job, node, result_set, result, view ) __all__ = [ "command_line", "utils", "api", "artifact", "build", "config", "constants", "custom_exceptions", "executors", "executor", "fingerprint", "jenkins", "jenkinsbase", "job", "node", "result_set", "result", "view" ] __docformat__ = "epytext" jenkinsapi-0.2.16/jenkinsapi/view.py0000644000175000017500000001272412262651567015712 0ustar ahs3ahs3""" Module for jenkinsapi views """ import urllib import logging from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.job import Job log = logging.getLogger(__name__) class View(JenkinsBase): """ View class """ def __init__(self, url, name, jenkins_obj): self.name = name self.jenkins_obj = jenkins_obj JenkinsBase.__init__(self, url) self.deleted = False def __str__(self): return self.name def __getitem__(self, job_name): assert isinstance(job_name, str) api_url = self.python_api_url(self.get_job_url(job_name)) return Job(api_url, job_name, self.jenkins_obj) def __contains__(self, job_name): """ True if view_name is the name of a defined view """ return job_name in self.keys() def delete(self): """ Remove this view object """ url = "%s/doDelete" % self.baseurl self.jenkins_obj.requester.post_and_confirm_status(url, data='') self.jenkins_obj.poll() self.deleted = True def keys(self): return self.get_job_dict().keys() def iteritems(self): for name, url in self.get_job_dict().iteritems(): api_url = self.python_api_url(url) yield name, Job(api_url, name, self.jenkins_obj) def values(self): return [a[1] for a in self.iteritems()] def items(self): return [a for a in self.iteritems()] def _get_jobs(self): if not 'jobs' in self._data: pass else: for viewdict in self._data["jobs"]: yield viewdict["name"], viewdict["url"] def get_job_dict(self): return dict(self._get_jobs()) def __len__(self): return len(self.get_job_dict().keys()) def get_job_url(self, str_job_name): try: job_dict = self.get_job_dict() return job_dict[str_job_name] except KeyError: #noinspection PyUnboundLocalVariable all_views = ", ".join(job_dict.keys()) raise KeyError("Job %s is not known - available: %s" % (str_job_name, all_views)) def get_jenkins_obj(self): return self.jenkins_obj def add_job(self, str_job_name, job=None): """ Add job to a view :param str_job_name: name of the job to be added :param job: Job object to be added :return: True if job has been added, False if job already exists or job not known to Jenkins """ if not job: if str_job_name in self.get_job_dict(): log.warn(msg='Job %s is already in the view %s' % (str_job_name, self.name)) return False else: # Since this call can be made from nested view, # which doesn't have any jobs, we can miss existing job # Thus let's create top level Jenkins and ask him # http://jenkins:8080/view/CRT/view/CRT-FB/view/CRT-SCRT-1301/ top_jenkins = self.get_jenkins_obj().get_jenkins_obj_from_url( self.baseurl.split('view/')[0]) if not top_jenkins.has_job(str_job_name): log.error(msg='Job "%s" is not known to Jenkins' % str_job_name) return False else: job = top_jenkins.get_job(str_job_name) log.info(msg='Creating job %s in view %s' % (str_job_name, self.name)) data = { "description": "", "statusFilter": "", "useincluderegex": "on", "includeRegex": "", "columns": [{"stapler-class": "hudson.views.StatusColumn", "kind": "hudson.views.StatusColumn"}, {"stapler-class": "hudson.views.WeatherColumn", "kind": "hudson.views.WeatherColumn"}, {"stapler-class": "hudson.views.JobColumn", "kind": "hudson.views.JobColumn"}, {"stapler-class": "hudson.views.LastSuccessColumn", "kind": "hudson.views.LastSuccessColumn"}, {"stapler-class": "hudson.views.LastFailureColumn", "kind": "hudson.views.LastFailureColumn"}, {"stapler-class": "hudson.views.LastDurationColumn", "kind": "hudson.views.LastDurationColumn"}, {"stapler-class": "hudson.views.BuildButtonColumn", "kind": "hudson.views.BuildButtonColumn"}], "Submit": "OK", } data["name"] = self.name # Add existing jobs (if any) for job_name in self.get_job_dict().keys(): data[job_name] = 'on' # Add new job data[job.name] = 'on' data['json'] = data.copy() data = urllib.urlencode(data) self.get_jenkins_obj().requester.post_and_confirm_status( '%s/configSubmit' % self.baseurl, data=data) self.poll() log.debug(msg='Job "%s" has been added to a view "%s"' % (job.name, self.name)) return True def _get_nested_views(self): for viewdict in self._data.get("views", []): yield viewdict["name"], viewdict["url"] def get_nested_view_dict(self): return dict(self._get_nested_views()) @property def views(self): return self.get_jenkins_obj().get_jenkins_obj_from_url(self.baseurl).views jenkinsapi-0.2.16/jenkinsapi/executor.py0000644000175000017500000000331612262651567016573 0ustar ahs3ahs3""" Module for jenkinsapi Executer class """ from jenkinsapi.jenkinsbase import JenkinsBase import logging log = logging.getLogger(__name__) class Executor(JenkinsBase): """ Class to hold information on nodes that are attached as slaves to the master jenkins instance """ def __init__(self, baseurl, nodename, jenkins_obj, number): """ Init a node object by providing all relevant pointers to it :param baseurl: basic url for querying information on a node :param nodename: hostname of the node :param jenkins_obj: ref to the jenkins obj :return: Node obj """ self.nodename = nodename self.number = number self.jenkins = jenkins_obj self.baseurl = baseurl JenkinsBase.__init__(self, baseurl) def __str__(self): return '%s %s' % (self.nodename, self.number) def get_jenkins_obj(self): return self.jenkins def get_progress(self): """Returns percentage""" self.poll() return self._data['progress'] def get_number(self): """ Get Executor number. """ self.poll() return self._data['number'] def is_idle(self): """ Returns Boolean: whether Executor is idle or not. """ self.poll() return self._data['idle'] def likely_stuck(self): """ Returns Boolean: whether Executor is likely stuck or not. """ self.poll() return self._data['likelyStuck'] def get_current_executable(self): """ Returns the current Queue.Task this executor is running. """ self.poll() return self._data['currentExecutable'] jenkinsapi-0.2.16/jenkinsapi/jenkins.py0000644000175000017500000002430612262651567016400 0ustar ahs3ahs3""" Module for jenkinsapi Jenkins object """ import json import urllib import logging import urlparse from jenkinsapi import config from jenkinsapi.executors import Executors from jenkinsapi.job import Job from jenkinsapi.jobs import Jobs from jenkinsapi.node import Node from jenkinsapi.view import View from jenkinsapi.nodes import Nodes from jenkinsapi.plugins import Plugins from jenkinsapi.views import Views from jenkinsapi.queue import Queue from jenkinsapi.fingerprint import Fingerprint from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.utils.requester import Requester from jenkinsapi.custom_exceptions import UnknownJob log = logging.getLogger(__name__) class Jenkins(JenkinsBase): """ Represents a jenkins environment. """ def __init__(self, baseurl, username=None, password=None, requester=None): """ :param baseurl: baseurl for jenkins instance including port, str :param username: username for jenkins auth, str :param password: password for jenkins auth, str :return: a Jenkins obj """ self.username = username self.password = password self.requester = requester or Requester(username, password, baseurl=baseurl) JenkinsBase.__init__(self, baseurl) def _clone(self): return Jenkins(self.baseurl, username=self.username, password=self.password, requester=self.requester) def base_server_url(self): if config.JENKINS_API in self.baseurl: return self.baseurl[:-(len(config.JENKINS_API))] else: return self.baseurl def validate_fingerprint(self, id_): obj_fingerprint = Fingerprint(self.baseurl, id_, jenkins_obj=self) obj_fingerprint.validate() log.info(msg="Jenkins says %s is valid" % id_) # def reload(self): # '''Try and reload the configuration from disk''' # self.requester.get_url("%(baseurl)s/reload" % self.__dict__) def get_artifact_data(self, id_): obj_fingerprint = Fingerprint(self.baseurl, id_, jenkins_obj=self) obj_fingerprint.validate() return obj_fingerprint.get_info() def validate_fingerprint_for_build(self, digest, filename, job, build): obj_fingerprint = Fingerprint(self.baseurl, digest, jenkins_obj=self) return obj_fingerprint.validate_for_build(filename, job, build) def get_jenkins_obj(self): return self def get_jenkins_obj_from_url(self, url): return Jenkins(url, self.username, self.password, self.requester) def get_create_url(self): # This only ever needs to work on the base object return '%s/createItem' % self.baseurl def get_nodes_url(self): # This only ever needs to work on the base object return '%s/computer' % self.baseurl @property def jobs(self): return Jobs(self) def get_jobs(self): """ Fetch all the build-names on this Jenkins server. """ for info in self._data["jobs"]: yield info["name"], \ Job(info["url"], info["name"], jenkins_obj=self) def get_jobs_info(self): """ Get the jobs information :return url, name """ for info in self._data["jobs"]: yield info["url"], info["name"] def get_job(self, jobname): """ Get a job by name :param jobname: name of the job, str :return: Job obj """ return self.jobs[jobname] def has_job(self, jobname): """ Does a job by the name specified exist :param jobname: string :return: boolean """ return jobname in self.jobs def create_job(self, jobname, config_): """ Create a job :param jobname: name of new job, str :param config: configuration of new job, xml :return: new Job obj """ return self.jobs.create(jobname, config_) def copy_job(self, jobname, newjobname): return self.jobs.copy(jobname, newjobname) def build_job(self, jobname, params=None): """ Invoke a build by job name :param jobname: name of exist job, str :param params: the job params, dict :return: none """ self[jobname].invoke(build_params=params or {}) def delete_job(self, jobname): """ Delete a job by name :param jobname: name of a exist job, str :return: new jenkins_obj """ del self.jobs[jobname] def rename_job(self, jobname, newjobname): """ Rename a job :param jobname: name of a exist job, str :param newjobname: name of new job, str :return: new Job obj """ return self.jobs.rename(jobname, newjobname) def iterkeys(self): for info in self._data["jobs"]: yield info["name"] def iteritems(self): """ :param return: An iterator of pairs. Each pair will be (job name, Job object) """ return self.get_jobs() def items(self): """ :param return: A list of pairs. Each pair will be (job name, Job object) """ return list(self.get_jobs()) def keys(self): return [a for a in self.iterkeys()] # This is a function alias we retain for historical compatibility get_jobs_list = keys def __str__(self): return "Jenkins server at %s" % self.baseurl @property def views(self): return Views(self) def get_view_by_url(self, str_view_url): #for nested view str_view_name = str_view_url.split('/view/')[-1].replace('/', '') return View(str_view_url, str_view_name, jenkins_obj=self) def delete_view_by_url(self, str_url): url = "%s/doDelete" % str_url self.requester.post_and_confirm_status(url, data='') self.poll() return self def __getitem__(self, jobname): """ Get a job by name :param jobname: name of job, str :return: Job obj """ for info in self._data["jobs"]: if info["name"] == jobname: return Job(info["url"], info["name"], jenkins_obj=self) raise UnknownJob(jobname) def __len__(self): return len(self._data["jobs"]) def __contains__(self, jobname): """ Does a job by the name specified exist :param jobname: string :return: boolean """ return jobname in self.jobs def get_node(self, nodename): """Get a node object for a specific node""" return self.get_nodes()[nodename] def get_node_url(self, nodename=""): """Return the url for nodes""" url = urlparse.urljoin(self.base_server_url(), 'computer/%s' % urllib.quote(nodename)) return url def get_queue_url(self): url = "%s/%s" % (self.base_server_url(), 'queue') return url def get_queue(self): queue_url = self.get_queue_url() return Queue(queue_url, self) def get_nodes(self): url = self.get_nodes_url() return Nodes(url, self) def has_node(self, nodename): """ Does a node by the name specified exist :param nodename: string, hostname :return: boolean """ self.poll() return nodename in self.get_nodes() def delete_node(self, nodename): """ Remove a node from the managed slave list Please note that you cannot remove the master node :param nodename: string holding a hostname :return: None """ assert self.has_node(nodename), "This node: %s is not registered as a slave" % nodename assert nodename != "master", "you cannot delete the master node" url = "%s/doDelete" % self.get_node_url(nodename) self.requester.get_and_confirm_status(url) def create_node(self, name, num_executors=2, node_description=None, remote_fs='/var/lib/jenkins', labels=None, exclusive=False): """ Create a new slave node by name. :param name: fqdn of slave, str :param num_executors: number of executors, int :param node_description: a freetext field describing the node :param remote_fs: jenkins path, str :param labels: labels to associate with slave, str :param exclusive: tied to specific job, boolean :return: node obj """ NODE_TYPE = 'hudson.slaves.DumbSlave$DescriptorImpl' MODE = 'NORMAL' if self.has_node(name): return Node(nodename=name, baseurl=self.get_node_url(nodename=name), jenkins_obj=self) if exclusive: MODE = 'EXCLUSIVE' params = { 'name': name, 'type': NODE_TYPE, 'json': json.dumps({ 'name': name, 'nodeDescription': node_description, 'numExecutors': num_executors, 'remoteFS': remote_fs, 'labelString': labels, 'mode': MODE, 'type': NODE_TYPE, 'retentionStrategy': {'stapler-class': 'hudson.slaves.RetentionStrategy$Always'}, 'nodeProperties': {'stapler-class-bag': 'true'}, 'launcher': {'stapler-class': 'hudson.slaves.JNLPLauncher'} }) } url = self.get_node_url() + "doCreateItem?%s" % urllib.urlencode(params) self.requester.get_and_confirm_status(url) return Node(nodename=name, baseurl=self.get_node_url(nodename=name), jenkins_obj=self) def get_plugins_url(self): # This only ever needs to work on the base object return '%s/pluginManager/api/python?depth=1' % self.baseurl def get_plugins(self): url = self.get_plugins_url() return Plugins(url, self) def has_plugin(self, plugin_name): return plugin_name in self.get_plugins() def get_executors(self, nodename): url = '%s/computer/%s' % (self.baseurl, nodename) return Executors(url, nodename, self) @property def version(self): """ Return version number of Jenkins """ response = self.requester.get_and_confirm_status(self.baseurl) version_key = 'X-Jenkins' return response.headers.get(version_key, '0.0') jenkinsapi-0.2.16/jenkinsapi/executors.py0000644000175000017500000000214112262651567016751 0ustar ahs3ahs3""" This module implements the Executors class, which is intended to be a container-like interface for all of the executors defined on a single Jenkins node. """ import logging from jenkinsapi.executor import Executor from jenkinsapi.jenkinsbase import JenkinsBase log = logging.getLogger(__name__) class Executors(JenkinsBase): """ This class provides a container-like API which gives access to all executors on a Jenkins node. Returns a list of Executor Objects. """ def __init__(self, baseurl, nodename, jenkins): self.nodename = nodename self.jenkins = jenkins JenkinsBase.__init__(self, baseurl) self.count = self._data['numExecutors'] def __str__(self): return 'Executors @ %s' % self.baseurl def get_jenkins_obj(self): return self.jenkins def __iter__(self): for index in range(self.count): executor_url = '%s/executors/%s' % (self.baseurl, index) yield Executor( executor_url, self.nodename, self.jenkins, index ) jenkinsapi-0.2.16/jenkinsapi/jenkinsbase.py0000644000175000017500000000427512262651567017236 0ustar ahs3ahs3""" Module for JenkinsBase class """ import ast import logging from jenkinsapi import config from jenkinsapi.custom_exceptions import JenkinsAPIException log = logging.getLogger(__name__) class JenkinsBase(object): """ This appears to be the base object that all other jenkins objects are inherited from """ RETRY_ATTEMPTS = 1 def __repr__(self): return """<%s.%s %s>""" % (self.__class__.__module__, self.__class__.__name__, str(self)) def __str__(self): raise NotImplementedError def __init__(self, baseurl, poll=True): """ Initialize a jenkins connection """ self._data = None self.baseurl = self.strip_trailing_slash(baseurl) if poll: self.poll() def get_jenkins_obj(self): raise NotImplementedError('Please implement this method on %s' % self.__class__.__name__) def __eq__(self, other): """ Return true if the other object represents a connection to the same server """ if not isinstance(other, self.__class__): return False if not other.baseurl == self.baseurl: return False return True @classmethod def strip_trailing_slash(cls, url): while url.endswith('/'): url = url[:-1] return url def poll(self): self._data = self._poll() def _poll(self): url = self.python_api_url(self.baseurl) return self.get_data(url) def get_data(self, url, params=None): requester = self.get_jenkins_obj().requester response = requester.get_url(url, params) try: return ast.literal_eval(response.text) except Exception: log.exception('Inappropriate content found at %s', url) raise JenkinsAPIException('Cannot parse %s' % response.content) @classmethod def python_api_url(cls, url): if url.endswith(config.JENKINS_API): return url else: if url.endswith(r"/"): fmt = "%s%s" else: fmt = "%s/%s" return fmt % (url, config.JENKINS_API) jenkinsapi-0.2.16/jenkinsapi/command_line/0000755000175000017500000000000012262651567017005 5ustar ahs3ahs3jenkinsapi-0.2.16/jenkinsapi/command_line/jenkins_invoke.py0000644000175000017500000000520312262651567022373 0ustar ahs3ahs3""" jenkinsapi class for invoking Jenkins """ import os import sys import logging import optparse from jenkinsapi import jenkins log = logging.getLogger(__name__) class JenkinsInvoke(object): """ JenkinsInvoke object implements class to call from command line """ @classmethod def mkparser(cls): parser = optparse.OptionParser() DEFAULT_BASEURL = os.environ.get("JENKINS_URL", "http://localhost/jenkins") parser.help_text = "Execute a number of jenkins jobs on the server of your choice." + \ " Optionally block until the jobs are complete." parser.add_option("-J", "--jenkinsbase", dest="baseurl", help="Base URL for the Jenkins server, default is %s" % DEFAULT_BASEURL, type="str", default=DEFAULT_BASEURL) parser.add_option('--username', '-u', dest='username', help="Username for jenkins authentification", type='str', default=None) parser.add_option('--password', '-p', dest='password', help="password for jenkins user auth", type='str', default=None) parser.add_option("-b", "--block", dest="block", action="store_true", default=False, help="Block until each of the jobs is complete.") parser.add_option("-t", "--token", dest="token", help="Optional security token.", default=None) return parser @classmethod def main(cls): parser = cls.mkparser() options, args = parser.parse_args() try: assert len(args) > 0, "Need to specify at least one job name" except AssertionError as err: log.critical(err[0]) parser.print_help() sys.exit(1) invoker = cls(options, args) invoker() def __init__(self, options, jobs): self.options = options self.jobs = jobs self.api = self._get_api(baseurl=options.baseurl, username=options.username, password=options.password) def _get_api(self, baseurl, username, password): return jenkins.Jenkins(baseurl, username, password) def __call__(self): for job in self.jobs: self.invokejob(job, block=self.options.block, token=self.options.token) def invokejob(self, jobname, block, token): assert type(block) == bool assert type(jobname) == str assert token is None or isinstance(token, str) job = self.api.get_job(jobname) job.invoke(securitytoken=token, block=block) def main(): logging.basicConfig() logging.getLogger("").setLevel(logging.INFO) JenkinsInvoke.main() jenkinsapi-0.2.16/jenkinsapi/command_line/__init__.py0000644000175000017500000000005312262651567021114 0ustar ahs3ahs3""" __init__,py for commandline module """ jenkinsapi-0.2.16/jenkinsapi/nodes.py0000644000175000017500000000321012262651567016036 0ustar ahs3ahs3""" Module for jenkinsapi nodes """ import logging from jenkinsapi.node import Node from jenkinsapi.custom_exceptions import UnknownNode from jenkinsapi.jenkinsbase import JenkinsBase log = logging.getLogger(__name__) class Nodes(JenkinsBase): """ Class to hold information on a collection of nodes """ def __init__(self, baseurl, jenkins_obj): """ Handy access to all of the nodes on your Jenkins server """ self.jenkins = jenkins_obj JenkinsBase.__init__(self, baseurl) def get_jenkins_obj(self): return self.jenkins def __str__(self): return 'Nodes @ %s' % self.baseurl def __contains__(self, node_name): return node_name in self.keys() def iterkeys(self): for item in self._data['computer']: yield item['displayName'] def keys(self): return list(self.iterkeys()) def iteritems(self): for item in self._data['computer']: nodename = item['displayName'] if nodename.lower() == 'master': nodeurl = '%s/(%s)' % (self.baseurl, nodename) else: nodeurl = '%s/%s' % (self.baseurl, nodename) try: yield item['displayName'], Node(nodeurl, nodename, self.jenkins) except Exception: import ipdb ipdb.set_trace() def __getitem__(self, nodename): self_as_dict = dict(self.iteritems()) if nodename in self_as_dict: return self_as_dict[nodename] else: raise UnknownNode(nodename) def __len__(self): return len(self.iteritems()) jenkinsapi-0.2.16/jenkinsapi/queue.py0000644000175000017500000000562312262651567016064 0ustar ahs3ahs3""" Queue module for jenkinsapi """ from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.custom_exceptions import UnknownQueueItem import logging log = logging.getLogger(__name__) class Queue(JenkinsBase): """ Class that represents the Jenkins queue """ def __init__(self, baseurl, jenkins_obj): """ Init the Jenkins queue object :param baseurl: basic url for the queue :param jenkins_obj: ref to the jenkins obj """ self.jenkins = jenkins_obj JenkinsBase.__init__(self, baseurl) def __str__(self): return self.baseurl def get_jenkins_obj(self): return self.jenkins def iteritems(self): for item in self._data['items']: yield item['id'], QueueItem(self.jenkins, **item) def iterkeys(self): for item in self._data['items']: yield item['id'] def iterivalues(self): for item in self._data['items']: yield QueueItem(self.jenkins, **item) def keys(self): return list(self.iterkeys()) def values(self): return list(self.itervalues()) def __len__(self): return len(self._data['items']) def __getitem__(self, item_id): self_as_dict = dict(self.iteritems()) if item_id in self_as_dict: return self_as_dict[item_id] else: raise UnknownQueueItem(item_id) def get_queue_items_for_job(self, job_name): if not job_name: return [QueueItem(self.jenkins, **item) for item in self._data['items']] else: return [QueueItem(self.jenkins, **item) for item in self._data['items'] if item['task']['name'] == job_name] def delete_item(self, queue_item): self.delete_item_by_id(queue_item.id) def delete_item_by_id(self, item_id): deleteurl = '%s/cancelItem?id=%s' % (self.baseurl, item_id) self.get_jenkins_obj().requester.post_url(deleteurl) class QueueItem(object): """ Flexible class to handle queue items. If the Jenkins API changes this support those changes """ def __init__(self, jenkins, **kwargs): self.jenkins = jenkins self.__dict__.update(kwargs) def get_job(self): """ Return the job associated with this queue item """ return self.jenkins[self.task['name']] def get_parameters(self): """returns parameters of queue item""" actions = getattr(self, 'actions', []) for action in actions: if type(action) is dict and 'parameters' in action: parameters = action['parameters'] return dict([(x['name'], x['value']) for x in parameters]) return [] def __repr__(self): return "<%s.%s %s>" % (self.__class__.__module__, self.__class__.__name__, str(self)) def __str__(self): return "%s #%i" % (self.task['name'], self.id) jenkinsapi-0.2.16/jenkinsapi/plugin.py0000644000175000017500000000044212262651567016230 0ustar ahs3ahs3""" Module for jenkinsapi Plugin """ class Plugin(object): """ Plugin class """ def __init__(self, plugin_dict): assert isinstance(plugin_dict, dict) self.__dict__ = plugin_dict def __eq__(self, other): return self.__dict__ == other.__dict__ jenkinsapi-0.2.16/jenkinsapi/fingerprint.py0000644000175000017500000000660312262651567017266 0ustar ahs3ahs3""" Module for jenkinsapi Fingerprint """ from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.custom_exceptions import ArtifactBroken import urllib2 import re import logging log = logging.getLogger(__name__) class Fingerprint(JenkinsBase): """ Represents a jenkins fingerprint on a single artifact file ?? """ RE_MD5 = re.compile("^([0-9a-z]{32})$") def __init__(self, baseurl, id_, jenkins_obj): logging.basicConfig() self.jenkins_obj = jenkins_obj assert self.RE_MD5.search(id_), "%s does not look like a valid id" % id_ url = "%s/fingerprint/%s/" % (baseurl, id_) JenkinsBase.__init__(self, url, poll=False) self.id_ = id_ self.unknown = False # Previously uninitialized in ctor def get_jenkins_obj(self): return self.jenkins_obj def __str__(self): return self.id_ def valid(self): """ Return True / False if valid. If returns True, self.unknown is set to either True or False, and can be checked if we have positive validity (fingerprint known at server) or negative validity (fingerprint not known at server, but not really an error). """ try: self.poll() self.unknown = False except urllib2.HTTPError as err: # We can't really say anything about the validity of # fingerprints not found -- but the artifact can still # exist, so it is not possible to definitely say they are # valid or not. if err.code == 404: self.unknown = True return True return False return True def validate_for_build(self, filename, job, build): if not self.valid(): log.info("Unknown to jenkins.") return False if self.unknown: # not request error, but unknown to jenkins return True if not self._data["original"] is None: if self._data["original"]["name"] == job: if self._data["original"]["number"] == build: return True if self._data["fileName"] != filename: log.info(msg="Filename from jenkins (%s) did not match provided (%s)" % (self._data["fileName"], filename)) return False for usage_item in self._data["usage"]: if usage_item["name"] == job: for range_ in usage_item["ranges"]["ranges"]: if range_["start"] <= build <= range_["end"]: msg = "This artifact was generated by %s between build %i and %i" % \ (job, range_["start"], range_["end"]) log.info(msg=msg) return True return False def validate(self): try: assert self.valid() except AssertionError: raise ArtifactBroken("Artifact %s seems to be broken, check %s" % (self.id_, self.baseurl)) except urllib2.HTTPError: raise ArtifactBroken("Unable to validate artifact id %s using %s" % (self.id_, self.baseurl)) return True def get_info(self): """ Returns a tuple of build-name, build# and artifiact filename for a good build. """ self.poll() return self._data["original"]["name"], self._data["original"]["number"], self._data["fileName"] jenkinsapi-0.2.16/jenkinsapi/mutable_jenkins_thing.py0000644000175000017500000000047312262651567021301 0ustar ahs3ahs3""" Module for MutableJenkinsThing """ class MutableJenkinsThing(object): """ A mixin for certain mutable objects which can be renamed and deleted. """ def get_delete_url(self): return '%s/doDelete' % self.baseurl def get_rename_url(self): return '%s/doRename' % self.baseurl jenkinsapi-0.2.16/jenkinsapi/jobs.py0000644000175000017500000000777712262651567015711 0ustar ahs3ahs3""" This module implements the Jobs class, which is intended to be a container-like interface for all of the jobs defined on a single Jenkins server. """ import logging from jenkinsapi.job import Job from jenkinsapi.custom_exceptions import JenkinsAPIException, UnknownJob log = logging.getLogger(__name__) class Jobs(object): """ This class provides a container-like API which gives access to all jobs defined on the Jenkins server. It behaves like a dict in which keys are Job-names and values are actual jenkinsapi.Job objects. """ def __init__(self, jenkins): self.jenkins = jenkins def __len__(self): return len(self.keys) def __delitem__(self, job_name): """ Delete a job by name :param job_name: name of a exist job, str """ if job_name in self: delete_job_url = self[job_name].get_delete_url() self.jenkins.requester.post_and_confirm_status( delete_job_url, data='some random bytes...' ) self.jenkins.poll() def __setitem__(self, key, value): raise NotImplementedError() def __getitem__(self, job_name): for row in self.jenkins._data.get('jobs', []): if row['name'] == job_name: return Job( row['url'], row['name'], self.jenkins) raise UnknownJob(job_name) def iteritems(self): """ Get the names & objects for all jobs """ self.jenkins.poll() for row in self.jenkins._data.get('jobs', []): name = row['name'] url = row['url'] yield name, Job(url, name, self.jenkins) def __contains__(self, job_name): """ True if job_name is the name of a defined job """ return job_name in self.keys() def iterkeys(self): """ Get the names of all available views """ for row in self.jenkins._data.get('jobs', []): yield row['name'] def keys(self): """ Return a list of the names of all jobs """ return list(self.iterkeys()) def create(self, job_name, config): """ Create a job :param jobname: name of new job, str :param config: configuration of new job, xml :return: new Job obj """ if job_name in self: return self[job_name] params = {'name': job_name} if isinstance(config, unicode): config = str(config) self.jenkins.requester.post_xml_and_confirm_status( self.jenkins.get_create_url(), data=config, params=params ) self.jenkins.poll() if job_name not in self: raise JenkinsAPIException('Cannot create job %s' % job_name) return self[job_name] def copy(self, job_name, new_job_name): """ Copy a job :param job_name: name of a exist job, str :param new_job_name: name of new job, str :return: new Job obj """ params = {'name': new_job_name, 'mode': 'copy', 'from': job_name} self.jenkins.requester.post_and_confirm_status( self.jenkins.get_create_url(), params=params, data='') self.jenkins.poll() return self[new_job_name] def rename(self, job_name, new_job_name): """ Rename a job :param job_name: name of a exist job, str :param new_job_name: name of new job, str :return: new Job obj """ params = {'newName': new_job_name} rename_job_url = self[job_name].get_rename_url() self.jenkins.requester.post_and_confirm_status( rename_job_url, params=params, data='') self.jenkins.poll() return self[new_job_name] def build(self, job_name, params): assert isinstance(params, dict) self[job_name].invoke(build_params=params) jenkinsapi-0.2.16/jenkinsapi/plugins.py0000644000175000017500000000275312262651567016422 0ustar ahs3ahs3""" jenkinsapi plugins """ import logging from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.plugin import Plugin log = logging.getLogger(__name__) class Plugins(JenkinsBase): """ Plugins class for jenkinsapi """ def __init__(self, url, jenkins_obj): self.jenkins_obj = jenkins_obj JenkinsBase.__init__(self, url) # print 'DEBUG: Plugins._data=', self._data def get_jenkins_obj(self): return self.jenkins_obj def _poll(self): return self.get_data(self.baseurl) def keys(self): return self.get_plugins_dict().keys() def iteritems(self): return self._get_plugins() def values(self): return [a[1] for a in self.iteritems()] def _get_plugins(self): if not 'plugins' in self._data: pass else: for p_dict in self._data["plugins"]: yield p_dict["shortName"], Plugin(p_dict) def get_plugins_dict(self): return dict(self._get_plugins()) def __len__(self): return len(self.get_plugins_dict().keys()) def __getitem__(self, plugin_name): return self.get_plugins_dict().get(plugin_name, None) def __contains__(self, plugin_name): """ True if plugin_name is the name of a defined plugin """ return plugin_name in self.keys() def __str__(self): plugins = [plugin["shortName"] for plugin in self._data.get("plugins", [])] return str(sorted(plugins)) jenkinsapi-0.2.16/jenkinsapi_tests/0000755000175000017500000000000012262651567015602 5ustar ahs3ahs3jenkinsapi-0.2.16/jenkinsapi_tests/systests/0000755000175000017500000000000012262651567017503 5ustar ahs3ahs3jenkinsapi-0.2.16/jenkinsapi_tests/systests/test_downstream_upstream.py0000644000175000017500000000707312262651567025226 0ustar ahs3ahs3''' System tests for `jenkinsapi.jenkins` module. ''' import time import logging import unittest from jenkinsapi.custom_exceptions import NoBuildData from jenkinsapi_tests.systests.base import BaseSystemTest log = logging.getLogger(__name__) JOB_CONFIGS = { 'A': """ false true false false false false B SUCCESS 0 BLUE """, 'B': """ false true false false false false C SUCCESS 0 BLUE """, 'C': """ false true false false false false """ } class TestDownstreamUpstream(BaseSystemTest): DELAY = 10 def test_stream_relationship(self): """ Can we keep track of the relationships between upstream & downstream jobs? """ for job_name, job_config in JOB_CONFIGS.items(): self.jenkins.create_job(job_name, job_config) self.jenkins['A'].invoke() for _ in xrange(10): try: self.jenkins['C'].get_last_completed_buildnumber() > 0 except NoBuildData: log.info("Waiting %i seconds for until the final job has run", self.DELAY) time.sleep(self.DELAY) else: break else: self.fail('Jenkins took too long to run these jobs') self.assertTrue(self.jenkins[ 'C'].get_upstream_jobs(), self.jenkins['B']) self.assertTrue(self.jenkins[ 'B'].get_upstream_jobs(), self.jenkins['A']) self.assertTrue(self.jenkins[ 'A'].get_downstream_jobs(), self.jenkins['B']) self.assertTrue(self.jenkins[ 'B'].get_downstream_jobs(), self.jenkins['C']) if __name__ == '__main__': logging.basicConfig() unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/systests/get-jenkins-war.sh0000755000175000017500000000014712262651567023051 0ustar ahs3ahs3#!/bin/sh JENKINS_WAR_URL="http://mirrors.jenkins-ci.org/war/latest/jenkins.war" wget $JENKINS_WAR_URL jenkinsapi-0.2.16/jenkinsapi_tests/systests/base.py0000644000175000017500000000240712262651567020772 0ustar ahs3ahs3import unittest import jenkinsapi_tests.systests from jenkinsapi_tests.systests.job_configs import EMPTY_JOB from jenkinsapi.jenkins import Jenkins class BaseSystemTest(unittest.TestCase): def setUp(self): port = jenkinsapi_tests.systests.state['launcher'].http_port self.jenkins = Jenkins('http://localhost:%d' % port) self._delete_all_jobs() self._delete_all_views() def tearDown(self): pass def _delete_all_jobs(self): self.jenkins.poll() for name in self.jenkins.get_jobs_list(): self.jenkins.delete_job(name) def _delete_all_views(self): all_view_names = self.jenkins.views.keys()[1:] for name in all_view_names: del self.jenkins.views[name] def _create_job(self, name='whatever', config=EMPTY_JOB): job = self.jenkins.create_job(name, config) self.jenkins.poll() return job def assertJobIsPresent(self, name): self.jenkins.poll() self.assertTrue(name in self.jenkins, 'Job %r is absent in jenkins.' % name) def assertJobIsAbsent(self, name): self.jenkins.poll() self.assertTrue(name not in self.jenkins, 'Job %r is present in jenkins.' % name) jenkinsapi-0.2.16/jenkinsapi_tests/systests/config.xml0000644000175000017500000000226612262651567021500 0ustar ahs3ahs3 1.0 2 NORMAL true ${JENKINS_HOME}/workspace/${ITEM_FULLNAME} ${ITEM_ROOTDIR}/builds 0 All false false All 0 jenkinsapi-0.2.16/jenkinsapi_tests/systests/job_configs.py0000644000175000017500000002042012262651567022335 0ustar ahs3ahs3""" A selection of job objects used in testing. """ EMPTY_JOB = '''\ false true false false false false '''.strip() LONG_RUNNING_JOB = """ false true false false false false ping -c 200 localhost """.strip() SHORTISH_JOB = """ false true false false false false ping -c 10 localhost """.strip() SCM_GIT_JOB = """ false 2 https://github.com/salimfadhley/jenkinsapi.git ** false false false false false false false false false false Default true true false false false false """.strip() JOB_WITH_ARTIFACTS = """ Ping a load of stuff for about 10s false true false false false false ping -c 5 localhost | tee out.txt gzip < out.txt > out.gz *.txt,*.gz false true """.strip() MATRIX_JOB = """ false true false false false false foo one two three ping -c 10 localhost """.strip() JOB_WITH_FILE = """ false file.txt true false false false false cat file.txt * false """.strip() JOB_WITH_PARAMETERS = """ A build that explores the wonderous possibilities of parameterized builds. false B B, like buzzing B. true false false false false ping -c 1 localhost | tee out.txt echo $A > a.txt echo $B > b.txt * false true """.strip() jenkinsapi-0.2.16/jenkinsapi_tests/systests/test_scm.py0000644000175000017500000000164412262651567021703 0ustar ahs3ahs3# ''' # System tests for `jenkinsapi.jenkins` module. # ''' # import unittest # from jenkinsapi_tests.systests.base import BaseSystemTest # from jenkinsapi_tests.test_utils.random_strings import random_string # from jenkinsapi_tests.systests.job_configs import SCM_GIT_JOB # # Maybe have a base class for all SCM test activites? # class TestSCMGit(BaseSystemTest): # # Maybe it makes sense to move plugin dependencies outside the code. # # Have a config to dependencies mapping from the launcher can use to install plugins. # def test_get_revision(self): # job_name = 'git_%s' % random_string() # job = self.jenkins.create_job(job_name, SCM_GIT_JOB) # ii = job.invoke() # ii.block(until='completed') # self.assertFalse(ii.is_running()) # b = ii.get_build() # self.assertIsInstance(b.get_revision(), basestring) # if __name__ == '__main__': # unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/systests/test_queue.py0000644000175000017500000000313412262651567022241 0ustar ahs3ahs3''' System tests for `jenkinsapi.jenkins` module. ''' import time import logging import unittest from jenkinsapi.queue import Queue from jenkinsapi_tests.systests.base import BaseSystemTest from jenkinsapi_tests.test_utils.random_strings import random_string from jenkinsapi_tests.systests.job_configs import LONG_RUNNING_JOB log = logging.getLogger(__name__) class TestQueue(BaseSystemTest): """ All kinds of testing on Jenkins Queues """ # TODO: Test timeout behavior def test_get_queue(self): qq = self.jenkins.get_queue() self.assertIsInstance(qq, Queue) def test_invoke_many_jobs(self): job_names = [random_string() for _ in range(5)] jobs = [] for job_name in job_names: j = self.jenkins.create_job(job_name, LONG_RUNNING_JOB) jobs.append(j) j.invoke() self.assertTrue(j.is_queued_or_running()) queue = self.jenkins.get_queue() reprString = repr(queue) self.assertIn(queue.baseurl, reprString) for _, item in queue.iteritems(): queue.delete_item(item) queue.poll() self.assertEquals(len(queue), 0) def test_start_and_stop_long_running_job(self): job_name = random_string() j = self.jenkins.create_job(job_name, LONG_RUNNING_JOB) j.invoke() self.assertTrue(j.is_queued_or_running()) while j.is_queued(): time.sleep(0.5) j.get_first_build().stop() self.assertFalse(j.is_queued_or_running()) if __name__ == '__main__': logging.basicConfig() unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/systests/test_jenkins_matrix.py0000644000175000017500000000243512262651567024145 0ustar ahs3ahs3''' System tests for `jenkinsapi.jenkins` module. ''' import re import time import unittest from jenkinsapi_tests.systests.base import BaseSystemTest from jenkinsapi_tests.systests.job_configs import MATRIX_JOB from jenkinsapi_tests.test_utils.random_strings import random_string class TestMatrixJob(BaseSystemTest): def test_invoke_matrix_job(self): job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, MATRIX_JOB) job.invoke(block=True) build = job.get_last_build() while build.is_running(): time.sleep(1) set_of_groups = set() for run in build.get_matrix_runs(): self.assertEquals(run.get_number(), build.get_number()) self.assertEquals(run.get_upstream_build(), build) match_result = re.search(u'\xbb (.*) #\\d+$', run.name) self.assertIsNotNone(match_result) set_of_groups.add(match_result.group(1)) build.get_master_job_name() # This is a bad test, it simply verifies that this function does # not crash on a build from a matrix job. self.assertFalse(build.get_master_job_name()) self.assertEqual(set_of_groups, set(['one', 'two', 'three'])) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/systests/test_invocation.py0000644000175000017500000000520312262651567023265 0ustar ahs3ahs3''' System tests for `jenkinsapi.jenkins` module. ''' import unittest import time from jenkinsapi.build import Build from jenkinsapi.invocation import Invocation from jenkinsapi_tests.systests.base import BaseSystemTest from jenkinsapi_tests.test_utils.random_strings import random_string from jenkinsapi_tests.systests.job_configs import LONG_RUNNING_JOB from jenkinsapi_tests.systests.job_configs import SHORTISH_JOB, EMPTY_JOB class TestInvocation(BaseSystemTest): def test_invocation_object(self): job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, LONG_RUNNING_JOB) ii = job.invoke(invoke_pre_check_delay=7) self.assertIsInstance(ii, Invocation) # Let Jenkins catchup time.sleep(3) self.assertTrue(ii.is_queued_or_running()) self.assertEquals(ii.get_build_number(), 1) def test_get_block_until_build_running(self): job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, LONG_RUNNING_JOB) ii = job.invoke(invoke_pre_check_delay=7) time.sleep(3) bn = ii.get_build_number() self.assertIsInstance(bn, int) ii.block(until='not_queued') self.assertTrue(ii.is_running()) b = ii.get_build() self.assertIsInstance(b, Build) ii.stop() self.assertFalse(ii.is_running()) self.assertIsInstance(ii.get_build().get_console(), str) self.assertIn('Started by user', ii.get_build().get_console()) def test_get_block_until_build_complete(self): job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, SHORTISH_JOB) ii = job.invoke() ii.block(until='completed') self.assertFalse(ii.is_running()) def test_multiple_invocations_and_get_last_build(self): job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, SHORTISH_JOB) for _ in range(3): ii = job.invoke() ii.block(until='completed') build_number = job.get_last_good_buildnumber() self.assertEquals(build_number, 3) build = job.get_build(build_number) self.assertIsInstance(build, Build) def test_multiple_invocations_and_get_build_number(self): job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, EMPTY_JOB) for invocation in range(3): ii = job.invoke() ii.block(until='completed') build_number = ii.get_build_number() self.assertEquals(build_number, invocation + 1) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/systests/__init__.py0000644000175000017500000000117112262651567021614 0ustar ahs3ahs3import os from jenkinsapi_utils.jenkins_launcher import JenkinsLancher state = {} # Extra plugins required by the systests PLUGIN_DEPENDENCIES = ["http://updates.jenkins-ci.org/latest/git.hpi", "http://updates.jenkins-ci.org/latest/git-client.hpi", "https://updates.jenkins-ci.org/latest/nested-view.hpi"] def setUpPackage(): systests_dir, _ = os.path.split(__file__) war_path = os.path.join(systests_dir, 'jenkins.war') state['launcher'] = JenkinsLancher(war_path, PLUGIN_DEPENDENCIES) state['launcher'].start() def tearDownPackage(): state['launcher'].stop() jenkinsapi-0.2.16/jenkinsapi_tests/systests/test_executors.py0000644000175000017500000000411712262651567023140 0ustar ahs3ahs3''' System tests for `jenkinsapi.jenkins` module. ''' from jenkinsapi_tests.systests.base import BaseSystemTest from jenkinsapi_tests.systests.job_configs import LONG_RUNNING_JOB from jenkinsapi_tests.test_utils.random_strings import random_string import logging import time import unittest log = logging.getLogger(__name__) class TestNodes(BaseSystemTest): def test_get_executors(self): node_name = random_string() self.jenkins.create_node(node_name) executors = self.jenkins.get_executors(node_name) self.assertEqual(executors.count, 2) for count, execs in enumerate(executors): self.assertEqual(count, execs.get_number()) self.assertEqual(execs.is_idle(), True) def test_running_executor(self): node_name = random_string() self.jenkins.create_node(node_name) job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, LONG_RUNNING_JOB) ii = job.invoke(invoke_pre_check_delay=2) ii.block(until='not_queued') if job.is_running() is False: time.sleep(1) executors = self.jenkins.get_executors(node_name) all_idle = True for execs in executors: if execs.is_idle() is False: all_idle = False self.assertNotEqual(execs.get_progress(), -1) self.assertEqual(execs.get_current_executable(), ii.get_build_number()) self.assertEqual(execs.likely_stuck(), False) self.assertEqual(all_idle, True, "Executor should have been triggered.") def test_idle_executors(self): node_name = random_string() self.jenkins.create_node(node_name) executors = self.jenkins.get_executors(node_name) for execs in executors: self.assertEqual(execs.get_progress(), -1) self.assertEqual(execs.get_current_executable(), None) self.assertEqual(execs.likely_stuck(), False) self.assertEqual(execs.is_idle(), True) if __name__ == '__main__': logging.basicConfig() unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/systests/test_parameterized_builds.py0000644000175000017500000000621112262651567025312 0ustar ahs3ahs3''' System tests for `jenkinsapi.jenkins` module. ''' import time import unittest from StringIO import StringIO from jenkinsapi_tests.systests.base import BaseSystemTest from jenkinsapi_tests.test_utils.random_strings import random_string from jenkinsapi_tests.systests.job_configs import JOB_WITH_FILE from jenkinsapi_tests.systests.job_configs import JOB_WITH_PARAMETERS from jenkinsapi.custom_exceptions import WillNotBuild class TestParameterizedBuilds(BaseSystemTest): def test_invoke_job_with_file(self): file_data = random_string() param_file = StringIO(file_data) job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, JOB_WITH_FILE) job.invoke(block=True, files={'file.txt': param_file}) b = job.get_last_build() while b.is_running(): time.sleep(0.25) artifacts = b.get_artifact_dict() self.assertIsInstance(artifacts, dict) art_file = artifacts['file.txt'] self.assertTrue(art_file.get_data().strip(), file_data) def test_invoke_job_parameterized(self): param_B = random_string() job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, JOB_WITH_PARAMETERS) job.invoke(block=True, build_params={'B': param_B}) b = job.get_last_build() while b.is_running(): time.sleep(0.25) artifacts = b.get_artifact_dict() self.assertIsInstance(artifacts, dict) artB = artifacts['b.txt'] self.assertTrue(artB.get_data().strip(), param_B) self.assertIn(param_B, b.get_console()) def test_parameterized_job_build_queuing(self): """Accept multiple builds of parameterized jobs with unique parameters.""" job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, JOB_WITH_PARAMETERS) for i in range(3): param_B = random_string() params = {'B': param_B} job.invoke(build_params=params) self.assertTrue(job.has_queued_build(params)) while(job.has_queued_build(params)): time.sleep(0.25) b = job.get_last_build() while b.is_running(): time.sleep(0.25) artifacts = b.get_artifact_dict() self.assertIsInstance(artifacts, dict) artB = artifacts['b.txt'] self.assertTrue(artB.get_data().strip(), param_B) self.assertIn(param_B, b.get_console()) def test_parameterized_job_build_rejection(self): """Reject build of paramterized job when existing build with same parameters is queued, raising WillNotBuild.""" job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, JOB_WITH_PARAMETERS) for i in range(3): params = {'B': random_string()} job.invoke(build_params=params) with self.assertRaises(WillNotBuild) as na: job.invoke(build_params=params) expected_msg = 'A build with these parameters is already queued.' self.assertEqual(na.exception.message, expected_msg) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/systests/test_jenkins.py0000644000175000017500000000602412262651567022557 0ustar ahs3ahs3''' System tests for `jenkinsapi.jenkins` module. ''' import unittest from jenkinsapi.job import Job from jenkinsapi.invocation import Invocation from jenkinsapi_tests.systests.base import BaseSystemTest from jenkinsapi_tests.systests.job_configs import EMPTY_JOB from jenkinsapi_tests.test_utils.random_strings import random_string class JobTests(BaseSystemTest): def test_create_job(self): job_name = 'create_%s' % random_string() self.jenkins.create_job(job_name, EMPTY_JOB) self.assertJobIsPresent(job_name) def test_enable_disable_job(self): job_name = 'create_%s' % random_string() self.jenkins.create_job(job_name, EMPTY_JOB) self.assertJobIsPresent(job_name) j = self.jenkins[job_name] j.invoke(block=True) # run this at least once j.disable() self.assertEquals(j.is_enabled(), False, 'A disabled job is reporting incorrectly') j.enable() self.assertEquals(j.is_enabled(), True, 'An enabled job is reporting incorrectly') def test_get_job_and_update_config(self): job_name = 'config_%s' % random_string() self.jenkins.create_job(job_name, EMPTY_JOB) self.assertJobIsPresent(job_name) config = self.jenkins[job_name].get_config() self.assertEquals(config.strip(), EMPTY_JOB.strip()) self.jenkins[job_name].update_config(EMPTY_JOB) def test_invoke_job(self): job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, EMPTY_JOB) job.invoke() def test_invocation_object(self): job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, EMPTY_JOB) ii = job.invoke() self.assertIsInstance(ii, Invocation) def test_get_jobs_list(self): job1_name = 'first_%s' % random_string() job2_name = 'second_%s' % random_string() self._create_job(job1_name) self._create_job(job2_name) job_list = self.jenkins.get_jobs_list() self.assertEqual([job1_name, job2_name], job_list) def test_delete_job(self): job1_name = 'delete_me_%s' % random_string() self._create_job(job1_name) self.jenkins.delete_job(job1_name) self.assertJobIsAbsent(job1_name) def test_rename_job(self): job1_name = 'A__%s' % random_string() job2_name = 'B__%s' % random_string() self._create_job(job1_name) self.jenkins.rename_job(job1_name, job2_name) self.assertJobIsAbsent(job1_name) self.assertJobIsPresent(job2_name) def test_copy_job(self): template_job_name = 'TPL%s' % random_string() copied_job_name = 'CPY%s' % random_string() self._create_job(template_job_name) j = self.jenkins.copy_job(template_job_name, copied_job_name) self.assertJobIsPresent(template_job_name) self.assertJobIsPresent(copied_job_name) self.assertIsInstance(j, Job) self.assertEquals(j.name, copied_job_name) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/systests/test_jenkins_artifacts.py0000644000175000017500000000323412262651567024617 0ustar ahs3ahs3''' System tests for `jenkinsapi.jenkins` module. ''' import os import re import time import gzip import shutil import tempfile import unittest from jenkinsapi_tests.systests.base import BaseSystemTest from jenkinsapi_tests.systests.job_configs import JOB_WITH_ARTIFACTS from jenkinsapi_tests.test_utils.random_strings import random_string class TestPingerJob(BaseSystemTest): def test_invoke_job(self): job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, JOB_WITH_ARTIFACTS) job.invoke(block=True) b = job.get_last_build() while b.is_running(): time.sleep(1) artifacts = b.get_artifact_dict() self.assertIsInstance(artifacts, dict) text_artifact = artifacts['out.txt'] binary_artifact = artifacts['out.gz'] tempDir = tempfile.mkdtemp() try: # Verify that we can handle text artifacts text_artifact.save_to_dir(tempDir) readBackText = open(os.path.join( tempDir, text_artifact.filename), 'rb').read().strip() self.assertTrue(re.match(r'^PING \S+ \(127.0.0.1\)', readBackText)) self.assertTrue(readBackText.endswith('ms')) # Verify that we can hande binary artifacts binary_artifact.save_to_dir(tempDir) readBackText = gzip.open(os.path.join(tempDir, binary_artifact.filename,), 'rb').read().strip() self.assertTrue(re.match(r'^PING \S+ \(127.0.0.1\)', readBackText)) self.assertTrue(readBackText.endswith('ms')) finally: shutil.rmtree(tempDir) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/systests/test_views.py0000644000175000017500000000577412262651567022266 0ustar ahs3ahs3''' System tests for `jenkinsapi.jenkins` module. ''' import logging import unittest from jenkinsapi.view import View from jenkinsapi.views import Views from jenkinsapi.api import get_view_from_url from jenkinsapi_tests.systests.base import BaseSystemTest from jenkinsapi_tests.test_utils.random_strings import random_string log = logging.getLogger(__name__) class TestViews(BaseSystemTest): def test_make_views(self): self._create_job() view_name = random_string() self.assertNotIn(view_name, self.jenkins.views) v = self.jenkins.views.create(view_name) self.assertIn(view_name, self.jenkins.views) self.assertIsInstance(v, View) # Can we use the API convenience methods v2 = get_view_from_url(v.baseurl) self.assertEquals(v, v2) def test_create_and_delete_views(self): self._create_job() view1_name = random_string() new_view = self.jenkins.views.create(view1_name) self.assertIsInstance(new_view, View) self.assertIn(view1_name, self.jenkins.views) del self.jenkins.views[view1_name] self.assertNotIn(view1_name, self.jenkins.views) def test_create_and_delete_views_by_url(self): self._create_job() view1_name = random_string() new_view = self.jenkins.views.create(view1_name) self.assertIsInstance(new_view, View) self.assertIn(view1_name, self.jenkins.views) view_url = new_view.baseurl view_by_url = self.jenkins.get_view_by_url(view_url) self.assertIsInstance(view_by_url, View) self.jenkins.delete_view_by_url(view_url) self.assertNotIn(view1_name, self.jenkins.views) def test_delete_view_which_does_not_exist(self): self._create_job() view1_name = random_string() new_view = self.jenkins.views.create(view1_name) self.assertIn(view1_name, self.jenkins.views) del self.jenkins.views[view1_name] self.assertNotIn(view1_name, self.jenkins.views) def test_make_nested_views(self): job = self._create_job() top_view_name = random_string() sub1_view_name = random_string() sub2_view_name = random_string() self.assertNotIn(top_view_name, self.jenkins.views) tv = self.jenkins.views.create(top_view_name, Views.NESTED_VIEW) self.assertIn(top_view_name, self.jenkins.views) self.assertIsInstance(tv, View) # Empty sub view sv1 = tv.views.create(sub1_view_name) self.assertIn(sub1_view_name, tv.views) self.assertIsInstance(sv1, View) # Sub view with job in it tv.views[sub2_view_name] = job.name self.assertIn(sub2_view_name, tv.views) sv2 = tv.views[sub2_view_name] self.assertIsInstance(sv2, View) self.assertTrue(job.name in sv2) # Can we use the API convenience methods v = get_view_from_url(sv2.baseurl) self.assertEquals(v, sv2) if __name__ == '__main__': logging.basicConfig() unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/systests/test_nodes.py0000644000175000017500000000261312262651567022226 0ustar ahs3ahs3''' System tests for `jenkinsapi.jenkins` module. ''' import logging import unittest from jenkinsapi_tests.systests.base import BaseSystemTest from jenkinsapi_tests.test_utils.random_strings import random_string log = logging.getLogger(__name__) class TestNodes(BaseSystemTest): def test_invoke_job_parameterized(self): node_name = random_string() self.jenkins.create_node(node_name) self.assertTrue(self.jenkins.has_node(node_name)) N = self.jenkins.get_node(node_name) self.assertEquals(N.baseurl, self.jenkins.get_node_url(node_name)) self.jenkins.delete_node(node_name) self.assertFalse(self.jenkins.has_node(node_name)) def test_online_offline(self): """ Can we flip the online / offline state of the master node. """ # Master node name should be case insensitive # mn0 = self.jenkins.get_node('MaStEr') mn = self.jenkins.get_node('master') # self.assertEquals(mn, mn0) mn.set_online() # It should already be online, hence no-op self.assertTrue(mn.is_online()) mn.set_offline() # We switch that suckah off mn.set_offline() # This should be a no-op self.assertFalse(mn.is_online()) mn.set_online() # Switch it back on self.assertTrue(mn.is_online()) if __name__ == '__main__': logging.basicConfig() unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/__init__.py0000644000175000017500000000000012262651567017701 0ustar ahs3ahs3jenkinsapi-0.2.16/jenkinsapi_tests/test_utils/0000755000175000017500000000000012262651567020001 5ustar ahs3ahs3jenkinsapi-0.2.16/jenkinsapi_tests/test_utils/__init__.py0000644000175000017500000000000012262651567022100 0ustar ahs3ahs3jenkinsapi-0.2.16/jenkinsapi_tests/test_utils/random_strings.py0000644000175000017500000000030312262651567023400 0ustar ahs3ahs3import random import string def random_string(length=10): return ''.join(random.choice(string.ascii_lowercase) for i in range(length)) if __name__ == '__main__': print random_string() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/0000755000175000017500000000000012262651567017644 5ustar ahs3ahs3jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_job_multiconf.py0000644000175000017500000001636112262651567024116 0ustar ahs3ahs3# import mock # import unittest # from jenkinsapi.job import Job # from jenkinsapi.jenkinsbase import JenkinsBase # from jenkinsapi.custom_exceptions import NoBuildData # class TestMultiConfigJob(unittest.TestCase): # JOB_DATA = {'actions': [{'causes': [{'shortDescription': 'Started by user anonymous', # 'userId': None, # 'userName': 'anonymous'}]}], # 'artifacts': [], # 'building': False, # 'builtOn': '', # 'changeSet': {'items': [], 'kind': None}, # 'culprits': [], # 'description': None, # 'duration': 1042, # 'estimatedDuration': 1055, # 'executor': None, # 'fullDisplayName': 'test_multiconf0 #16', # 'id': '2013-07-17_00-39-50', # 'keepLog': False, # 'number': 16, # 'result': 'SUCCESS', # 'runs': [{'number': 10, # 'url': 'http://halob:8080/job/test_multiconf0/./a=1,b=a/10/'}, # {'number': 10, # 'url': 'http://halob:8080/job/test_multiconf0/./a=1,b=b/10/'}, # {'number': 10, # 'url': 'http://halob:8080/job/test_multiconf0/./a=1,b=c/10/'}, # {'number': 10, # 'url': 'http://halob:8080/job/test_multiconf0/./a=2,b=a/10/'}, # {'number': 10, # 'url': 'http://halob:8080/job/test_multiconf0/./a=2,b=b/10/'}, # {'number': 10, # 'url': 'http://halob:8080/job/test_multiconf0/./a=2,b=c/10/'}, # {'number': 10, # 'url': 'http://halob:8080/job/test_multiconf0/./a=3,b=a/10/'}, # {'number': 10, # 'url': 'http://halob:8080/job/test_multiconf0/./a=3,b=b/10/'}, # {'number': 10, # 'url': 'http://halob:8080/job/test_multiconf0/./a=3,b=c/10/'}, # {'number': 16, # 'url': 'http://halob:8080/job/test_multiconf0/./a=q,b=x/16/'}, # {'number': 16, # 'url': 'http://halob:8080/job/test_multiconf0/./a=q,b=y/16/'}, # {'number': 16, # 'url': 'http://halob:8080/job/test_multiconf0/./a=q,b=z/16/'}, # {'number': 16, # 'url': 'http://halob:8080/job/test_multiconf0/./a=r,b=x/16/'}, # {'number': 16, # 'url': 'http://halob:8080/job/test_multiconf0/./a=r,b=y/16/'}, # {'number': 16, # 'url': 'http://halob:8080/job/test_multiconf0/./a=r,b=z/16/'}, # {'number': 16, # 'url': 'http://halob:8080/job/test_multiconf0/./a=s,b=x/16/'}, # {'number': 16, # 'url': 'http://halob:8080/job/test_multiconf0/./a=s,b=y/16/'}, # {'number': 16, # 'url': 'http://halob:8080/job/test_multiconf0/./a=s,b=z/16/'}], # 'timestamp': 1374017990735, # 'url': 'http://halob:8080/job/test_multiconf0/16/'} # URL_DATA = {'http://halob:8080/job/foo/api/python/':JOB_DATA} # def fakeGetData(self, url, *args): # try: # return TestJob.URL_DATA[url] # except KeyError: # raise Exception("Missing data for %s" % url) # @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) # def setUp(self): # self.J = mock.MagicMock() # Jenkins object # self.j = Job('http://halob:8080/job/foo/', 'foo', self.J) # def testRepr(self): # # Can we produce a repr string for this object # self.assertEquals(repr(self.j), '') # def testName(self): # with self.assertRaises(AttributeError): # self.j.id() # self.assertEquals(self.j.name, 'foo') # def testNextBuildNumber(self): # self.assertEquals(self.j.get_next_build_number(), 4) # def test_special_urls(self): # self.assertEquals(self.j.baseurl, 'http://halob:8080/job/foo') # self.assertEquals( # self.j.get_delete_url(), 'http://halob:8080/job/foo/doDelete') # self.assertEquals( # self.j.get_rename_url(), 'http://halob:8080/job/foo/doRename') # def test_get_description(self): # self.assertEquals(self.j.get_description(), 'test job') # def test_get_build_triggerurl(self): # self.assertEquals( # self.j.get_build_triggerurl(), 'http://halob:8080/job/foo/build') # def test_wrong__mk_json_from_build_parameters(self): # with self.assertRaises(AssertionError) as ar: # self.j._mk_json_from_build_parameters(build_params='bad parameter') # self.assertEquals( # ar.exception.message, 'Build parameters must be a dict') # def test__mk_json_from_build_parameters(self): # params = {'param1': 'value1', 'param2': 'value2'} # ret = self.j.mk_json_from_build_parameters(build_params=params) # self.assertTrue(isinstance(ret, str)) # self.assertEquals(ret, # '{"parameter": [{"name": "param2", "value": "value2"}, {"name": "param1", "value": "value1"}]}') # def test_wrong_mk_json_from_build_parameters(self): # with self.assertRaises(AssertionError) as ar: # self.j.mk_json_from_build_parameters(build_params='bad parameter') # self.assertEquals( # ar.exception.message, 'Build parameters must be a dict') # @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) # def test_wrong_field__build_id_for_type(self): # with self.assertRaises(AssertionError): # self.j._buildid_for_type('wrong') # @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) # def test_get_last_good_buildnumber(self): # ret = self.j.get_last_good_buildnumber() # self.assertTrue(ret, 3) # @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) # def test_get_last_failed_buildnumber(self): # with self.assertRaises(NoBuildData): # self.j.get_last_failed_buildnumber() # @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) # def test_get_last_buildnumber(self): # ret = self.j.get_last_buildnumber() # self.assertEquals(ret, 3) # @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) # def test_get_last_completed_buildnumber(self): # ret = self.j.get_last_completed_buildnumber() # self.assertEquals(ret, 3) # def test_get_build_dict(self): # ret = self.j.get_build_dict() # self.assertTrue(isinstance(ret, dict)) # self.assertEquals(len(ret), 3) # @mock.patch.object(Job, '_poll') # def test_nobuilds_get_build_dict(self, _poll): # # Bare minimum build dict, we only testing dissapearance of 'builds' # _poll.return_value = {"name": "foo"} # j = Job('http://halob:8080/job/foo/', 'foo', self.J) # with self.assertRaises(NoBuildData): # j.get_build_dict() # def test_get_build_ids(self): # # We don't want to deal with listreverseiterator here # # So we convert result to a list # ret = list(self.j.get_build_ids()) # self.assertTrue(isinstance(ret, list)) # self.assertEquals(len(ret), 3) # @mock.patch.object(Job, '_poll') # def test_nobuilds_get_revision_dict(self, _poll): # # Bare minimum build dict, we only testing dissapearance of 'builds' # _poll.return_value = {"name": "foo"} # j = Job('http://halob:8080/job/foo/', 'foo', self.J) # with self.assertRaises(NoBuildData): # j.get_revision_dict() # if __name__ == '__main__': # unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_jenkinsbase.py0000644000175000017500000000016212262651567023550 0ustar ahs3ahs3class TestJenkinsBaseMixin(object): """ Tests which apply to all or most Jenkins objects """ pass jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_job.py0000644000175000017500000001611012262651567022026 0ustar ahs3ahs3import mock import unittest from jenkinsapi import config from jenkinsapi.job import Job from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.custom_exceptions import NoBuildData class TestJob(unittest.TestCase): JOB_DATA = { "actions": [], "description": "test job", "displayName": "foo", "displayNameOrNull": None, "name": "foo", "url": "http://halob:8080/job/foo/", "buildable": True, "builds": [ {"number": 3, "url": "http://halob:8080/job/foo/3/"}, {"number": 2, "url": "http://halob:8080/job/foo/2/"}, {"number": 1, "url": "http://halob:8080/job/foo/1/"} ], "color": "blue", "firstBuild": {"number": 1, "url": "http://halob:8080/job/foo/1/"}, "healthReport": [ {"description": "Build stability: No recent builds failed.", "iconUrl": "health-80plus.png", "score": 100} ], "inQueue": False, "keepDependencies": False, "lastBuild": {"number": 4, "url": "http://halob:8080/job/foo/4/"}, # build running "lastCompletedBuild": {"number": 3, "url": "http://halob:8080/job/foo/3/"}, "lastFailedBuild": None, "lastStableBuild": {"number": 3, "url": "http://halob:8080/job/foo/3/"}, "lastSuccessfulBuild": {"number": 3, "url": "http://halob:8080/job/foo/3/"}, "lastUnstableBuild": None, "lastUnsuccessfulBuild": None, "nextBuildNumber": 4, "property": [], "queueItem": None, "concurrentBuild": False, "downstreamProjects": [], "scm": {}, "upstreamProjects": [] } URL_DATA = {'http://halob:8080/job/foo/%s' % config.JENKINS_API: JOB_DATA} def fakeGetData(self, url, *args): try: return TestJob.URL_DATA[url] except KeyError: raise Exception("Missing data for %s" % url) @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) def setUp(self): self.J = mock.MagicMock() # Jenkins object self.j = Job('http://halob:8080/job/foo/', 'foo', self.J) def testRepr(self): # Can we produce a repr string for this object self.assertEquals(repr(self.j), '') def testName(self): with self.assertRaises(AttributeError): self.j.id() self.assertEquals(self.j.name, 'foo') def testNextBuildNumber(self): self.assertEquals(self.j.get_next_build_number(), 4) def test_special_urls(self): self.assertEquals(self.j.baseurl, 'http://halob:8080/job/foo') self.assertEquals( self.j.get_delete_url(), 'http://halob:8080/job/foo/doDelete') self.assertEquals( self.j.get_rename_url(), 'http://halob:8080/job/foo/doRename') def test_get_description(self): self.assertEquals(self.j.get_description(), 'test job') def test_get_build_triggerurl(self): self.assertEquals( self.j.get_build_triggerurl(), 'http://halob:8080/job/foo/build') def test_wrong__mk_json_from_build_parameters(self): with self.assertRaises(AssertionError) as ar: self.j._mk_json_from_build_parameters(build_params='bad parameter') self.assertEquals( ar.exception.message, 'Build parameters must be a dict') def test__mk_json_from_build_parameters(self): params = {'param1': 'value1', 'param2': 'value2'} ret = self.j.mk_json_from_build_parameters(build_params=params) self.assertTrue(isinstance(ret, str)) self.assertEquals(ret, '{"parameter": [{"name": "param2", "value": "value2"}, {"name": "param1", "value": "value1"}]}') def test_wrong_mk_json_from_build_parameters(self): with self.assertRaises(AssertionError) as ar: self.j.mk_json_from_build_parameters(build_params='bad parameter') self.assertEquals( ar.exception.message, 'Build parameters must be a dict') @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) def test_wrong_field__build_id_for_type(self): with self.assertRaises(AssertionError): self.j._buildid_for_type('wrong') @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) def test_get_last_good_buildnumber(self): ret = self.j.get_last_good_buildnumber() self.assertTrue(ret, 3) @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) def test_get_last_stable_buildnumber(self): ret = self.j.get_last_stable_buildnumber() self.assertTrue(ret, 3) @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) def test_get_last_failed_buildnumber(self): with self.assertRaises(NoBuildData): self.j.get_last_failed_buildnumber() @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) def test_get_last_buildnumber(self): ret = self.j.get_last_buildnumber() self.assertEquals(ret, 4) @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) def test_get_last_completed_buildnumber(self): ret = self.j.get_last_completed_buildnumber() self.assertEquals(ret, 3) def test_get_build_dict(self): ret = self.j.get_build_dict() self.assertTrue(isinstance(ret, dict)) self.assertEquals(len(ret), 4) @mock.patch.object(Job, '_poll') def test_nobuilds_get_build_dict(self, _poll): # Bare minimum build dict, we only testing dissapearance of 'builds' _poll.return_value = {"name": "foo"} j = Job('http://halob:8080/job/foo/', 'foo', self.J) with self.assertRaises(NoBuildData): j.get_build_dict() def test_get_build_ids(self): # We don't want to deal with listreverseiterator here # So we convert result to a list ret = list(self.j.get_build_ids()) self.assertTrue(isinstance(ret, list)) self.assertEquals(len(ret), 4) @mock.patch.object(Job, '_poll') def test_nobuilds_get_revision_dict(self, _poll): # Bare minimum build dict, we only testing dissapearance of 'builds' _poll.return_value = {"name": "foo"} j = Job('http://halob:8080/job/foo/', 'foo', self.J) with self.assertRaises(NoBuildData): j.get_revision_dict() @mock.patch.object(Job, '_poll') def test_nobuilds_get_last_build(self, _poll): # Bare minimum build dict, we only testing dissapearance of 'builds' _poll.return_value = {"name": "foo"} j = Job('http://halob:8080/job/foo/', 'foo', self.J) with self.assertRaises(NoBuildData): j.get_last_build() @mock.patch.object(JenkinsBase, 'get_data') def test_empty_field__add_missing_builds(self, get_data): url = 'http://halob:8080/job/foo/%s' % config.JENKINS_API data = TestJob.URL_DATA[url].copy() data.update({'firstBuild': None}) get_data.return_value = data j = Job('http://halob:8080/job/foo/', 'foo', self.J) initial_call_count = get_data.call_count j._add_missing_builds(data) self.assertEquals(get_data.call_count, initial_call_count) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_build.py0000644000175000017500000000542312262651567022360 0ustar ahs3ahs3import pytz import mock import unittest import datetime from jenkinsapi.build import Build class test_build(unittest.TestCase): DATA = { 'actions': [{'causes': [{'shortDescription': 'Started by user anonymous', 'userId': None, 'userName': 'anonymous'}]}], 'artifacts': [], 'building': False, 'builtOn': '', 'changeSet': {'items': [], 'kind': None}, 'culprits': [], 'description': None, "duration": 5782, 'estimatedDuration': 106, 'executor': None, "fingerprint": [{"fileName": "BuildId.json", "hash": "e3850a45ab64aa34c1aa66e30c1a8977", "original": {"name": "ArtifactGenerateJob", "number": 469}, "timestamp": 1380270162488, "usage": [{"name": "SingleJob", "ranges": {"ranges": [{"end": 567, "start": 566}]}}, {"name": "MultipleJobs", "ranges": {"ranges": [{"end": 150, "start": 139}]}}] }], 'fullDisplayName': 'foo #1', 'id': '2013-05-31_23-15-40', 'keepLog': False, 'number': 1, 'result': 'SUCCESS', 'timestamp': 1370042140000, 'url': 'http://localhost:8080/job/foo/1/'} @mock.patch.object(Build, '_poll') def setUp(self, _poll): _poll.return_value = self.DATA self.j = mock.MagicMock() # Job self.j.name = 'FooJob' self.b = Build('http://', 97, self.j) def test_timestamp(self): self.assertIsInstance(self.b.get_timestamp(), datetime.datetime) expected = pytz.utc.localize( datetime.datetime(2013, 5, 31, 23, 15, 40)) self.assertEqual(self.b.get_timestamp(), expected) def testName(self): with self.assertRaises(AttributeError): self.b.id() self.assertEquals(self.b.name, 'foo #1') def test_duration(self): expected = datetime.timedelta(milliseconds=5782) self.assertEquals(self.b.get_duration(), expected) self.assertEquals(self.b.get_duration().seconds, 5) self.assertEquals(self.b.get_duration().microseconds, 782000) self.assertEquals(str(self.b.get_duration()), '0:00:05.782000') ## TEST DISABLED - DOES NOT WORK # def test_downstream(self): # expected = ['SingleJob','MultipleJobs'] # self.assertEquals(self.b.get_downstream_job_names(), expected) def main(): unittest.main(verbosity=2) if __name__ == '__main__': main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_build_scm_git.py0000644000175000017500000001100712262651567024060 0ustar ahs3ahs3import mock import unittest from jenkinsapi.build import Build class test_build(unittest.TestCase): DATA = { 'actions': [ { 'causes': [{'shortDescription': 'Started by an SCM change'}] }, {}, { 'buildsByBranchName': { 'origin/HEAD': { 'buildNumber': 2, 'buildResult': None, 'revision': { 'SHA1': 'd2a5d435fa2df3bff572bd06e43c86544749c5d2', 'branch': [ {'SHA1': 'd2a5d435fa2df3bff572bd06e43c86544749c5d2', 'name': 'origin/HEAD'}, {'SHA1': 'd2a5d435fa2df3bff572bd06e43c86544749c5d2', 'name': 'origin/master'} ] } }, 'origin/master': { 'buildNumber': 2, 'buildResult': None, 'revision': { 'SHA1': 'd2a5d435fa2df3bff572bd06e43c86544749c5d2', 'branch': [ {'SHA1': 'd2a5d435fa2df3bff572bd06e43c86544749c5d2', 'name': 'origin/HEAD'}, {'SHA1': 'd2a5d435fa2df3bff572bd06e43c86544749c5d2', 'name': 'origin/master'} ] } }, 'origin/python_3_compatibility': { 'buildNumber': 1, 'buildResult': None, 'revision': { 'SHA1': 'c9d1c96bc926ff63a5209c51b3ed537e62ea50e6', 'branch': [ {'SHA1': 'c9d1c96bc926ff63a5209c51b3ed537e62ea50e6', 'name': 'origin/python_3_compatibility'} ] } }, 'origin/unstable': { 'buildNumber': 3, 'buildResult': None, 'revision': { 'SHA1': '7def9ed6e92580f37d00e4980c36c4d36e68f702', 'branch': [ {'SHA1': '7def9ed6e92580f37d00e4980c36c4d36e68f702', 'name': 'origin/unstable'} ] } } }, 'lastBuiltRevision': { 'SHA1': '7def9ed6e92580f37d00e4980c36c4d36e68f702', 'branch': [ {'SHA1': '7def9ed6e92580f37d00e4980c36c4d36e68f702', 'name': 'origin/unstable'} ] }, 'remoteUrls': ['https://github.com/salimfadhley/jenkinsapi.git'], 'scmName': '' }, {}, {} ], 'artifacts': [], 'building': False, 'builtOn': '', 'changeSet': {'items': [], 'kind': 'git'}, 'culprits': [], 'description': None, 'duration': 1051, 'estimatedDuration': 2260, 'executor': None, 'fullDisplayName': 'git_yssrtigfds #3', 'id': '2013-06-30_01-54-35', 'keepLog': False, 'number': 3, 'result': 'SUCCESS', 'timestamp': 1372553675652, 'url': 'http://localhost:8080/job/git_yssrtigfds/3/' } @mock.patch.object(Build, '_poll') def setUp(self, _poll): _poll.return_value = self.DATA self.j = mock.MagicMock() # Job self.j.name = 'FooJob' self.b = Build('http://', 97, self.j) def test_git_scm(self): """ Can we extract git build revision data from a build object? """ self.assertIsInstance(self.b.get_revision(), basestring) self.assertEquals(self.b.get_revision(), '7def9ed6e92580f37d00e4980c36c4d36e68f702') def test_git_revision_branch(self): """ Can we extract git build branch from a build object? """ self.assertIsInstance(self.b.get_revision_branch(), list) self.assertEquals(len(self.b.get_revision_branch()), 1) self.assertIsInstance(self.b.get_revision_branch()[0], dict) self.assertEquals(self.b.get_revision_branch()[0]['SHA1'], '7def9ed6e92580f37d00e4980c36c4d36e68f702') self.assertEquals(self.b.get_revision_branch()[0]['name'], 'origin/unstable') if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_node.py0000644000175000017500000000426412262651567022210 0ustar ahs3ahs3import mock import unittest from jenkinsapi.node import Node class TestNode(unittest.TestCase): DATA = {"actions": [], "displayName": "bobnit", "executors": [{}], "icon": "computer.png", "idle": True, "jnlpAgent": False, "launchSupported": True, "loadStatistics": {}, "manualLaunchAllowed": True, "monitorData": {"hudson.node_monitors.SwapSpaceMonitor": {"availablePhysicalMemory": 7681417216, "availableSwapSpace": 12195983360, "totalPhysicalMemory": 8374497280, "totalSwapSpace": 12195983360}, "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)", "hudson.node_monitors.ResponseTimeMonitor": {"average": 64}, "hudson.node_monitors.TemporarySpaceMonitor": {"path": "/tmp", "size": 250172776448}, "hudson.node_monitors.DiskSpaceMonitor": {"path": "/home/sal/jenkins", "size": 170472026112}, "hudson.node_monitors.ClockMonitor": {"diff": 6736}}, "numExecutors": 1, "offline": False, "offlineCause": None, "oneOffExecutors": [], "temporarilyOffline": False} @mock.patch.object(Node, '_poll') def setUp(self, _poll): _poll.return_value = self.DATA # def __init__(self, baseurl, nodename, jenkins_obj): self.J = mock.MagicMock() # Jenkins object self.n = Node('http://', 'bobnit', self.J) def testRepr(self): # Can we produce a repr string for this object repr(self.n) def testName(self): with self.assertRaises(AttributeError): self.n.id() self.assertEquals(self.n.name, 'bobnit') @mock.patch.object(Node, '_poll') def test_online(self, _poll): _poll.return_value = self.DATA return self.assertEquals(self.n.is_online(), True) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_job_get_all_builds.py0000644000175000017500000001706712262651567025073 0ustar ahs3ahs3import mock import unittest from jenkinsapi import config from jenkinsapi.job import Job from jenkinsapi.jenkinsbase import JenkinsBase class TestJobGetAllBuilds(unittest.TestCase): # this job has builds JOB1_DATA = { "actions": [], "description": "test job", "displayName": "foo", "displayNameOrNull": None, "name": "foo", "url": "http://halob:8080/job/foo/", "buildable": True, # do as if build 1 & 2 are not returned by jenkins "builds": [{"number": 3, "url": "http://halob:8080/job/foo/3/"}], "color": "blue", "firstBuild": {"number": 1, "url": "http://halob:8080/job/foo/1/"}, "healthReport": [ {"description": "Build stability: No recent builds failed.", "iconUrl": "health-80plus.png", "score": 100} ], "inQueue": False, "keepDependencies": False, "lastBuild": {"number": 4, "url": "http://halob:8080/job/foo/4/"}, # build running "lastCompletedBuild": {"number": 3, "url": "http://halob:8080/job/foo/3/"}, "lastFailedBuild": None, "lastStableBuild": {"number": 3, "url": "http://halob:8080/job/foo/3/"}, "lastSuccessfulBuild": {"number": 3, "url": "http://halob:8080/job/foo/3/"}, "lastUnstableBuild": None, "lastUnsuccessfulBuild": None, "nextBuildNumber": 4, "property": [], "queueItem": None, "concurrentBuild": False, "downstreamProjects": [], "scm": {}, "upstreamProjects": [] } JOB1_ALL_BUILDS_DATA = { "allBuilds": [ {"number": 3, "url": "http://halob:8080/job/foo/3/"}, {"number": 2, "url": "http://halob:8080/job/foo/2/"}, {"number": 1, "url": "http://halob:8080/job/foo/1/"} ], } JOB1_API_URL = 'http://halob:8080/job/foo/%s' % config.JENKINS_API JOB2_DATA = { 'actions': [], 'buildable': True, 'builds': [], 'color': 'notbuilt', 'concurrentBuild': False, 'description': '', 'displayName': 'look_ma_no_builds', 'displayNameOrNull': None, 'downstreamProjects': [], 'firstBuild': None, 'healthReport': [], 'inQueue': False, 'keepDependencies': False, 'lastBuild': None, 'lastCompletedBuild': None, 'lastFailedBuild': None, 'lastStableBuild': None, 'lastSuccessfulBuild': None, 'lastUnstableBuild': None, 'lastUnsuccessfulBuild': None, 'name': 'look_ma_no_builds', 'nextBuildNumber': 1, 'property': [{}], 'queueItem': None, 'scm': {}, 'upstreamProjects': [], 'url': 'http://halob:8080/job/look_ma_no_builds/' } JOB2_API_URL = 'http://halob:8080/job/look_ma_no_builds/%s' % config.JENKINS_API # Full list available immediatly JOB3_DATA = { "actions": [], "description": "test job", "displayName": "fullfoo", "displayNameOrNull": None, "name": "fullfoo", "url": "http://halob:8080/job/fullfoo/", "buildable": True, # all builds have been returned by Jenkins "builds": [ {"number": 3, "url": "http://halob:8080/job/fullfoo/3/"}, {"number": 2, "url": "http://halob:8080/job/fullfoo/2/"}, {"number": 1, "url": "http://halob:8080/job/fullfoo/1/"} ], "color": "blue", "firstBuild": {"number": 1, "url": "http://halob:8080/job/fullfoo/1/"}, "healthReport": [ {"description": "Build stability: No recent builds failed.", "iconUrl": "health-80plus.png", "score": 100} ], "inQueue": False, "keepDependencies": False, "lastBuild": {"number": 4, "url": "http://halob:8080/job/fullfoo/4/"}, # build running "lastCompletedBuild": {"number": 3, "url": "http://halob:8080/job/fullfoo/3/"}, "lastFailedBuild": None, "lastStableBuild": {"number": 3, "url": "http://halob:8080/job/fullfoo/3/"}, "lastSuccessfulBuild": {"number": 3, "url": "http://halob:8080/job/fullfoo/3/"}, "lastUnstableBuild": None, "lastUnsuccessfulBuild": None, "nextBuildNumber": 4, "property": [], "queueItem": None, "concurrentBuild": False, "downstreamProjects": [], "scm": {}, "upstreamProjects": [] } JOB3_ALL_BUILDS_DATA = {"allBuilds": [ {"number": 3, "url": "http://halob:8080/job/fullfoo/3/"}, {"number": 2, "url": "http://halob:8080/job/fullfoo/2/"}, {"number": 1, "url": "http://halob:8080/job/fullfoo/1/"}], } JOB3_API_URL = 'http://halob:8080/job/fullfoo/%s' % config.JENKINS_API URL_DATA = { JOB1_API_URL: JOB1_DATA, (JOB1_API_URL, str({'tree': 'allBuilds[number,url]'})): JOB1_ALL_BUILDS_DATA, JOB2_API_URL: JOB2_DATA, JOB3_API_URL: JOB3_DATA, # this one below should never be used (JOB3_API_URL, str({'tree': 'allBuilds[number,url]'})): JOB3_ALL_BUILDS_DATA, } def fakeGetData(self, url, params=None): TestJobGetAllBuilds.__get_data_call_count += 1 if params is None: try: return dict(TestJobGetAllBuilds.URL_DATA[url]) except KeyError: raise Exception("Missing data for url: %s" % url) else: try: return dict(TestJobGetAllBuilds.URL_DATA[(url, str(params))]) except KeyError: raise Exception("Missing data for url: %s with parameters %s" % (url, repr(params))) @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) def setUp(self): TestJobGetAllBuilds.__get_data_call_count = 0 self.J = mock.MagicMock() # Jenkins object self.j = Job('http://halob:8080/job/foo/', 'foo', self.J) def test_get_build_dict(self): # The job data contains only one build, so we expect that the # remaining jobs will be fetched automatically ret = self.j.get_build_dict() self.assertTrue(isinstance(ret, dict)) self.assertEquals(len(ret), 4) @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) def test_incomplete_builds_list_will_call_jenkins_twice(self): # The job data contains only one build, so we expect that the # remaining jobs will be fetched automatically, and to have two calls to # the Jenkins API TestJobGetAllBuilds.__get_data_call_count = 0 self.j = Job('http://halob:8080/job/foo/', 'foo', self.J) self.assertEquals(TestJobGetAllBuilds.__get_data_call_count, 2) @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) def test_complete_builds_list_will_call_jenkins_once(self): # The job data contains all builds, so we will not gather remaining builds TestJobGetAllBuilds.__get_data_call_count = 0 self.j = Job('http://halob:8080/job/fullfoo/', 'fullfoo', self.J) self.assertEquals(TestJobGetAllBuilds.__get_data_call_count, 1) @mock.patch.object(JenkinsBase, 'get_data', fakeGetData) def test_nobuilds_get_build_dict(self): j = Job('http://halob:8080/job/look_ma_no_builds/', 'look_ma_no_builds', self.J) ret = j.get_build_dict() self.assertTrue(isinstance(ret, dict)) self.assertEquals(len(ret), 0) def test_get_build_ids(self): # The job data contains only one build, so we expect that the # remaining jobs will be fetched automatically ret = list(self.j.get_build_ids()) self.assertTrue(isinstance(ret, list)) self.assertEquals(len(ret), 4) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_queue.py0000644000175000017500000001071512262651567022405 0ustar ahs3ahs3import mock import unittest from jenkinsapi import config from jenkinsapi.jenkins import Jenkins from jenkinsapi.queue import Queue, QueueItem from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.job import Job class FourOhFourError(Exception): """ Missing fake data """ class TestQueue(unittest.TestCase): @classmethod def mockGetData(self, url): try: return TestQueue.URL_DATA[url] except KeyError: raise FourOhFourError(url) URL_DATA = {} URL_DATA['http://localhost:8080/%s' % config.JENKINS_API] = { 'jobs': [ { 'name': 'utmebvpxrw', 'url': 'http://localhost/job/utmebvpxrw' } ] } URL_DATA['http://localhost/job/utmebvpxrw/%s' % config.JENKINS_API] = {} URL_DATA['http://localhost:8080/queue/%s' % config.JENKINS_API] = { 'items': [ { 'actions': [ { 'causes': [ { 'shortDescription': 'Started by user anonymous', 'userId': None, 'userName': 'anonymous' } ] } ], 'blocked': False, 'buildable': True, 'buildableStartMilliseconds': 1371419916747, 'id': 42, 'inQueueSince': 1371419909428, 'params': '', 'stuck': False, 'task': { 'color': 'grey', 'name': 'klscuimkqo', 'url': 'http://localhost:8080/job/klscuimkqo/' }, 'why': 'Waiting for next available executor' }, { 'actions': [ { 'causes': [ { 'shortDescription': 'Started by user anonymous', 'userId': None, 'userName': 'anonymous' } ] } ], 'blocked': False, 'buildable': True, 'buildableStartMilliseconds': 1371419911747, 'id': 41, 'inQueueSince': 1371419906327, 'params': '', 'stuck': False, 'task': { 'color': 'grey', 'name': 'vluyhzzepl', 'url': 'http://localhost:8080/job/vluyhzzepl/' }, 'why': 'Waiting for next available executor' }, { 'actions': [ { 'causes': [ { 'shortDescription': 'Started by user anonymous', 'userId': None, 'userName': 'anonymous' } ] } ], 'blocked': False, 'buildable': True, 'buildableStartMilliseconds': 1371419911747, 'id': 40, 'inQueueSince': 1371419903212, 'params': '', 'stuck': False, 'task': { 'color': 'grey', 'name': 'utmebvpxrw', 'url': 'http://localhost:8080/job/utmebvpxrw/' }, 'why': 'Waiting for next available executor' } ] } @mock.patch.object(JenkinsBase, 'get_data', mockGetData) def setUp(self): self.J = Jenkins('http://localhost:8080') # Jenkins self.q = Queue('http://localhost:8080/queue', self.J) def testRepr(self): self.assertTrue(repr(self.q)) def test_length(self): self.assertEquals(len(self.q), 3) def test_list_items(self): self.assertEquals(set(self.q.keys()), set([40, 41, 42])) def test_getitem(self): item40 = self.q[40] self.assertIsInstance(item40, QueueItem) @mock.patch.object(JenkinsBase, 'get_data', mockGetData) def test_get_job_for_queue_item(self): item40 = self.q[40] j = item40.get_job() self.assertIsInstance(j, Job) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_view.py0000644000175000017500000001241112262651567022226 0ustar ahs3ahs3import mock import unittest from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.jenkins import Jenkins from jenkinsapi.view import View from jenkinsapi.job import Job class TestView(unittest.TestCase): DATA = {'description': 'Important Shizz', 'jobs': [ {'color': 'blue', 'name': 'foo', 'url': 'http://halob:8080/job/foo/'}, {'color': 'red', 'name': 'test_jenkinsapi', 'url': 'http://halob:8080/job/test_jenkinsapi/'} ], 'name': 'FodFanFo', 'property': [], 'url': 'http://halob:8080/view/FodFanFo/'} JOB_DATA = { "actions": [], "description": "test job", "displayName": "foo", "displayNameOrNull": None, "name": "foo", "url": "http://halob:8080/job/foo/", "buildable": True, "builds": [ {"number": 3, "url": "http://halob:8080/job/foo/3/"}, {"number": 2, "url": "http://halob:8080/job/foo/2/"}, {"number": 1, "url": "http://halob:8080/job/foo/1/"} ], "color": "blue", "firstBuild": {"number": 1, "url": "http://halob:8080/job/foo/1/"}, "healthReport": [ {"description": "Build stability: No recent builds failed.", "iconUrl": "health-80plus.png", "score": 100} ], "inQueue": False, "keepDependencies": False, "lastBuild": {"number": 3, "url": "http://halob:8080/job/foo/3/"}, "lastCompletedBuild": {"number": 3, "url": "http://halob:8080/job/foo/3/"}, "lastFailedBuild": None, "lastStableBuild": {"number": 3, "url": "http://halob:8080/job/foo/3/"}, "lastSuccessfulBuild": {"number": 3, "url": "http://halob:8080/job/foo/3/"}, "lastUnstableBuild": None, "lastUnsuccessfulBuild": None, "nextBuildNumber": 4, "property": [], "queueItem": None, "concurrentBuild": False, "downstreamProjects": [], "scm": {}, "upstreamProjects": [] } @mock.patch.object(Job, '_poll') @mock.patch.object(View, '_poll') def setUp(self, _view_poll, _job_poll): _view_poll.return_value = self.DATA _job_poll.return_value = self.JOB_DATA # def __init__(self, url, name, jenkins_obj) self.J = mock.MagicMock() # Jenkins object self.J.has_job.return_value = False self.v = View('http://localhost:800/view/FodFanFo', 'FodFanFo', self.J) def testRepr(self): # Can we produce a repr string for this object repr(self.v) def testName(self): with self.assertRaises(AttributeError): self.v.id() self.assertEquals(self.v.name, 'FodFanFo') @mock.patch.object(JenkinsBase, '_poll') def test_iteritems(self, _poll): _poll.return_value = self.JOB_DATA for job_name, job_obj in self.v.iteritems(): self.assertTrue(isinstance(job_obj, Job)) def test_get_job_dict(self): jobs = self.v.get_job_dict() self.assertEquals(jobs, { 'foo': 'http://halob:8080/job/foo/', 'test_jenkinsapi': 'http://halob:8080/job/test_jenkinsapi/'}) def test_len(self): self.assertEquals(len(self.v), 2) # We have to re-patch JenkinsBase here because by the time # it get to create Job, MagicMock will already expire @mock.patch.object(JenkinsBase, '_poll') def test_getitem(self, _poll): _poll.return_value = self.JOB_DATA self.assertTrue(isinstance(self.v['foo'], Job)) def test_delete(self): self.v.delete() self.assertTrue(self.v.deleted) def test_get_job_url(self): self.assertEquals(self.v.get_job_url('foo'), 'http://halob:8080/job/foo/') def test_wrong_get_job_url(self): with self.assertRaises(KeyError): self.v.get_job_url('bar') # We have to re-patch JenkinsBase here because by the time # it get to create Job, MagicMock will already expire @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(View, '_poll') def test_add_job(self, _poll, _view_poll): _poll.return_value = self.DATA _view_poll.return_value = self.DATA J = mock.MagicMock() # Jenkins object J.has_job.return_value = True v = View('http://localhost:800/view/FodFanFo', 'FodFanFo', self.J) result = v.add_job('bar') self.assertTrue(result) class SelfPatchJenkins(object): def has_job(self, job_name): return False def get_jenkins_obj_from_url(self, url): return self # We have to re-patch JenkinsBase here because by the time # it get to create Job, MagicMock will already expire @mock.patch.object(View, 'get_jenkins_obj') def test_add_wrong_job(self, _get_jenkins): _get_jenkins.return_value = TestView.SelfPatchJenkins() result = self.v.add_job('bar') self.assertFalse(result) def test_add_existing_job(self): result = self.v.add_job('foo') self.assertFalse(result) def test_get_nested_view_dict(self): result = self.v.get_nested_view_dict() self.assertTrue(isinstance(result, dict)) self.assertEquals(len(result), 0) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/__init__.py0000644000175000017500000000000012262651567021743 0ustar ahs3ahs3jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_executors.py0000644000175000017500000002307412262651567023304 0ustar ahs3ahs3import mock import types import unittest from jenkinsapi.custom_exceptions import JenkinsAPIException from jenkinsapi.jenkins import Jenkins from jenkinsapi.executors import Executors from jenkinsapi.executor import Executor class TestExecutors(unittest.TestCase): DATAM = { 'assignedLabels': [{}], 'description': None, 'jobs': [], 'mode': 'NORMAL', 'nodeDescription': 'the master Jenkins node', 'nodeName': '', 'numExecutors': 2, 'overallLoad': {}, 'primaryView': {'name': 'All', 'url': 'http://localhost:8080/'}, 'quietingDown': False, 'slaveAgentPort': 0, 'unlabeledLoad': {}, 'useCrumbs': False, 'useSecurity': False, 'views': [ {'name': 'All', 'url': 'http://localhost:8080/'}, {'name': 'BigMoney', 'url': 'http://localhost:8080/view/BigMoney/'} ] } DATA0 = { "actions": [ ], "displayName": "host0.host.com", "executors": [ {}, {}, {}, {}, {}, {}, {}, {} ], "icon": "computer.png", "idle": False, "jnlpAgent": True, "launchSupported": False, "loadStatistics": { }, "manualLaunchAllowed": True, "monitorData": { "hudson.node_monitors.SwapSpaceMonitor": { "availablePhysicalMemory": 8462417920, "availableSwapSpace": 0, "totalPhysicalMemory": 75858042880, "totalSwapSpace": 0 }, "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)", "hudson.node_monitors.ResponseTimeMonitor": { "average": 2 }, "hudson.node_monitors.TemporarySpaceMonitor": { "path": "/tmp", "size": 430744551424 }, "hudson.node_monitors.DiskSpaceMonitor": { "path": "/data/jenkins", "size": 1214028627968 }, "hudson.node_monitors.ClockMonitor": { "diff": 1 } }, "numExecutors": 8, "offline": False, "offlineCause": None, "offlineCauseReason": "", "oneOffExecutors": [ {}, {} ], "temporarilyOffline": False } DATA1 = { "actions": [ ], "displayName": "host1.host.com", "executors": [ {}, {} ], "icon": "computer.png", "idle": False, "jnlpAgent": True, "launchSupported": False, "loadStatistics": { }, "manualLaunchAllowed": True, "monitorData": { "hudson.node_monitors.SwapSpaceMonitor": { "availablePhysicalMemory": 8462417920, "availableSwapSpace": 0, "totalPhysicalMemory": 75858042880, "totalSwapSpace": 0 }, "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)", "hudson.node_monitors.ResponseTimeMonitor": { "average": 2 }, "hudson.node_monitors.TemporarySpaceMonitor": { "path": "/tmp", "size": 430744551424 }, "hudson.node_monitors.DiskSpaceMonitor": { "path": "/data/jenkins", "size": 1214028627968 }, "hudson.node_monitors.ClockMonitor": { "diff": 1 } }, "numExecutors": 2, "offline": False, "offlineCause": None, "offlineCauseReason": "", "oneOffExecutors": [ {}, {} ], "temporarilyOffline": False } DATA2 = { "actions": [ ], "displayName": "host2.host.com", "executors": [ {}, {}, {}, {} ], "icon": "computer.png", "idle": False, "jnlpAgent": True, "launchSupported": False, "loadStatistics": { }, "manualLaunchAllowed": True, "monitorData": { "hudson.node_monitors.SwapSpaceMonitor": { "availablePhysicalMemory": 8462417920, "availableSwapSpace": 0, "totalPhysicalMemory": 75858042880, "totalSwapSpace": 0 }, "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)", "hudson.node_monitors.ResponseTimeMonitor": { "average": 2 }, "hudson.node_monitors.TemporarySpaceMonitor": { "path": "/tmp", "size": 430744551424 }, "hudson.node_monitors.DiskSpaceMonitor": { "path": "/data/jenkins", "size": 1214028627968 }, "hudson.node_monitors.ClockMonitor": { "diff": 1 } }, "numExecutors": 4, "offline": False, "offlineCause": None, "offlineCauseReason": "", "oneOffExecutors": [ {}, {} ], "temporarilyOffline": False } DATA3 = { "actions": [ ], "displayName": "host3.host.com", "executors": [ {} ], "icon": "computer.png", "idle": False, "jnlpAgent": True, "launchSupported": False, "loadStatistics": {}, "manualLaunchAllowed": True, "monitorData": { "hudson.node_monitors.SwapSpaceMonitor": { "availablePhysicalMemory": 8462417920, "availableSwapSpace": 0, "totalPhysicalMemory": 75858042880, "totalSwapSpace": 0 }, "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)", "hudson.node_monitors.ResponseTimeMonitor": { "average": 2 }, "hudson.node_monitors.TemporarySpaceMonitor": { "path": "/tmp", "size": 430744551424 }, "hudson.node_monitors.DiskSpaceMonitor": { "path": "/data/jenkins", "size": 1214028627968 }, "hudson.node_monitors.ClockMonitor": { "diff": 1 } }, "numExecutors": 1, "offline": False, "offlineCause": None, "offlineCauseReason": "", "oneOffExecutors": [ {}, {} ], "temporarilyOffline": False } EXEC0 = { "currentExecutable": { "number": 4168, "url": "http://localhost:8080/job/testjob/4168/" }, "currentWorkUnit": {}, "idle": False, "likelyStuck": False, "number": 0, "progress": 48 } EXEC1 = { "currentExecutable": None, "currentWorkUnit": None, "idle": True, "likelyStuck": False, "number": 0, "progress": -1 } @mock.patch.object(Jenkins, '_poll') def setUp(self, _poll_jenkins): _poll_jenkins.return_value = self.DATAM self.J = Jenkins('http://localhost:8080') def testRepr(self): # Can we produce a repr string for this object assert repr(self.J) def testCheckURL(self): self.assertEquals(self.J.baseurl, 'http://localhost:8080') @mock.patch.object(Executors, '_poll') @mock.patch.object(Executor, '_poll') def testGetExecutors(self, _poll_executor, _poll_executors): _poll_executor.return_value = self.EXEC0 _poll_executors.return_value = self.DATA3 exec_info = self.J.get_executors(self.DATA3['displayName']) self.assertIsInstance(exec_info, object) self.assertIsInstance(repr(exec_info), str) for e in exec_info: self.assertEquals(e.get_progress(), 48, 'Should return 48 %') @mock.patch.object(Executors, '_poll') @mock.patch.object(Executor, '_poll') def testis_idle(self, _poll_executor, _poll_executors): _poll_executor.return_value = self.EXEC1 _poll_executors.return_value = self.DATA3 exec_info = self.J.get_executors('host3.host.com') self.assertIsInstance(exec_info, object) for e in exec_info: self.assertEquals(e.get_progress(), -1, 'Should return 48 %') self.assertEquals(e.is_idle(), True, 'Should return True') self.assertEquals( repr(e), '' ) @mock.patch.object(Executor, '_poll') def test_likely_stuck(self, _poll_executor): _poll_executor.return_value = self.EXEC0 baseurl = 'http://localhost:8080/computer/host0.host.com/executors/0' nodename = 'host0.host.com' single_executer = Executor(baseurl, nodename, self.J, '0') self.assertEquals(single_executer.likely_stuck(), False) @mock.patch.object(Executor, '_poll') def test_get_current_executable(self, _poll_executor): _poll_executor.return_value = self.EXEC0 baseurl = 'http://localhost:8080/computer/host0.host.com/executors/0' nodename = 'host0.host.com' single_executer = Executor(baseurl, nodename, self.J, '0') self.assertEquals( single_executer.get_current_executable()['number'], 4168 ) self.assertEquals( single_executer.get_current_executable()['url'], 'http://localhost:8080/job/testjob/4168/' ) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_plugins.py0000644000175000017500000001206712262651567022744 0ustar ahs3ahs3import mock import unittest from jenkinsapi.jenkins import Jenkins from jenkinsapi.plugins import Plugins from jenkinsapi.plugin import Plugin class TestPlugins(unittest.TestCase): DATA = { 'plugins': [ { 'deleted': False, 'hasUpdate': True, 'downgradable': False, 'dependencies': [{}, {}, {}, {}], 'longName': 'Jenkins Subversion Plug-in', 'active': True, 'shortName': 'subversion', 'backupVersion': None, 'url': 'http://wiki.jenkins-ci.org/display/JENKINS/Subversion+Plugin', 'enabled': True, 'pinned': False, 'version': '1.45', 'supportsDynamicLoad': 'MAYBE', 'bundled': True }, { 'deleted': False, 'hasUpdate': True, 'downgradable': False, 'dependencies': [{}, {}], 'longName': 'Maven Integration plugin', 'active': True, 'shortName': 'maven-plugin', 'backupVersion': None, 'url': 'http://wiki.jenkins-ci.org/display/JENKINS/Maven+Project+Plugin', 'enabled': True, 'pinned': False, 'version': '1.521', 'supportsDynamicLoad': 'MAYBE', 'bundled': True } ] } @mock.patch.object(Jenkins, '_poll') def setUp(self, _poll_jenkins): _poll_jenkins.return_value = {} self.J = Jenkins('http://localhost:8080') @mock.patch.object(Plugins, '_poll') def test_get_plugins(self, _poll_plugins): _poll_plugins.return_value = self.DATA # Can we produce a repr string for this object self.assertIsInstance(self.J.get_plugins(), Plugins) @mock.patch.object(Plugins, '_poll') def test_no_plugins_str(self, _poll_plugins): _poll_plugins.return_value = {} plugins = self.J.get_plugins() self.assertEquals(str(plugins), "[]") @mock.patch.object(Plugins, '_poll') def test_plugins_str(self, _poll_plugins): _poll_plugins.return_value = self.DATA plugins = self.J.get_plugins() self.assertEquals(str(plugins), "['maven-plugin', 'subversion']") @mock.patch.object(Plugins, '_poll') def test_plugins_len(self, _poll_plugins): _poll_plugins.return_value = self.DATA plugins = self.J.get_plugins() self.assertEquals(len(plugins), 2) @mock.patch.object(Plugins, '_poll') def test_plugins_contains(self, _poll_plugins): _poll_plugins.return_value = self.DATA plugins = self.J.get_plugins() self.assertIn('subversion', plugins) self.assertIn('maven-plugin', plugins) @mock.patch.object(Plugins, '_poll') def test_plugins_values(self, _poll_plugins): _poll_plugins.return_value = self.DATA p = Plugin( { 'deleted': False, 'hasUpdate': True, 'downgradable': False, 'dependencies': [{}, {}, {}, {}], 'longName': 'Jenkins Subversion Plug-in', 'active': True, 'shortName': 'subversion', 'backupVersion': None, 'url': 'http://wiki.jenkins-ci.org/display/JENKINS/Subversion+Plugin', 'enabled': True, 'pinned': False, 'version': '1.45', 'supportsDynamicLoad': 'MAYBE', 'bundled': True } ) plugins = self.J.get_plugins().values() self.assertIn(p, plugins) @mock.patch.object(Plugins, '_poll') def test_plugins_keys(self, _poll_plugins): _poll_plugins.return_value = self.DATA plugins = self.J.get_plugins().keys() self.assertIn('subversion', plugins) self.assertIn('maven-plugin', plugins) @mock.patch.object(Plugins, '_poll') def test_plugins_empty(self, _poll_plugins): _poll_plugins.return_value = {} plugins = self.J.get_plugins().keys() self.assertEquals([], plugins) @mock.patch.object(Plugins, '_poll') def test_plugin_get_by_name(self, _poll_plugins): _poll_plugins.return_value = self.DATA p = Plugin( { 'deleted': False, 'hasUpdate': True, 'downgradable': False, 'dependencies': [{}, {}, {}, {}], 'longName': 'Jenkins Subversion Plug-in', 'active': True, 'shortName': 'subversion', 'backupVersion': None, 'url': 'http://wiki.jenkins-ci.org/display/JENKINS/Subversion+Plugin', 'enabled': True, 'pinned': False, 'version': '1.45', 'supportsDynamicLoad': 'MAYBE', 'bundled': True } ) plugin = self.J.get_plugins()['subversion'] self.assertEquals(p, plugin) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_jenkins.py0000644000175000017500000003620012262651567022717 0ustar ahs3ahs3import mock import unittest from jenkinsapi.plugins import Plugins from jenkinsapi.utils.requester import Requester from jenkinsapi.jenkins import Jenkins, JenkinsBase, Job from jenkinsapi.custom_exceptions import JenkinsAPIException, UnknownJob, BadURL class TestJenkins(unittest.TestCase): DATA = {} @mock.patch.object(Jenkins, '_poll') def setUp(self, _poll): _poll.return_value = self.DATA self.J = Jenkins('http://localhost:8080', username='foouser', password='foopassword') @mock.patch.object(Jenkins, '_poll') def test_clone(self, _poll): _poll.return_value = self.DATA JJ = self.J._clone() self.assertNotEquals(id(JJ), id(self.J)) self.assertEquals(JJ, self.J) def test_stored_passwords(self): self.assertEquals(self.J.requester.password, 'foopassword') self.assertEquals(self.J.requester.username, 'foouser') @mock.patch.object(Jenkins, '_poll') def test_reload(self, _poll): mock_requester = Requester(username='foouser', password='foopassword') mock_requester.get_url = mock.MagicMock(return_value='') J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword', requester=mock_requester) J.poll() @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Job, '_poll') def test_get_jobs(self, _base_poll, _poll, _job_poll): _poll.return_value = { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, {'name': 'job_two', 'url': 'http://localhost:8080/job_two'}, ] } _base_poll.return_value = _poll.return_value _job_poll.return_value = {} J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword') for idx, (job_name, job) in enumerate(J.get_jobs()): self.assertEquals(job_name, _poll.return_value['jobs'][idx]['name']) self.assertTrue(isinstance(job, Job)) self.assertEquals(job.name, _poll.return_value['jobs'][idx]['name']) self.assertEquals(job.baseurl, _poll.return_value['jobs'][idx]['url']) @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Job, '_poll') def test_get_jobs_info(self, _base_poll, _poll, _job_poll): _poll.return_value = { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, {'name': 'job_two', 'url': 'http://localhost:8080/job_two'}, ] } _base_poll.return_value = _poll.return_value _job_poll.return_value = {} J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword') for idx, (url, job_name) in enumerate(J.get_jobs_info()): self.assertEquals(job_name, _poll.return_value['jobs'][idx]['name']) self.assertEquals(url, _poll.return_value['jobs'][idx]['url']) @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Job, '_poll') def test_get_jobs_list(self, _base_poll, _poll, _job_poll): _poll.return_value = { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, {'name': 'job_two', 'url': 'http://localhost:8080/job_two'}, ] } _base_poll.return_value = _poll.return_value _job_poll.return_value = {} J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword') for idx, job_name in enumerate(J.get_jobs_list()): self.assertEquals(job_name, _poll.return_value['jobs'][idx]['name']) @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Job, '_poll') def test_get_job(self, _base_poll, _poll, _job_poll): _poll.return_value = { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, {'name': 'job_two', 'url': 'http://localhost:8080/job_two'}, ] } _base_poll.return_value = _poll.return_value _job_poll.return_value = {} J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword') job = J.get_job('job_one') self.assertTrue(isinstance(job, Job)) self.assertEquals(job.name, _poll.return_value['jobs'][0]['name']) self.assertEquals(job.baseurl, _poll.return_value['jobs'][0]['url']) @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Job, '_poll') def test_get_job_that_does_not_exist(self, _base_poll, _poll, _job_poll): _poll.return_value = { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, {'name': 'job_two', 'url': 'http://localhost:8080/job_two'}, ] } _base_poll.return_value = _poll.return_value _job_poll.return_value = {} J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword') with self.assertRaises(UnknownJob): job = J.get_job('job_three') @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Job, '_poll') def test_has_job(self, _base_poll, _poll, _job_poll): _poll.return_value = { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, {'name': 'job_two', 'url': 'http://localhost:8080/job_two'}, ] } _base_poll.return_value = _poll.return_value _job_poll.return_value = {} J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword') job = J.has_job('job_one') self.assertTrue(job) @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Job, '_poll') def test_has_no_job(self, _base_poll, _poll, _job_poll): _poll.return_value = { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, {'name': 'job_two', 'url': 'http://localhost:8080/job_two'}, ] } _base_poll.return_value = _poll.return_value _job_poll.return_value = {} J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword') job = J.has_job('inexistant_job') self.assertFalse(job) @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Job, '_poll') def test_create_dup_job(self, _base_poll, _poll, _job_poll): _poll.return_value = { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, {'name': 'job_two', 'url': 'http://localhost:8080/job_two'}, ] } _base_poll.return_value = _poll.return_value _job_poll.return_value = {} J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword') job = J.create_job('job_one', None) self.assertTrue(isinstance(job, Job)) self.assertTrue(job.baseurl == 'http://localhost:8080/job_one') self.assertTrue(job.name == 'job_one') # Here we're going to test function, which is going to modify # Jenkins internal data. It calls for data once to check # if job already there, then calls again to see if job hs been created. # So we need to create mock function, which # will return different value per each call # Define what we will return create_job_returns = [ # This will be returned when job is not yet created { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8081/job_one'}, {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, ] }, # This to simulate that the job has been created { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, {'name': 'job_two', 'url': 'http://localhost:8080/job_two'}, {'name': 'job_new', 'url': 'http://localhost:8080/job_new'}, ] } ] # Mock function def second_call_poll(): return TestJenkins.create_job_returns.pop(0) # Patch Jenkins with mock function @mock.patch.object(Jenkins, '_poll', side_effect=second_call_poll) @mock.patch.object(Job, '_poll') def test_create_new_job(self, _poll, _job_poll): _job_poll.return_value = {} mock_requester = Requester(username='foouser', password='foopassword') mock_requester.post_xml_and_confirm_status = mock.MagicMock(return_value='') J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword', requester=mock_requester) job = J.create_job('job_new', None) self.assertTrue(isinstance(job, Job)) self.assertTrue(job.baseurl == 'http://localhost:8080/job_new') self.assertTrue(job.name == 'job_new') @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Job, '_poll') def test_create_new_job_fail(self, _base_poll, _poll, _job_poll): _job_poll.return_value = {} _poll.return_value = { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, ] } _base_poll.return_value = _poll.return_value mock_requester = Requester(username='foouser', password='foopassword') mock_requester.post_xml_and_confirm_status = mock.MagicMock(return_value='') J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword', requester=mock_requester) with self.assertRaises(JenkinsAPIException) as ar: J.create_job('job_new', None) self.assertEquals(ar.exception.message, 'Cannot create job job_new') @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Job, '_poll') def test_get_jenkins_obj_from_url(self, _base_poll, _poll, _job_poll): _job_poll.return_value = {} _poll.return_value = { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, ] } _base_poll.return_value = _poll.return_value mock_requester = Requester(username='foouser', password='foopassword') mock_requester.post_xml_and_confirm_status = mock.MagicMock(return_value='') J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword', requester=mock_requester) new_jenkins = J.get_jenkins_obj_from_url('http://localhost:8080/') self.assertEquals(new_jenkins, J) new_jenkins = J.get_jenkins_obj_from_url('http://localhost:8080/foo') self.assertNotEquals(new_jenkins, J) @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Job, '_poll') def test_get_jenkins_obj(self, _base_poll, _poll, _job_poll): _job_poll.return_value = {} _poll.return_value = { 'jobs': [ {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, {'name': 'job_one', 'url': 'http://localhost:8080/job_one'}, ] } _base_poll.return_value = _poll.return_value mock_requester = Requester(username='foouser', password='foopassword') mock_requester.post_xml_and_confirm_status = mock.MagicMock(return_value='') J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword', requester=mock_requester) new_jenkins = J.get_jenkins_obj() self.assertEquals(new_jenkins, J) @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') def test_get_version(self, _base_poll, _poll): class MockResponse(object): def __init__(self): self.headers = {} self.headers['X-Jenkins'] = '1.542' mock_requester = Requester(username='foouser', password='foopassword') mock_requester.get_and_confirm_status = mock.MagicMock(return_value=MockResponse()) J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword', requester=mock_requester) self.assertEquals('1.542', J.version) @mock.patch.object(JenkinsBase, '_poll') @mock.patch.object(Jenkins, '_poll') def test_get_version_nonexistent(self, _base_poll, _poll): class MockResponse(object): def __init__(self): self.headers = {} base_url = 'http://localhost:8080' mock_requester = Requester(username='foouser', password='foopassword') mock_requester.get_and_confirm_status = mock.MagicMock(return_value=MockResponse()) J = Jenkins(base_url, username='foouser', password='foopassword', requester=mock_requester) self.assertEquals('0.0', J.version) class TestJenkinsURLs(unittest.TestCase): @mock.patch.object(Jenkins, '_poll') def testNoSlash(self, _poll): _poll.return_value = {} J = Jenkins('http://localhost:8080', username='foouser', password='foopassword') self.assertEquals(J.get_create_url(), 'http://localhost:8080/createItem') @mock.patch.object(Jenkins, '_poll') def testWithSlash(self, _poll): _poll.return_value = {} J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword') self.assertEquals(J.get_create_url(), 'http://localhost:8080/createItem') @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Plugins, '_poll') def test_has_plugin(self, _p_poll, _poll): _poll.return_value = {} _p_poll.return_value = { 'plugins': [ { 'deleted': False, 'hasUpdate': True, 'downgradable': False, 'dependencies': [{}, {}, {}, {}], 'longName': 'Jenkins Subversion Plug-in', 'active': True, 'shortName': 'subversion', 'backupVersion': None, 'url': 'http://wiki.jenkins-ci.org/display/JENKINS/Subversion+Plugin', 'enabled': True, 'pinned': False, 'version': '1.45', 'supportsDynamicLoad': 'MAYBE', 'bundled': True } ] } J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword') self.assertTrue(J.has_plugin('subversion')) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_requester.py0000644000175000017500000001625712262651567023307 0ustar ahs3ahs3import mock import unittest import requests from jenkinsapi.jenkins import Requester from jenkinsapi.custom_exceptions import JenkinsAPIException class TestQueue(unittest.TestCase): def test_get_request_dict_auth(self): req = Requester('foo', 'bar') req_return = req.get_request_dict( params={}, data=None, headers=None ) self.assertTrue(isinstance(req_return, dict)) self.assertTrue(req_return.get('auth')) self.assertTrue(req_return['auth'] == ('foo', 'bar')) def test_get_request_dict_wrong_params(self): req = Requester('foo', 'bar') with self.assertRaises(AssertionError) as na: req.get_request_dict( params='wrong', data=None, headers=None ) self.assertTrue(na.exception.message == "Params must be a dict, got 'wrong'") def test_get_request_dict_correct_params(self): req = Requester('foo', 'bar') req_return = req.get_request_dict( params={'param': 'value'}, data=None, headers=None ) self.assertTrue(isinstance(req_return, dict)) self.assertTrue(req_return.get('params')) self.assertTrue(req_return['params'] == {'param': 'value'}) def test_get_request_dict_wrong_headers(self): req = Requester('foo', 'bar') with self.assertRaises(AssertionError) as na: req.get_request_dict( params={}, data=None, headers='wrong' ) self.assertTrue(na.exception.message == "headers must be a dict, got 'wrong'") def test_get_request_dict_correct_headers(self): req = Requester('foo', 'bar') req_return = req.get_request_dict( params={'param': 'value'}, data=None, headers={'header': 'value'} ) self.assertTrue(isinstance(req_return, dict)) self.assertTrue(req_return.get('headers')) self.assertTrue(req_return['headers'] == {'header': 'value'}) def test_get_request_dict_data_passed(self): req = Requester('foo', 'bar') req_return = req.get_request_dict( params={'param': 'value'}, data='some data', headers={'header': 'value'} ) self.assertTrue(isinstance(req_return, dict)) print req_return.get('data') self.assertTrue(req_return.get('data')) self.assertTrue(req_return['data'] == 'some data') def test_get_request_dict_data_not_passed(self): req = Requester('foo', 'bar') req_return = req.get_request_dict( params={'param': 'value'}, data=None, headers={'header': 'value'} ) self.assertTrue(isinstance(req_return, dict)) self.assertFalse(req_return.get('data')) @mock.patch.object(requests, 'get') def test_get_url_get(self, _get): _get.return_value = 'SUCCESS' req = Requester('foo', 'bar') response = req.get_url( 'http://dummy', params={'param': 'value'}, headers=None) self.assertTrue(response == 'SUCCESS') @mock.patch.object(requests, 'post') def test_get_url_post(self, _post): _post.return_value = 'SUCCESS' req = Requester('foo', 'bar') response = req.post_url( 'http://dummy', params={'param': 'value'}, headers=None) self.assertTrue(response == 'SUCCESS') @mock.patch.object(requests, 'post') def test_post_xml_and_confirm_status_empty_xml(self, _post): _post.return_value = 'SUCCESS' req = Requester('foo', 'bar') with self.assertRaises(AssertionError) as ae: req.post_xml_and_confirm_status( url='http://dummy', params={'param': 'value'}, data=None ) self.assertTrue(ae.exception.message == "Unexpected type of parameter 'data': . Expected (str, dict)") @mock.patch.object(requests, 'post') def test_post_xml_and_confirm_status_some_xml(self, _post): response = requests.Response() response.status_code = 200 _post.return_value = response req = Requester('foo', 'bar') ret = req.post_xml_and_confirm_status( url='http://dummy', params={'param': 'value'}, data='' ) self.assertTrue(isinstance(ret, requests.Response)) @mock.patch.object(requests, 'post') def test_post_and_confirm_status_empty_data(self, _post): _post.return_value = 'SUCCESS' req = Requester('foo', 'bar') with self.assertRaises(AssertionError) as ae: req.post_and_confirm_status( url='http://dummy', params={'param': 'value'}, data=None ) self.assertTrue(ae.exception.message == "Unexpected type of parameter 'data': . Expected (str, dict)") @mock.patch.object(requests, 'post') def test_post_and_confirm_status_some_data(self, _post): response = requests.Response() response.status_code = 200 _post.return_value = response req = Requester('foo', 'bar') ret = req.post_and_confirm_status( url='http://dummy', params={'param': 'value'}, data='some data' ) self.assertTrue(isinstance(ret, requests.Response)) @mock.patch.object(requests, 'post') def test_post_and_confirm_status_bad_result(self, _post): response = requests.Response() response.status_code = 500 _post.return_value = response req = Requester('foo', 'bar') with self.assertRaises(JenkinsAPIException) as ae: req.post_and_confirm_status( url='http://dummy', params={'param': 'value'}, data='some data' ) print ae.exception.message self.assertTrue(ae.exception.message == "Operation failed. url=None, data=some data, headers={'Content-Type': 'application/x-www-form-urlencoded'}, status=500, text=") @mock.patch.object(requests, 'get') def test_get_and_confirm_status(self, _get): response = requests.Response() response.status_code = 200 _get.return_value = response req = Requester('foo', 'bar') ret = req.get_and_confirm_status( url='http://dummy', params={'param': 'value'} ) self.assertTrue(isinstance(ret, requests.Response)) @mock.patch.object(requests, 'get') def test_get_and_confirm_status_bad_result(self, _get): response = requests.Response() response.status_code = 500 _get.return_value = response req = Requester('foo', 'bar', baseurl='http://dummy') with self.assertRaises(JenkinsAPIException) as ae: req.get_and_confirm_status( url='http://dummy', params={'param': 'value'} ) print ae.exception.message self.assertTrue(ae.exception.message == "Operation failed. url=None, headers=None, status=500, text=") if __name__ == "__main__": unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_result_set.py0000644000175000017500000001015612262651567023451 0ustar ahs3ahs3import mock import unittest from jenkinsapi.result_set import ResultSet from jenkinsapi.result import Result class TestResultSet(unittest.TestCase): DATA = {'duration': 0.0, 'failCount': 2, 'passCount': 0, 'skipCount': 0, 'suites': [{'cases': [{'age': 1, 'className': ':setup', 'skipped': False, 'status': 'FAILED', 'stderr': None, 'stdout': None}, {'age': 1, 'className': 'nose.failure.Failure', 'duration': 0.0, 'errorDetails': 'No module named mock', 'errorStackTrace': 'Traceback (most recent call last):\n File "/usr/lib/python2.7/unittest/case.py", line 332, in run\n testMethod()\n File "/usr/lib/python2.7/dist-packages/nose/loader.py", line 390, in loadTestsFromName\n addr.filename, addr.module)\n File "/usr/lib/python2.7/dist-packages/nose/importer.py", line 39, in importFromPath\n return self.importFromDir(dir_path, fqname)\n File "/usr/lib/python2.7/dist-packages/nose/importer.py", line 86, in importFromDir\n mod = load_module(part_fqname, fh, filename, desc)\n File "/var/lib/jenkins/jobs/test_jenkinsapi/workspace/jenkinsapi/src/jenkinsapi_tests/unittests/test_build.py", line 1, in \n import mock\nImportError: No module named mock\n', 'failedSince': 88, 'name': 'runTest', 'skipped': False, 'status': 'FAILED', 'stderr': None, 'stdout': None}], 'duration': 0.0, 'id': None, 'name': 'nosetests', 'stderr': None, 'stdout': None, 'timestamp': None}]} @mock.patch.object(ResultSet, '_poll') def setUp(self, _poll): _poll.return_value = self.DATA # def __init__(self, url, build ): self.b = mock.MagicMock() # Build object self.b.__str__.return_value = 'FooBuild' self.rs = ResultSet('http://', self.b) def testRepr(self): # Can we produce a repr string for this object repr(self.rs) def testName(self): with self.assertRaises(AttributeError): self.rs.id() self.assertEquals(self.rs.name, 'Test Result for FooBuild') def testBuildComponents(self): self.assertTrue(self.rs.items()) for k, v in self.rs.items(): self.assertIsInstance(k, str) self.assertIsInstance(v, Result) self.assertIsInstance(v.identifier(), str) jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_views.py0000644000175000017500000000531212262651567022413 0ustar ahs3ahs3import mock import unittest from jenkinsapi import config from jenkinsapi.view import View from jenkinsapi.jenkins import Jenkins from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.utils.requester import Requester class TestDataMissing(Exception): pass class TestViews(unittest.TestCase): @mock.patch.object(Jenkins, '_poll') @mock.patch.object(JenkinsBase, '_poll') def test_create_view(self, _poll, _base_poll): mock_requester = Requester(username='foouser', password='foopassword') mock_requester.get_url = mock.MagicMock(return_value='
') mock_requester.post_url = mock.MagicMock(return_value='') _poll.return_value = { 'views': [ {'name': 'All', 'url': 'http://localhost:8080/views/All'}, {'name': 'NewView', 'url': 'http://localhost:8080/views/NewView'}, ] } _base_poll.return_value = _poll.return_value J = Jenkins('http://localhost:8080/', username='foouser', password='foopassword', requester=mock_requester) new_view = J.views.create('NewView') self.assertTrue(isinstance(new_view, View)) self.assertEquals(new_view.baseurl, 'http://localhost:8080/views/NewView') def test_create_existing_view(self): """ Assert that attempting to create a view which already exists simply returns the same view. """ def mockGetData(JJ, url): DATA = {} DATA['http://localhost:8080/%s' % config.JENKINS_API] = \ {'views': [dict(name='NewView', url='http://xxxxx/yyyy')]} DATA['http://xxxxx/yyyy/%s' % config.JENKINS_API] = \ {} try: result = DATA[url] return result except KeyError: raise TestDataMissing(url) with mock.patch.object(JenkinsBase, 'get_data', mockGetData): J = Jenkins('http://localhost:8080', username='foouser', password='foopassword') new_view = J.views.create('NewView') self.assertIsInstance(new_view, View) # @mock.patch.object(Jenkins, '_poll') # def test_delete_inexisting_view(self, _poll): # mock_requester = Requester(username='foouser', password='foopassword') # mock_requester.get_url = mock.MagicMock(return_value='
') # J = Jenkins('http://localhost:8080/', # username='foouser', password='foopassword', # requester=mock_requester) # delete_result = J.delete_view(str_view_name='NewView') # self.assertFalse(delete_result) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/jenkinsapi_tests/unittests/test_nodes.py0000644000175000017500000002000712262651567022364 0ustar ahs3ahs3import mock import unittest from jenkinsapi.jenkins import Jenkins from jenkinsapi.nodes import Nodes from jenkinsapi.node import Node class TestNode(unittest.TestCase): DATA0 = { 'assignedLabels': [{}], 'description': None, 'jobs': [], 'mode': 'NORMAL', 'nodeDescription': 'the master Jenkins node', 'nodeName': '', 'numExecutors': 2, 'overallLoad': {}, 'primaryView': {'name': 'All', 'url': 'http://halob:8080/'}, 'quietingDown': False, 'slaveAgentPort': 0, 'unlabeledLoad': {}, 'useCrumbs': False, 'useSecurity': False, 'views': [ {'name': 'All', 'url': 'http://halob:8080/'}, {'name': 'FodFanFo', 'url': 'http://halob:8080/view/FodFanFo/'} ] } DATA1 = { 'busyExecutors': 0, 'computer': [ { 'actions': [], 'displayName': 'master', 'executors': [{}, {}], 'icon': 'computer.png', 'idle': True, 'jnlpAgent': False, 'launchSupported': True, 'loadStatistics': {}, 'manualLaunchAllowed': True, 'monitorData': { 'hudson.node_monitors.ArchitectureMonitor': 'Linux (amd64)', 'hudson.node_monitors.ClockMonitor': {'diff': 0}, 'hudson.node_monitors.DiskSpaceMonitor': { 'path': '/var/lib/jenkins', 'size': 671924924416 }, 'hudson.node_monitors.ResponseTimeMonitor': {'average': 0}, 'hudson.node_monitors.SwapSpaceMonitor': { 'availablePhysicalMemory': 3174686720, 'availableSwapSpace': 17163087872, 'totalPhysicalMemory': 16810180608, 'totalSwapSpace': 17163087872 }, 'hudson.node_monitors.TemporarySpaceMonitor': { 'path': '/tmp', 'size': 671924924416 } }, 'numExecutors': 2, 'offline': False, 'offlineCause': None, 'oneOffExecutors': [], 'temporarilyOffline': False }, { 'actions': [], 'displayName': 'bobnit', 'executors': [{}], 'icon': 'computer-x.png', 'idle': True, 'jnlpAgent': False, 'launchSupported': True, 'loadStatistics': {}, 'manualLaunchAllowed': True, 'monitorData': { 'hudson.node_monitors.ArchitectureMonitor': 'Linux (amd64)', 'hudson.node_monitors.ClockMonitor': {'diff': 4261}, 'hudson.node_monitors.DiskSpaceMonitor': { 'path': '/home/sal/jenkins', 'size': 169784860672 }, 'hudson.node_monitors.ResponseTimeMonitor': {'average': 29}, 'hudson.node_monitors.SwapSpaceMonitor': { 'availablePhysicalMemory': 4570710016, 'availableSwapSpace': 12195983360, 'totalPhysicalMemory': 8374497280, 'totalSwapSpace': 12195983360 }, 'hudson.node_monitors.TemporarySpaceMonitor': { 'path': '/tmp', 'size': 249737277440 } }, 'numExecutors': 1, 'offline': True, 'offlineCause': {}, 'oneOffExecutors': [], 'temporarilyOffline': False }, { 'actions': [], 'displayName': 'halob', 'executors': [{}], 'icon': 'computer-x.png', 'idle': True, 'jnlpAgent': True, 'launchSupported': False, 'loadStatistics': {}, 'manualLaunchAllowed': True, 'monitorData': { 'hudson.node_monitors.ArchitectureMonitor': None, 'hudson.node_monitors.ClockMonitor': None, 'hudson.node_monitors.DiskSpaceMonitor': None, 'hudson.node_monitors.ResponseTimeMonitor': None, 'hudson.node_monitors.SwapSpaceMonitor': None, 'hudson.node_monitors.TemporarySpaceMonitor': None }, 'numExecutors': 1, 'offline': True, 'offlineCause': None, 'oneOffExecutors': [], 'temporarilyOffline': False } ], 'displayName': 'nodes', 'totalExecutors': 2 } DATA2 = { 'actions': [], 'displayName': 'master', 'executors': [{}, {}], 'icon': 'computer.png', 'idle': True, 'jnlpAgent': False, 'launchSupported': True, 'loadStatistics': {}, 'manualLaunchAllowed': True, 'monitorData': { 'hudson.node_monitors.ArchitectureMonitor': 'Linux (amd64)', 'hudson.node_monitors.ClockMonitor': {'diff': 0}, 'hudson.node_monitors.DiskSpaceMonitor': { 'path': '/var/lib/jenkins', 'size': 671942561792 }, 'hudson.node_monitors.ResponseTimeMonitor': {'average': 0}, 'hudson.node_monitors.SwapSpaceMonitor': { 'availablePhysicalMemory': 2989916160, 'availableSwapSpace': 17163087872, 'totalPhysicalMemory': 16810180608, 'totalSwapSpace': 17163087872 }, 'hudson.node_monitors.TemporarySpaceMonitor': { 'path': '/tmp', 'size': 671942561792 } }, 'numExecutors': 2, 'offline': False, 'offlineCause': None, 'oneOffExecutors': [], 'temporarilyOffline': False } DATA3 = { 'actions': [], 'displayName': 'halob', 'executors': [{}], 'icon': 'computer-x.png', 'idle': True, 'jnlpAgent': True, 'launchSupported': False, 'loadStatistics': {}, 'manualLaunchAllowed': True, 'monitorData': { 'hudson.node_monitors.ArchitectureMonitor': None, 'hudson.node_monitors.ClockMonitor': None, 'hudson.node_monitors.DiskSpaceMonitor': None, 'hudson.node_monitors.ResponseTimeMonitor': None, 'hudson.node_monitors.SwapSpaceMonitor': None, 'hudson.node_monitors.TemporarySpaceMonitor': None}, 'numExecutors': 1, 'offline': True, 'offlineCause': None, 'oneOffExecutors': [], 'temporarilyOffline': False } @mock.patch.object(Jenkins, '_poll') @mock.patch.object(Nodes, '_poll') def setUp(self, _poll_nodes, _poll_jenkins): _poll_jenkins.return_value = self.DATA0 _poll_nodes.return_value = self.DATA1 # def __init__(self, baseurl, nodename, jenkins_obj): self.J = Jenkins('http://localhost:8080') self.ns = self.J.get_nodes() # self.ns = Nodes('http://localhost:8080/computer', 'bobnit', self.J) def testRepr(self): # Can we produce a repr string for this object repr(self.ns) def testCheckURL(self): self.assertEquals(self.ns.baseurl, 'http://localhost:8080/computer') @mock.patch.object(Node, '_poll') def testGetMasterNode(self, _poll_node): _poll_node.return_value = self.DATA2 mn = self.ns['master'] self.assertIsInstance(mn, Node) @mock.patch.object(Node, '_poll') def testGetNonMasterNode(self, _poll_node): _poll_node.return_value = self.DATA3 mn = self.ns['halob'] self.assertIsInstance(mn, Node) if __name__ == '__main__': unittest.main() jenkinsapi-0.2.16/misc/0000755000175000017500000000000012262651567013160 5ustar ahs3ahs3jenkinsapi-0.2.16/misc/readme.txt0000644000175000017500000000032112262651567015152 0ustar ahs3ahs3This folder contains configuration files which may be useful for developers. Almost certainly, none of these files will work from their current location. You may need to symlink them to a more useful location.jenkinsapi-0.2.16/misc/jenkinsapi.sublime-project0000644000175000017500000000313112262651567020337 0ustar ahs3ahs3{ "folders": [ { "path": "src", "file_exclude_patterns": ["*.pyc"] } ], "settings": { "python_test_runner": { "before_test": "source .env/bin/activate", "after_test": "deactivate", "test_root": "src/pythonmoo/tests", "test_delimeter": ":", "test_command": "nosetests" }, "pylinter": { "ignore":["C"], "use_icons":true } }, "build_systems": [ { "name":"Virtualenv 2.7", "cmd": [ "${project_path}/bin/python2.7", "$file" ] }, { "name":"Nose 2.7 Unittests", "working_dir": "${project_path:${folder}}/src", "cmd": [ "${project_path}/bin/nosetests", "${project_path}/src/jenkinsapi_tests/unittests" ] }, { "name":"Nose 2.7 All tests", "working_dir": "${project_path:${folder}}/src", "cmd": [ "${project_path}/bin/nosetests", "${project_path}/src/jenkinsapi_tests" ] }, { "name":"Virtualenv 3.3", "working_dir": "${project_path:${folder}}/src", "cmd": [ "source", "${project_path}/bin/activate" ], "cmd": [ "${project_path}/bin/python3.3", "-u", "$file" ] } ] } jenkinsapi-0.2.16/TODO0000644000175000017500000000055112262651567012716 0ustar ahs3ahs3TODO: * Add a testsuite (preferably nose or py.test) which doesn't rely on a local jenkins setup (or instantiates one during test) * Clean up the fingerprint code * Clean up the resultset and results code * Make all objects inherit off jenkins_base where that makes sense * Add ability to add/modify/delete jobs * Add ability to query jenkins for plugin data jenkinsapi-0.2.16/.travis.yml0000644000175000017500000000070412262651567014337 0ustar ahs3ahs3language: python python: - "2.7" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - python setup.py develop - pip install --use-mirrors nose mock coverage pep8 pylint # command to run tests, e.g. python setup.py test script: - pep8 --ignore=E501 jenkinsapi/*.py - pylint --rcfile=pylintrc jenkinsapi/*.py - nosetests jenkinsapi_tests # - "nosetests jenkinsapi_tests/systests" jenkinsapi-0.2.16/setup.py0000644000175000017500000000314212262651567013737 0ustar ahs3ahs3from setuptools import setup import os PROJECT_ROOT, _ = os.path.split(__file__) REVISION = '0.2.16' PROJECT_NAME = 'JenkinsAPI' PROJECT_AUTHORS = "Salim Fadhley, Ramon van Alteren, Ruslan Lutsenko" PROJECT_EMAILS = 'salimfadhley@gmail.com, ramon@vanalteren.nl, ruslan.lutcenko@gmail.com' PROJECT_URL = "https://github.com/salimfadhley/jenkinsapi" SHORT_DESCRIPTION = 'A Python API for accessing resources on a Jenkins continuous-integration server.' try: DESCRIPTION = open(os.path.join(PROJECT_ROOT, "README.rst")).read() except IOError, _: DESCRIPTION = SHORT_DESCRIPTION GLOBAL_ENTRY_POINTS = { "console_scripts": ["jenkins_invoke=jenkinsapi.command_line.jenkins_invoke:main"] } setup( name=PROJECT_NAME.lower(), version=REVISION, author=PROJECT_AUTHORS, author_email=PROJECT_EMAILS, packages=['jenkinsapi', 'jenkinsapi.utils', 'jenkinsapi.command_line', 'jenkinsapi_tests'], zip_safe=True, include_package_data=False, install_requires=['requests>=1.2.3', 'pytz>=2013b'], test_suite='nose.collector', tests_require=['mock', 'nose', 'coverage'], entry_points=GLOBAL_ENTRY_POINTS, url=PROJECT_URL, description=SHORT_DESCRIPTION, long_description=DESCRIPTION, license='MIT', classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2.7', 'Topic :: Software Development :: Testing', ], ) jenkinsapi-0.2.16/jenkinsapi_utils/0000755000175000017500000000000012262651567015600 5ustar ahs3ahs3jenkinsapi-0.2.16/jenkinsapi_utils/simple_post_logger.py0000644000175000017500000000146412262651567022054 0ustar ahs3ahs3import SimpleHTTPServer import SocketServer import logging import cgi PORT = 8080 class ServerHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): logging.error(self.headers) SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) def do_POST(self): logging.error(self.headers) form = cgi.FieldStorage( fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers['Content-Type'], }) for item in form.list: logging.error(item) SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) Handler = ServerHandler httpd = SocketServer.TCPServer(("", PORT), Handler) print "serving at port", PORT httpd.serve_forever() jenkinsapi-0.2.16/jenkinsapi_utils/__init__.py0000644000175000017500000000000012262651567017677 0ustar ahs3ahs3jenkinsapi-0.2.16/jenkinsapi_utils/jenkins_launcher.py0000644000175000017500000001260212262651567021475 0ustar ahs3ahs3import os import time import Queue import random import shutil import logging import datetime import tempfile import requests import threading import subprocess import pkg_resources from jenkinsapi.jenkins import Jenkins from jenkinsapi.custom_exceptions import JenkinsAPIException log = logging.getLogger(__name__) class FailedToStart(Exception): pass class TimeOut(Exception): pass class StreamThread(threading.Thread): def __init__(self, name, q, stream, fn_log): threading.Thread.__init__(self) self.name = name self.q = q self.stream = stream self.fn_log = fn_log def run(self): log.info("Starting %s", self.name) while True: line = self.stream.readline() if line: self.fn_log(line.rstrip()) self.q.put((self.name, line)) else: break self.q.put((self.name, None)) class JenkinsLancher(object): """ Launch jenkins """ JENKINS_WAR_URL = "http://mirrors.jenkins-ci.org/war/latest/jenkins.war" def __init__(self, war_path, plugin_urls=None): self.war_path = war_path self.war_directory, self.war_filename = os.path.split(self.war_path) self.jenkins_home = tempfile.mkdtemp(prefix='jenkins-home-') self.jenkins_process = None self.q = Queue.Queue() self.plugin_urls = plugin_urls or [] self.http_port = random.randint(9000, 10000) def update_war(self): os.chdir(self.war_directory) if os.path.exists(self.war_path): log.info("We already have the War file...") else: log.info("Redownloading Jenkins") subprocess.check_call('./get-jenkins-war.sh') def update_config(self): config_dest = os.path.join(self.jenkins_home, 'config.xml') config_dest_file = open(config_dest, 'w') config_source = pkg_resources.resource_string('jenkinsapi_tests.systests', 'config.xml') config_dest_file.write(config_source.encode('UTF-8')) def install_plugins(self): for i, url in enumerate(self.plugin_urls): self.install_plugin(url, i) def install_plugin(self, hpi_url, i): plugin_dir = os.path.join(self.jenkins_home, 'plugins') if not os.path.exists(plugin_dir): os.mkdir(plugin_dir) log.info("Downloading %s", hpi_url) log.info("Plugins will be installed in '%s'" % plugin_dir) # FIXME: This is kinda ugly but works filename = "plugin_%s.hpi" % i plugin_path = os.path.join(plugin_dir, filename) with open(plugin_path, 'wb') as h: request = requests.get(hpi_url) h.write(request.content) def stop(self): log.info("Shutting down jenkins.") self.jenkins_process.terminate() self.jenkins_process.wait() shutil.rmtree(self.jenkins_home) def block_until_jenkins_ready(self, timeout): start_time = datetime.datetime.now() timeout_time = start_time + datetime.timedelta(seconds=timeout) while True: try: Jenkins('http://localhost:8080') log.info('Jenkins is finally ready for use.') except JenkinsAPIException: log.info('Jenkins is not yet ready...') if datetime.datetime.now() > timeout_time: raise TimeOut('Took too long for Jenkins to become ready...') time.sleep(5) def start(self, timeout=60): self.update_war() self.update_config() self.install_plugins() os.environ['JENKINS_HOME'] = self.jenkins_home os.chdir(self.war_directory) jenkins_command = ['java', '-jar', self.war_filename, '--httpPort=%d' % self.http_port] log.info("About to start Jenkins...") log.info("%s> %s", os.getcwd(), " ".join(jenkins_command)) self.jenkins_process = subprocess.Popen( jenkins_command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) threads = [ StreamThread('out', self.q, self.jenkins_process.stdout, log.info), StreamThread('err', self.q, self.jenkins_process.stderr, log.warn) ] # Start the threads for t in threads: t.start() while True: try: streamName, line = self.q.get(block=True, timeout=timeout) except Queue.Empty: log.warn("Input ended unexpectedly") break else: if line: if 'Failed to initialize Jenkins' in line: raise FailedToStart(line) if 'Invalid or corrupt jarfile' in line: raise FailedToStart(line) if 'is fully up and running' in line: log.info(line) return else: log.warn('Stream %s has terminated', streamName) self.block_until_jenkins_ready(timeout) if __name__ == '__main__': logging.basicConfig() logging.getLogger('').setLevel(logging.INFO) log.info("Hello!") jl = JenkinsLancher( '/home/sal/workspace/jenkinsapi/src/jenkinsapi_tests/systests/jenkins.war' ) jl.start() log.info("Jenkins was launched...") time.sleep(30) log.info("...now to shut it down!") jl.stop() jenkinsapi-0.2.16/travis.yaml0000644000175000017500000000035212262651567014421 0ustar ahs3ahs3language: python python: - "2.7" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: python setup.py develop # command to run tests, e.g. python setup.py test script: python setup.py test jenkinsapi-0.2.16/pylintrc0000644000175000017500000002076212262651567014023 0ustar ahs3ahs3[MASTER] # Specify a configuration file. #rcfile= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Profiled execution. profile=no # Add to the black list. It should be a base name, not a # path. You may set this option multiple times. ignore=CVS # Pickle collected data for later comparisons. persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= [MESSAGES CONTROL] # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time. #enable= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). # F0401: *Unable to import %r* # E0611: *No name %r in module %r* # E1101: *%s %r has no %r member* # W0142: *Used * or ** magic* # W0212: *Access to a protected member %s of a client class* # :R0201: *Method could be a function* # w0703: Allow catching Exception # R0801: 1: Similar lines in 2 files, badamson: had trouble disabling this locally # FIXME: should be re-enabled after it's fixed # hbrown: I don't think R0801 can be disabled locally # http://www.logilab.org/ticket/6905 # pylint #6905: R0801 message cannot be disabled locally [open] # R0901: Too many ancestors # # Amplify/Disco customizations: # W0511: TODO - we want to have TODOs during prototyping # E1103: %s %r has no %r member (but some types could not be inferred) - fails to infer real members of types, e.g. in Celery # W0231: method from base class is not called - complains about not invoking empty __init__s in parents, which is annoying # R0921: abstract class not referenced, when in fact referenced from another egg disable=F0401,E0611,E1101,W0142,W0212,R0201,W0703,R0801,R0901,W0511,E1103,W0231,R0921,W0402,I0011 [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html output-format=colorized # Include message's id in output include-ids=yes # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". files-output=no # Tells whether to display a full report or only the messages reports=no # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (R0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Add a comment according to your evaluation note. This is used by the global # evaluation report (R0004). comment=no [BASIC] # Required attributes for module, separated by a comma required-attributes= # List of builtins function names that should not be used, separated by a comma # Amplify: Allowing the use of 'map' and 'filter' bad-functions=apply,input # Regular expression which should only match correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression which should only match correct module level names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__)|([a-z_][a-z0-9_]*)) # Regular expression which should only match correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Regular expression which should only match correct function names # Amplify: Up to 40 characters long function-rgx=[a-z_][a-z0-9_]{2,40}$ # Regular expression which should only match correct method names # Amplify: Up to 40 characters long method-rgx=[a-z_][a-z0-9_]{2,40}$ # Regular expression which should only match correct instance attribute names # Amplify: Up to 40 characters long attr-rgx=[a-z_][a-z0-9_]{2,40}$ # Regular expression which should only match correct argument names # Amplify: Up to 40 characters long # argument-rgx=[a-z_][a-z0-9_]{2,40}$ argument-rgx=[A-Za-z_][A-Za-z0-9_]{1,40}$ # Regular expression which should only match correct variable names # Amplify: Up to 40 characters long # variable-rgx=[a-z_][a-z0-9_]{2,40}$ variable-rgx=[A-Za-z_][A-Za-z0-9_]{1,40}$ # Regular expression which should only match correct list comprehension / # generator expression variable names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_,setUp,setUpClass,tearDown,f # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # Regular expression which should only match functions or classes name which do # not require a docstring # Amplify: Do not require docstrings in test functions or classes no-docstring-rgx=(__.*__)|([a-z_][a-z0-9_]{2,30}$)|(test_.*)|(.*_test)|(Tests?[A-Z].*)|(.*Tests?) [FORMAT] # Maximum number of characters on a single line. # WGen: Line length 120 max-line-length=120 # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=XXX,TODO [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=4 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes [TYPECHECK] # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). ignored-classes=SQLObject # When zope mode is activated, add a predefined set of Zope acquired attributes # to generated-members. zope=no # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E0201 when accessed. generated-members=REQUEST,acl_users,aq_parent [VARIABLES] # Tells whether we should check for unused import in __init__ files. init-import=no # A regular expression matching names used for dummy variables (i.e. not used). dummy-variables-rgx=_|dummy # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= [CLASSES] # List of interface methods to ignore, separated by a comma. This is used for # instance to not check methods defines in Zope's Interface base class. ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp [DESIGN] # Maximum number of arguments for function / method max-args=10 # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.* # Maximum number of locals for function / method body max-locals=25 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branchs=12 # Maximum number of statements in function / method body max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=14 # Minimum number of public methods for a class (see R0903). min-public-methods=0 # Maximum number of public methods for a class (see R0904). max-public-methods=100 [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,string,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= jenkinsapi-0.2.16/build.xml0000644000175000017500000000211512262651567014045 0ustar ahs3ahs3 jenkinsapi-0.2.16/setup.cfg0000644000175000017500000000030512262651567014044 0ustar ahs3ahs3[build_sphinx] source-dir = doc/source build-dir = doc/build all_files = 1 [upload_sphinx] upload-dir = doc/build/html [nosetests] detailed-errors = 1 with-coverage = 1 cover-package = jenkinsapi