diff --git a/.gitignore b/.gitignore index f16ed4806..31f9e5c1c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ config .tox .test +doc/build +doc/source/sourcecode diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 000000000..8ce076e4b --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/JenkinsJobBuilder.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/JenkinsJobBuilder.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/JenkinsJobBuilder" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/JenkinsJobBuilder" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/doc/source/builders.rst b/doc/source/builders.rst new file mode 100644 index 000000000..a53166184 --- /dev/null +++ b/doc/source/builders.rst @@ -0,0 +1,7 @@ +.. _builders: + +Builders +======== + +.. automodule:: builders + :members: diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 000000000..c6591b617 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- +# +# Jenkins Job Builder documentation build configuration file, created by +# sphinx-quickstart on Mon Sep 10 19:36:21 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 sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('../../jenkins_jobs/modules')) + +# -- 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.coverage', + 'jenkins_jobs.sphinx.yaml'] + +# 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'Jenkins Job Builder' +copyright = u'2012, Jenkins Job Builder Maintainers' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '1.0' +# The full version, including alpha/beta/rc tags. +release = '1.0' + +# 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 = False + +# 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 = 'JenkinsJobBuilderdoc' + + +# -- 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', 'JenkinsJobBuilder.tex', u'Jenkins Job Builder Documentation', + u'Jenkins Job Builder Maintainers', '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', 'jenkinsjobbuilder', u'Jenkins Job Builder Documentation', + [u'Jenkins Job Builder Maintainers'], 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', 'JenkinsJobBuilder', u'Jenkins Job Builder Documentation', + u'Jenkins Job Builder Maintainers', 'JenkinsJobBuilder', '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' diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst new file mode 100644 index 000000000..59f7e7488 --- /dev/null +++ b/doc/source/configuration.rst @@ -0,0 +1,194 @@ +Configuration +============= + +The job definitions for Jenkins Job Builder are kept in any number of +YAML files, in whatever way you would like to organize them. When you +invoke ``jenkins-jobs`` you may specify either the path of a single +YAML file, or a directory. If you choose a directory, all of the +.yaml (or .yml) files in that directory will be read, and all the jobs +they define will be created or updated. + +Definitions +----------- + +Jenkins Job Builder understands a few basic object types which are +described in the next sections. + +.. _job: + +Job +^^^ + +The most straightforward way to create a job is simply to define a +Job in YAML. It looks like this:: + + - job: + name: job-name + +That's not very useful, so you'll want to add some actions such as +:ref:`builders`, and perhaps :ref:`publishers`. Those are described +later. There are a few basic optional fields for a Job definition:: + + - job: + name: job-name + project-type: freestyle + defaults: global + +**project-type** + Defaults to "freestyle", but "maven" can also be specified. + +**defaults** + Specifies a set of `Defaults`_ to use for this job, defaults to + ''global''. If you have values that are common to all of your jobs, + create a ``global`` `Defaults`_ object to hold them, and no further + configuration of individual jobs is necessary. If some jobs + should not use the ``global`` defaults, use this field to specify a + different set of defaults. + +Job Template +^^^^^^^^^^^^ + +If you need several jobs defined that are nearly identical, except +perhaps in their names, SCP targets, etc., then you may use a Job +Template to specify the particulars of the job, and then use a +`Project`_ to realize the job with appropriate variable substitution. + +A Job Template has the same syntax as a `Job`_, but you may add +variables anywhere in the definition. Variables are indicated by +enclosing them in braces, e.g., ``{name}`` will substitute the +variable `name`. When using a variable in a string field, it is good +practice to wrap the entire string in quotes, even if the rules of +YAML syntax don't require it because the value of the variable may +require quotes after substitution. + +You must include a variable in the ``name`` field of a Job Template +(otherwise, every instance would have the same name). For example:: + + - job-template: + name: '{name}-unit-tests' + +Will not cause any job to be created in Jenkins, however, it will +define a template that you can use to create jobs with a `Project`_ +definition. It's name will depend on what is supplied to the +`Project`_. + +Project +^^^^^^^ + +The purpose of a project is to collect related jobs together, and +provide values for the variables in a `Job Template`_. It looks like +this:: + + - project: + name: project-name + jobs: + - {name}-unit-tests + +Any number of arbitrarily named additional fields may be specified, +and they will be available for variable substitution in the job +template. Any job templates listed under ``jobs:`` will be realized +with those values. The example above would create the job called +'project-name-unit-tests' in Jenkins. + +Job Group +^^^^^^^^^ + +If you have several Job Templates that should all be realized +together, you can define a Job Group to collect them. Simply use the +Job Group where you would normally use a `Job Template`_ and all of +the Job Templates in the Job Group will be realized. For example:: + + - job-template: + name: '{name}-python-26' + + - job-template: + name: '{name}-python-27' + + - job-group: + name: python-jobs + jobs: + - '{name}-python-26' + - '{name}-python-27' + + - project: + name: foo + jobs: + - python-jobs + +Would cause the jobs `foo-python-26` and `foo-python-27` to be created +in Jekins. + +.. _macro: + +Macro +^^^^^ + +Many of the actions of a `Job`_, such as builders or publishers, can +be defined as a Macro, and then that Macro used in the `Job`_ +description. Builders are described later, but let's introduce a +simple one now to illustrate the Macro functionality. This snippet +will instruct Jenkins to execute "make test" as part of the job:: + + - job: + name: foo-test + + builders: + - shell: 'make test' + +If you wanted to define a macro (which won't save much typing in this +case, but could still be useful to centralize the definition of a +commonly repeated task), the configuration would look like:: + + - builder: + name: make-test + builders: + - shell: 'make test' + + - job: + name: foo-test + + builders: + - make-test + +This allows you to create complex actions (and even sequences of +actions) in YAML that look like first-class Jenkins Job Builder +actions. Not every attribute supports Macros, check the documentation +for the action before you try to use a Macro for it. + +Defaults +^^^^^^^^ + +Defaults collect job attributes (including actions) and will supply +those values when the job is created, unless superseded by a value in +the 'Job'_ definition. If a set of Defaults is specified with the +name ``global``, that will be used by all `Job`_ (and `Job Template`_) +definitions unless they specify a different Default object with the +``default`` attribute. For example:: + + - defaults: + name: global + description: 'Do not edit this job through the web!' + +Will set the job description for every job created. + + +Modules +------- + +The bulk of the job definitions come from the following modules. + +.. toctree:: + :maxdepth: 2 + + project_freestyle + project_maven + general + builders + notifications + parameters + properties + publishers + scm + triggers + wrappers + zuul diff --git a/doc/source/extending.rst b/doc/source/extending.rst new file mode 100644 index 000000000..e587d234f --- /dev/null +++ b/doc/source/extending.rst @@ -0,0 +1,67 @@ +.. _extending: + +Extending +========= + +Jenkins Job Builder is quite modular. It is easy to add new +attributes to existing components, a new module to support a Jenkins +plugin, or include locally defined methods to deal with an +idiosyncratic build system. + +XML Processing +-------------- + +Most of the work of building XML from the YAML configuration file is +handled by individual functions that implement a single +characteristic. For example, see the +``jenkins_jobs/modules/builders.py`` file for the Python module that +implements the standard Jenkins builders. The ``shell`` function at +the top of the file implements the standard `Execute a shell` build +step. All of the YAML to XML functions in Jenkins Job Builder have +the same signature: + +.. _component_interface: +.. py:function:: component(parser, xml_parent, data) + :noindex: + + :arg YAMLParser parser: the jenkins jobs YAML parser + :arg Element xml_parent: this attribute's parent XML element + :arg dict data: the YAML data structure for this attribute and below + +The function is expected to examine the YAML data structure and create +new XML nodes and attach them to the xml_parent element. This general +pattern is applied throughout the included modules. + +.. _module: + +Modules +------- + +Nearly all of Jenkins Job Builder is implemented in modules. The main +program has no concept of builders, publishers, properties, or any +other aspects of job definition. Each of those building blocks is +defined in a module, and due to the use of setuptools entry points, +most modules are easily extensible with new components. + +To add a new module, define a class that inherits from +:py:class:`jenkins_jobs.modules.base.Base`, and add it to the +``jenkins_jobs.modules`` entry point in your setup.py. + +.. autoclass:: jenkins_jobs.modules.base.Base + :members: + :undoc-members: + :private-members: + +.. _component: + +Components +---------- + +Most of the standard modules supply a number of components, and it's +easy to provide your own components for use by those modules. For +instance, the Builders module provides several builders, such as the +`shell` builder as well as the `trigger_builds` builder. If you +wanted to add a new builder, all you need to do is write a function +that conforms to the :ref:`Component Interface `, +and then add that function to the appropriate entry point (via a +setup.py file). diff --git a/doc/source/general.rst b/doc/source/general.rst new file mode 100644 index 000000000..62c0452b1 --- /dev/null +++ b/doc/source/general.rst @@ -0,0 +1,10 @@ +.. _general: + +General Job Configuration +========================= + +.. automodule:: assignednode + :members: + +.. automodule:: logrotate + :members: diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 000000000..20da5b213 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,34 @@ +.. Jenkins Job Builder documentation master file, created by + sphinx-quickstart on Mon Sep 10 19:36:21 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Jenkins Job Builder +=================== + +Jenkins Job Builder takes simple descriptions of Jenkins_ jobs in +YAML_ format, and uses them to configure Jenkins. You can keep your +job descriptions in human readable text format in a version control +system to make changes and auditing easier. It also has a flexible +template system, so creating many similarly configured jobs is easy. + + +.. _Jenkins: http://jenkins-ci.org/ +.. _YAML: http://www.yaml.org/ + +Contents: + +.. toctree:: + :maxdepth: 3 + + installation + configuration + extending + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/doc/source/installation.rst b/doc/source/installation.rst new file mode 100644 index 000000000..808282272 --- /dev/null +++ b/doc/source/installation.rst @@ -0,0 +1,72 @@ +Installation +============ + +To install Jenkins Job Builder, run:: + + sudo setup.py install + +The OpenStack project uses puppet to manage its infrastructure +systems, including Jenkins. If you use Puppet, you can use the +`OpenStack Jenkins module`__ to install Jenkins Job Builder. + +__ https://github.com/openstack/openstack-ci-puppet/tree/master/modules/jenkins + + +Configuration File +------------------ + +After installation, you will need to create a configuration file. By +default, `jenkins-jobs` looks in +``/etc/jenkins_jobs/jenkins_jobs.ini`` but you may specify an +alternate location when running `jenkins-jobs`. The file should have +the following format:: + + [jenkins] + user=USERNAME + password=PASSWORD + url=JENKINS_URL + +**user** + This should be the name of a user previously defined in Jenkins with + the permissions necessary to read, create, delete, and configure + jobs. + +**password** + The API token for the user specified. You cat get this through the + Jenkins management interface under ``People`` -> username -> + ``Configure`` and then click the ``Show API Token`` button. + +**url** + The base URL for your Jenkins installation. + + +Running +------- + +After it's installed and configured, you can invoke Jenkins Job +Builder by running ``jenkins-jobs``. You won't be able to do anything +useful just yet without a configuration which is discussed in the next +section). But you should be able to get help on the various commands +by running:: + + jenkins-jobs --help + jenkins-jobs update --help + jenkins-jobs test --help + (etc.) + +Once you have a configuration defined, you can test it with:: + + jenkins-jobs test /path/to/config -o /path/to/output + +That will write XML files to the output directory for all of the jobs +defined in the configuration directory. When you're satisfied, you +can run:: + + jenkins-jobs update /path/to/config + +Which will upload the configurations to Jenkins if needed. Jenkins +Job Builder maintains a cache of previously configured jobs, so that +you can run that command as often as you like, and it will only update +the configuration in Jenkins if the defined configuration has changed +since the last time it was run. Note: if you modify a job directly in +Jenkins, jenkins-jobs will not know about it and will not update it. diff --git a/doc/source/notifications.rst b/doc/source/notifications.rst new file mode 100644 index 000000000..b6251a7af --- /dev/null +++ b/doc/source/notifications.rst @@ -0,0 +1,7 @@ +.. _notifications: + +Notifications +============= + +.. automodule:: notifications + :members: diff --git a/doc/source/parameters.rst b/doc/source/parameters.rst new file mode 100644 index 000000000..996adc020 --- /dev/null +++ b/doc/source/parameters.rst @@ -0,0 +1,7 @@ +.. _parameters: + +Parameters +========== + +.. automodule:: parameters + :members: diff --git a/doc/source/project_freestyle.rst b/doc/source/project_freestyle.rst new file mode 100644 index 000000000..28d4810e9 --- /dev/null +++ b/doc/source/project_freestyle.rst @@ -0,0 +1,7 @@ +.. _project_freestyle: + +Freestyle Project +================= + +.. automodule:: project_freestyle + :members: diff --git a/doc/source/project_maven.rst b/doc/source/project_maven.rst new file mode 100644 index 000000000..a362a0047 --- /dev/null +++ b/doc/source/project_maven.rst @@ -0,0 +1,7 @@ +.. _project_maven: + +Maven Project +============= + +.. automodule:: project_maven + :members: diff --git a/doc/source/properties.rst b/doc/source/properties.rst new file mode 100644 index 000000000..a4eb4a9f7 --- /dev/null +++ b/doc/source/properties.rst @@ -0,0 +1,7 @@ +.. _properties: + +Properties +========== + +.. automodule:: properties + :members: diff --git a/doc/source/publishers.rst b/doc/source/publishers.rst new file mode 100644 index 000000000..e615038c4 --- /dev/null +++ b/doc/source/publishers.rst @@ -0,0 +1,7 @@ +.. _publishers: + +Publishers +========== + +.. automodule:: publishers + :members: diff --git a/doc/source/scm.rst b/doc/source/scm.rst new file mode 100644 index 000000000..c6c80e45f --- /dev/null +++ b/doc/source/scm.rst @@ -0,0 +1,7 @@ +.. _scm: + +SCM +=== + +.. automodule:: scm + :members: diff --git a/doc/source/triggers.rst b/doc/source/triggers.rst new file mode 100644 index 000000000..1446617aa --- /dev/null +++ b/doc/source/triggers.rst @@ -0,0 +1,7 @@ +.. _triggers: + +Triggers +======== + +.. automodule:: triggers + :members: diff --git a/doc/source/wrappers.rst b/doc/source/wrappers.rst new file mode 100644 index 000000000..47ad22be5 --- /dev/null +++ b/doc/source/wrappers.rst @@ -0,0 +1,7 @@ +.. _wrappers: + +Wrappers +======== + +.. automodule:: wrappers + :members: diff --git a/doc/source/zuul.rst b/doc/source/zuul.rst new file mode 100644 index 000000000..38a34ccc5 --- /dev/null +++ b/doc/source/zuul.rst @@ -0,0 +1,7 @@ +.. _zuul_doc: + +Zuul +==== + +.. automodule:: zuul + :members: diff --git a/jenkins_jobs/modules/assignednode.py b/jenkins_jobs/modules/assignednode.py index 6ce935f86..4c7603574 100644 --- a/jenkins_jobs/modules/assignednode.py +++ b/jenkins_jobs/modules/assignednode.py @@ -12,10 +12,21 @@ # License for the specific language governing permissions and limitations # under the License. -# Jenkins Job module for assigned nodes -# To use add the folowing into your YAML: -# assignednode: -# - node: 'oneiric' + +""" +The Assigned Node section allows you to specify which Jenkins node (or +named group) should run the specified job. It adds the ``node`` +attribute to the :ref:`Job` definition. + +Example:: + + job: + name: test_job + node: precise + +That speficies that the job should be run on a Jenkins node or node group +named ``precise``. +""" import xml.etree.ElementTree as XML import jenkins_jobs.modules.base diff --git a/jenkins_jobs/modules/base.py b/jenkins_jobs/modules/base.py index ca377051e..f18232dda 100644 --- a/jenkins_jobs/modules/base.py +++ b/jenkins_jobs/modules/base.py @@ -19,14 +19,76 @@ import yaml class Base(object): + """ + A base class for a Jenkins Job Builder Module. + + The module is initialized before any YAML is parsed. + + :arg ModuleRegistry registry: the global module registry. + """ + + #: The sequence number for the module. Modules are invoked in the + #: order of their sequence number in order to produce consistently + #: ordered XML output. sequence = 10 def __init__(self, registry): self.registry = registry + def handle_data(self, parser): + """This method is called before any XML is generated. By + overriding this method, the module may manipulate the YAML + data structure on the parser however it likes before any XML + is generated. If it has changed the data structure at all, it + must return ``True``, otherwise, it must return ``False``. + + :arg YAMLParser parser: the global YAML Parser + :rtype: boolean + """ + + return False + + def gen_xml(self, parser, xml_parent, data): + """Update the XML element tree based on YAML data. Override + this method to add elements to the XML output. Create new + Element objects and add them to the xml_parent. The YAML data + structure must not be modified. + + :arg YAMLParser parser: the global YAML Parser + :arg Element xml_parent: the parent XML element + :arg dict data: the YAML data structure + """ + + pass + def _dispatch(self, component_type, component_list_type, parser, xml_parent, component, template_data={}): + """This is a private helper method that you can call from your + implementation of gen_xml. It allows your module to define a + type of component, and benefit from extensibility via Python + entry points and Jenkins Job Builder :ref:`Macros `. + + :arg string component_type: the name of the component + (e.g., `builder`) + :arg string component_list_type: the plural name of the component + type (e.g., `builders`) + :arg YAMLParser parser: the global YMAL Parser + :arg Element xml_parent: the parent XML element + :arg dict template_data: values that should be interpolated into + the component definition + + The value of `component_list_type` will be used to look up + possible implementations of the component type via entry + points (entry points provide a list of components, so it + should be plural) while `component_type` will be used to look + for macros (they are defined singularly, and should not be + plural). + + See the Publishers module for a simple example of how to use + this method. + """ + if isinstance(component, dict): # The component is a sigleton dictionary of name: dict(args) name, component_data = component.items()[0] diff --git a/jenkins_jobs/modules/builders.py b/jenkins_jobs/modules/builders.py index 1144464f0..cd9d0473a 100644 --- a/jenkins_jobs/modules/builders.py +++ b/jenkins_jobs/modules/builders.py @@ -12,22 +12,65 @@ # License for the specific language governing permissions and limitations # under the License. -# Jenkins Job module for builders -# To use add the folowing into your YAML: -# builders: -# - 'gerrit_git_prep' -# - 'python26' + +""" +Builders define actions that the Jenkins job should execute. Examples +include shell scripts or maven targets. The ``builders`` attribute in +the :ref:`Job` definition accepts a list of builders to invoke. They +may be components defined below, locally defined macros (using the top +level definition of ``builder:``, or locally defined components found +via the ``jenkins_jobs.builders`` entry point. + +**Component**: builders + :Macro: builder + :Entry Point: jenkins_jobs.builders + +Example:: + + job: + name: test_job + + builders: + - shell: "make test" + +""" + import xml.etree.ElementTree as XML import jenkins_jobs.modules.base - def shell(parser, xml_parent, data): + """yaml: shell + Execute a shell command. + + :Parameter: the shell command to execute + + Example:: + + builders: + - shell: "make test" + + """ shell = XML.SubElement(xml_parent, 'hudson.tasks.Shell') XML.SubElement(shell, 'command').text = data - def trigger_builds(parser, xml_parent, data): + """yaml: trigger-builds + Trigger builds of other jobs. + + :arg str project: the Jenkins project to trigger + :arg str predefined-parameters: + key/value pairs to be passed to the job (optional) + + Example:: + + builders: + - trigger-builds: + - project: "build_started" + predefined-parameters: + FOO="bar" + + """ tbuilder = XML.SubElement(xml_parent, 'hudson.plugins.parameterizedtrigger.TriggerBuilder') configs = XML.SubElement(tbuilder, 'configs') diff --git a/jenkins_jobs/modules/logrotate.py b/jenkins_jobs/modules/logrotate.py index 0cd11c970..6d5b8bcb0 100644 --- a/jenkins_jobs/modules/logrotate.py +++ b/jenkins_jobs/modules/logrotate.py @@ -12,13 +12,23 @@ # License for the specific language governing permissions and limitations # under the License. -# Jenkins Job module for logrotate -# To use add the folowing into your YAML: -# logrotate: -# daysToKeep: 3 -# numToKeep: 20 -# artifactDaysToKeep: -1 -# artifactNumToKeep: -1 + +""" +The Logrotate section allows you to automatically remove old build +history. It adds the ``logrotate`` attribute to the :ref:`Job` +definition. + +Example:: + + job: + name: test_job + logrotate: + daysToKeep: 3 + numToKeep: 20 + artifactDaysToKeep: -1 + artifactNumToKeep: -1 +""" + import xml.etree.ElementTree as XML import jenkins_jobs.modules.base diff --git a/jenkins_jobs/modules/notifications.py b/jenkins_jobs/modules/notifications.py new file mode 100644 index 000000000..7b69ad417 --- /dev/null +++ b/jenkins_jobs/modules/notifications.py @@ -0,0 +1,74 @@ +# Copyright 2012 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +""" +The Notifications module allows you to configure Jenkins to notify +other applications about various build phases. It requires the +Jenkins notification plugin. + +**Component**: notifications + :Macro: notification + :Entry Point: jenkins_jobs.notifications + +Example:: + + job: + name: test_job + + notifications: + - http: + url: http://example.com/jenkins_endpoint +""" + + +import xml.etree.ElementTree as XML +import jenkins_jobs.modules.base + + +def http_endpoint(parser, xml_parent, data): + """yaml: http + Defines an HTTP notification endpoint. + + :arg str url: URL of the endpoint + + Example:: + + notifications: + - http: + url: http://example.com/jenkins_endpoint + """ + endpoint_element = XML.SubElement(xml_parent, + 'com.tikal.hudson.plugins.notification.Endpoint') + XML.SubElement(endpoint_element, 'protocol').text = 'HTTP' + XML.SubElement(endpoint_element, 'url').text = data['url'] + + +class Notifications(jenkins_jobs.modules.base.Base): + sequence = 22 + + def gen_xml(self, parser, xml_parent, data): + properties = xml_parent.find('properties') + if properties is None: + properties = XML.SubElement(xml_parent, 'properties') + + notifications = data.get('notifications', []) + if notifications: + notify_element = XML.SubElement(properties, + 'com.tikal.hudson.plugins.notification.HudsonNotificationProperty') + endpoints_element = XML.SubElement(notify_element, 'endpoints') + + for endpoint in notifications: + self._dispatch('notification', 'notifications', + parser, endpoints_element, endpoint) diff --git a/jenkins_jobs/modules/parameters.py b/jenkins_jobs/modules/parameters.py new file mode 100644 index 000000000..c164b09df --- /dev/null +++ b/jenkins_jobs/modules/parameters.py @@ -0,0 +1,166 @@ +# Copyright 2012 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +""" +The Parameters module allows you to specify build parameters for a job. + +**Component**: parameters + :Macro: parameter + :Entry Point: jenkins_jobs.parameters + +Example:: + + job: + name: test_job + + parameters: + - string: + name: FOO + default: bar + description: "A parameter named FOO, defaults to 'bar'." +""" + + +import xml.etree.ElementTree as XML +import jenkins_jobs.modules.base + + +def base_param(parser, xml_parent, data, do_default, ptype): + pdef = XML.SubElement(xml_parent, ptype) + XML.SubElement(pdef, 'name').text = data['name'] + XML.SubElement(pdef, 'description').text = data['description'] + if do_default: + default = data.get('default', None) + if default: + XML.SubElement(pdef, 'defaultValue').text = default + else: + XML.SubElement(pdef, 'defaultValue') + + +def string_param(parser, xml_parent, data): + """yaml: string + A string parameter. + + :arg str name: the name of the parameter + :arg str default: the default value of the parameter (optional) + :arg str description: a description of the parameter (optional) + + Example:: + + parameters: + - string: + name: FOO + default: bar + description: "A parameter named FOO, defaults to 'bar'." + """ + base_param(parser, xml_parent, data, True, + 'hudson.model.StringParameterDefinition') + + +def bool_param(parser, xml_parent, data): + """yaml: bool + A boolean parameter. + + :arg str name: the name of the parameter + :arg str default: the default value of the parameter (optional) + :arg str description: a description of the parameter (optional) + + Example:: + + parameters: + - bool: + name: FOO + default: false + description: "A parameter named FOO, defaults to 'false'." + """ + data['default'] = str(data.get('default', 'false')).lower() + base_param(parser, xml_parent, data, True, + 'hudson.model.BooleanParameterDefinition') + + +def file_param(parser, xml_parent, data): + """yaml: bool + A file parameter. + + :arg str name: the target location for the file upload + :arg str description: a description of the parameter (optional) + + Example:: + + parameters: + - file: + name: test.txt + description: "Upload test.txt." + """ + base_param(parser, xml_parent, data, False, + 'hudson.model.FileParameterDefinition') + + +def text_param(parser, xml_parent, data): + """yaml: string + A text parameter. + + :arg str name: the name of the parameter + :arg str default: the default value of the parameter (optional) + :arg str description: a description of the parameter (optional) + + Example:: + + parameters: + - text: + name: FOO + default: bar + description: "A parameter named FOO, defaults to 'bar'." + """ + base_param(parser, xml_parent, data, True, + 'hudson.model.TextParameterDefinition') + + +def label_param(parser, xml_parent, data): + """yaml: label + A node label parameter. + + :arg str name: the name of the parameter + :arg str default: the default value of the parameter (optional) + :arg str description: a description of the parameter (optional) + + Example:: + + parameters: + - label: + name: node + default: precise + description: "The node on which to run the job" + """ + base_param(parser, xml_parent, data, True, + 'org.jvnet.jenkins.plugins.nodelabelparameter.LabelParameterDefinition') + + +class Parameters(jenkins_jobs.modules.base.Base): + sequence = 21 + + def gen_xml(self, parser, xml_parent, data): + properties = xml_parent.find('properties') + if properties is None: + properties = XML.SubElement(xml_parent, 'properties') + + parameters = data.get('parameters', []) + if parameters: + pdefp = XML.SubElement(properties, + 'hudson.model.ParametersDefinitionProperty') + pdefs = XML.SubElement(pdefp, 'parameterDefinitions') + for param in parameters: + self._dispatch('parameter', 'parameters', + parser, pdefs, param) diff --git a/jenkins_jobs/modules/project_freestyle.py b/jenkins_jobs/modules/project_freestyle.py index b4c3b4697..cc9a7c7c2 100644 --- a/jenkins_jobs/modules/project_freestyle.py +++ b/jenkins_jobs/modules/project_freestyle.py @@ -12,13 +12,21 @@ # License for the specific language governing permissions and limitations # under the License. -# Jenkins Job module for maven projects -# To use you add the following into your YAML: -# maven: -# root_module: -# group_id: com.google.gerrit -# artifact_id: gerrit-parent -# goals: 'test' + +""" +The Freestyle Project module handles creating freestyle Jenkins +projects (i.e., those that do not use Maven). You may specify +``freestyle`` in the ``project-type`` attribute to the :ref:`Job` +definition if you wish, though it is the default, so you may omit +``project-type`` altogether if you are creating a freestyle project. + +Example:: + + job: + name: test_job + project-type: freestyle +""" + import xml.etree.ElementTree as XML import jenkins_jobs.modules.base diff --git a/jenkins_jobs/modules/project_maven.py b/jenkins_jobs/modules/project_maven.py index 5bca62059..aa48d955b 100644 --- a/jenkins_jobs/modules/project_maven.py +++ b/jenkins_jobs/modules/project_maven.py @@ -12,13 +12,29 @@ # License for the specific language governing permissions and limitations # under the License. -# Jenkins Job module for maven projects -# To use you add the following into your YAML: -# maven: -# root_module: -# group_id: com.google.gerrit -# artifact_id: gerrit-parent -# goals: 'test' + +""" +The Maven Project module handles creating Maven Jenkins projects. To +create a Maven project, specify ``maven`` in the ``project-type`` +attribute to the :ref:`Job` definition. + +It also requires a ``maven`` section in the :ref:`Job` definition. +All of the fields below are required, except ``root-pom``, whose +default is ``pom.xml``. + +Example:: + + job: + name: doc_job + project-type: maven + + maven: + root-module: + group-id: org.example.docs + artifact-id: example-guide + root-pom: doc/src/pom.xml + goals: "clean generate-sources" +""" import xml.etree.ElementTree as XML import jenkins_jobs.modules.base diff --git a/jenkins_jobs/modules/properties.py b/jenkins_jobs/modules/properties.py index b53330729..568da2139 100644 --- a/jenkins_jobs/modules/properties.py +++ b/jenkins_jobs/modules/properties.py @@ -12,14 +12,42 @@ # License for the specific language governing permissions and limitations # under the License. -# Jenkins Job module for job properties -# No additional YAML needed + +""" +The Properties module supplies a wide range of options that are +implemented as Jenkins job properties. + +**Component**: properties + :Macro: property + :Entry Point: jenkins_jobs.properties + +Example:: + + job: + name: test_job + + properties: + - github: + url: https://github.com/openstack-ci/jenkins-job-builder/ +""" + import xml.etree.ElementTree as XML import jenkins_jobs.modules.base def github(parser, xml_parent, data): + """yaml: github + Sets the GitHub URL for the project. + + :arg str url: the GitHub URL + + Example:: + + properties: + - github: + url: https://github.com/openstack-ci/jenkins-job-builder/ + """ github = XML.SubElement(xml_parent, 'com.coravy.hudson.plugins.github.GithubProjectProperty') github_url = XML.SubElement(github, 'projectUrl') @@ -27,6 +55,20 @@ def github(parser, xml_parent, data): def throttle(parser, xml_parent, data): + """yaml: throttle + Throttles the number of builds for this job. + + :arg int max-per-node: max concurrent builds per node (default 0) + :arg int max-total: max concurrent builds (default 0) + :arg bool enabled: whether throttling is enabled (default True) + :arg str option: TODO: describe throttleOption + + Example:: + + properties: + - throttle: + max-total: 4 + """ throttle = XML.SubElement(xml_parent, 'hudson.plugins.throttleconcurrents.ThrottleJobProperty') XML.SubElement(throttle, 'maxConcurrentPerNode').text = str( @@ -42,7 +84,27 @@ def throttle(parser, xml_parent, data): XML.SubElement(throttle, 'throttleOption').text = data.get('option') XML.SubElement(throttle, 'configVersion').text = '1' + def inject(parser, xml_parent, data): + """yaml: inject + Allows you to inject evironment variables into the build. + + :arg str properties-file: file to read with properties (optional) + :arg str properties-content: key=value properties (optional) + :arg str script-file: file with script to run (optional) + :arg str script-content: script to run (optional) + :arg str groovy-content: groovy script to run (optional) + :arg bool load-from-master: load files from master (default false) + :arg bool enabled: injection enabled (default true) + :arg bool keep-system-variables: keep system variables (default true) + :arg bool keep-build-variables: keep build variable (default true) + + Example:: + + properties: + - inject: + properties-content: FOO=bar + """ inject = XML.SubElement(xml_parent, 'EnvInjectJobProperty') info = XML.SubElement(inject, 'info') @@ -65,7 +127,17 @@ def inject(parser, xml_parent, data): XML.SubElement(inject, 'keepBuildVariables').text = str( data.get('keep-build-variables', 'true')).lower() + def authenticated_build(parser, xml_parent, data): + """yaml: authenticated-build + Specifies an authorization matrix where only authenticated users + may trigger a build. + + Example:: + + properties: + - authenticated-build + """ # TODO: generalize this if data: security = XML.SubElement(xml_parent, @@ -74,76 +146,14 @@ def authenticated_build(parser, xml_parent, data): 'hudson.model.Item.Build:authenticated' -def base_param(parser, xml_parent, data, do_default, ptype): - pdef = XML.SubElement(xml_parent, ptype) - XML.SubElement(pdef, 'name').text = data['name'] - XML.SubElement(pdef, 'description').text = data['description'] - if do_default: - default = data.get('default', None) - if default: - XML.SubElement(pdef, 'defaultValue').text = default - else: - XML.SubElement(pdef, 'defaultValue') - - -def string_param(parser, xml_parent, data): - base_param(parser, xml_parent, data, True, - 'hudson.model.StringParameterDefinition') - - -def bool_param(parser, xml_parent, data): - data['default'] = str(data.get('default', 'false')).lower() - base_param(parser, xml_parent, data, True, - 'hudson.model.BooleanParameterDefinition') - - -def file_param(parser, xml_parent, data): - base_param(parser, xml_parent, data, False, - 'hudson.model.FileParameterDefinition') - - -def text_param(parser, xml_parent, data): - base_param(parser, xml_parent, data, True, - 'hudson.model.TextParameterDefinition') - - -def label_param(parser, xml_parent, data): - base_param(parser, xml_parent, data, True, - 'org.jvnet.jenkins.plugins.nodelabelparameter.LabelParameterDefinition') - - -def http_endpoint(parser, xml_parent, data): - endpoint_element = XML.SubElement(xml_parent, - 'com.tikal.hudson.plugins.notification.Endpoint') - XML.SubElement(endpoint_element, 'protocol').text = 'HTTP' - XML.SubElement(endpoint_element, 'url').text = data['url'] - - class Properties(jenkins_jobs.modules.base.Base): sequence = 20 def gen_xml(self, parser, xml_parent, data): - properties = XML.SubElement(xml_parent, 'properties') + properties = xml_parent.find('properties') + if properties is None: + properties = XML.SubElement(xml_parent, 'properties') for prop in data.get('properties', []): self._dispatch('property', 'properties', parser, properties, prop) - - parameters = data.get('parameters', []) - if parameters: - pdefp = XML.SubElement(properties, - 'hudson.model.ParametersDefinitionProperty') - pdefs = XML.SubElement(pdefp, 'parameterDefinitions') - for param in parameters: - self._dispatch('parameter', 'parameters', - parser, pdefs, param) - - notifications = data.get('notifications', []) - if notifications: - notify_element = XML.SubElement(properties, - 'com.tikal.hudson.plugins.notification.HudsonNotificationProperty') - endpoints_element = XML.SubElement(notify_element, 'endpoints') - - for endpoint in notifications: - self._dispatch('notification', 'notifications', - parser, endpoints_element, endpoint) diff --git a/jenkins_jobs/modules/publishers.py b/jenkins_jobs/modules/publishers.py index 566acfff7..e8748e3cc 100644 --- a/jenkins_jobs/modules/publishers.py +++ b/jenkins_jobs/modules/publishers.py @@ -12,14 +12,47 @@ # License for the specific language governing permissions and limitations # under the License. -# Jenkins Job module for coverage publishers -# No additional YAML needed + +""" +Publishers define actions that the Jenkins job should perform after +the build is complete. + +**Component**: publishers + :Macro: publisher + :Entry Point: jenkins_jobs.publishers + +Example:: + + job: + name: test_job + + publishers: + - scp: + site: 'example.com' + source: 'doc/build/html/**/*' + target_path: 'project' +""" + import xml.etree.ElementTree as XML import jenkins_jobs.modules.base def archive(parser, xml_parent, data): + """yaml: archive + Archive build artifacts + + :arg str artifacts: path specifier for artifacts to archive + :arg str excludes: path specifier for artifacts to exclude + :arg bool latest_only: only keep the artifacts from the latest + successful build + + Example:: + + publishers: + - archive: + artifacts: *.tar.gz + """ archiver = XML.SubElement(xml_parent, 'hudson.tasks.ArtifactArchiver') artifacts = XML.SubElement(archiver, 'artifacts') artifacts.text = data['artifacts'] @@ -35,6 +68,20 @@ def archive(parser, xml_parent, data): def trigger_parameterized_builds(parser, xml_parent, data): + """yaml: trigger-parameterized-builds + Trigger parameterized builds of other jobs. + + :arg str project: name of the job to trigger + :arg str predefined-parameters: parameters to pass to the other + job (optional) + :arg str condition: when to trigger the other job (default 'ALWAYS') + + Example:: + + publishers: + - trigger-parameterized-builds: + project: other_job + """ tbuilder = XML.SubElement(xml_parent, 'hudson.plugins.parameterizedtrigger.BuildTrigger') configs = XML.SubElement(tbuilder, 'configs') @@ -59,6 +106,14 @@ def trigger_parameterized_builds(parser, xml_parent, data): def coverage(parser, xml_parent, data): + """yaml: coverage + Generate a cobertura coverage report. + + Example:: + + publishers: + - coverage + """ cobertura = XML.SubElement(xml_parent, 'hudson.plugins.cobertura.CoberturaPublisher') XML.SubElement(cobertura, 'coberturaReportFile').text = '**/coverage.xml' @@ -114,18 +169,27 @@ def coverage(parser, xml_parent, data): XML.SubElement(cobertura, 'sourceEncoding').text = 'ASCII' -# Jenkins Job module for publishing via ftp -# publish: -# site: 'docs.openstack.org' -# remote_dir: 'dest/dir' -# source_files: 'base/source/dir/**' -# remove_prefix: 'base/source/dir' -# excludes: '**/*.exludedfiletype' -# -# This will upload everything under $workspace/base/source/dir to -# docs.openstack.org $ftpdir/dest/dir exluding the excluded file type. - def ftp(parser, xml_parent, data): + """yaml: ftp + Upload files via FTP. + + :arg str site: name of the ftp site + :arg str target: destination directory + :arg str source: source path specifier + :arg str excludes: excluded file pattern (optional) + :arg str remove-prefix: prefix to remove from uploaded file paths + (optional) + + Example:: + + publishers: + - ftp: + site: 'ftp.example.com' + target: 'dest/dir' + source: 'base/source/dir/**' + remove-prefix: 'base/source/dir' + excludes: '**/*.excludedfiletype' + """ outer_ftp = XML.SubElement(xml_parent, 'jenkins.plugins.publish__over__ftp.BapFtpPublisherPlugin') XML.SubElement(outer_ftp, 'consolePrefix').text = 'FTP: ' @@ -158,12 +222,18 @@ def ftp(parser, xml_parent, data): 'reference': '../..'}) -# Jenkins Job module for coverage publishers -# To use you add the following into your YAML: -# publisher: -# results: 'nosetests.xml' - def junit(parser, xml_parent, data): + """yaml: junit + Publish JUnit test results. + + :arg str results: results filename + + Example:: + + publishers: + - junit: + results: nosetests.xml + """ junitresult = XML.SubElement(xml_parent, 'hudson.tasks.junit.JUnitResultArchiver') XML.SubElement(junitresult, 'testResults').text = data['results'] @@ -192,6 +262,35 @@ def _violations_add_entry(xml_parent, name, data): def violations(parser, xml_parent, data): + """yaml: violations + Publish code style violations. + + The violations component accepts any number of dictionaries keyed + by the name of the violations system. The dictionary has the + following values: + + :arg int min: sunny threshold + :arg int max: stormy threshold + :arg int unstable: unstable threshold + :arg str pattern: report filename pattern + + Any system without a dictionary provided will use default values. + + Valid systems are: + + checkstyle, codenarc, cpd, cpplint, csslint, findbugs, fxcop, + gendarme, jcreport, jslint, pep8, pmd, pylint, simian, stylecop + + Example:: + + publishers: + - violations: + pep8: + min: 0 + max: 1 + unstable: 1 + pattern: '**/pep8.txt' + """ violations = XML.SubElement(xml_parent, 'hudson.plugins.violations.ViolationsPublisher') config = XML.SubElement(violations, 'config') @@ -234,6 +333,27 @@ def violations(parser, xml_parent, data): # keep_heirarchy: 'true' def scp(parser, xml_parent, data): + """yaml: scp + Upload files via SCP + + :arg str site: name of the scp site + :arg str target: destination directory + :arg str source: source path specifier + :arg bool keep-hierarchy: keep the file hierarchy when uploading + (default false) + :arg bool copy-after-failure: copy files even if the job fails + (default false) + :arg bool copy-console: copy the console log (default false); if + specified, omit 'target' + + Example:: + + publishers: + - scp: + site: 'example.com' + target: 'dest/dir' + source: 'base/source/dir/**' + """ site = data['site'] scp = XML.SubElement(xml_parent, 'be.certipost.hudson.plugin.SCPRepositoryPublisher') diff --git a/jenkins_jobs/modules/scm.py b/jenkins_jobs/modules/scm.py index 607a97c53..f2ced73a5 100644 --- a/jenkins_jobs/modules/scm.py +++ b/jenkins_jobs/modules/scm.py @@ -12,18 +12,47 @@ # License for the specific language governing permissions and limitations # under the License. -# Jenkins Job module for scm -# To use add the folowing into your YAML: -# scm: -# scm: 'true' -# or -# scm: 'false' + +""" +The SCM module allows you to specify the source code location for the +project. It adds the ``scm`` attribute to the :ref:`Job` definition, +which accepts a single scm definiton. + +**Component**: scm + :Macro: scm + :Entry Point: jenkins_jobs.scm + +Example:: + + job: + name: test_job + scm: + -git: + url: https://example.com/project.git + +""" + import xml.etree.ElementTree as XML import jenkins_jobs.modules.base def git(self, xml_parent, data): + """yaml: git + Specifies the git SCM repository for this job. + + :arg str url: URL of the git repository + :arg list(str) branches: list of branch specifiers to build + + Example:: + + scm: + -git: + url: https://example.com/project.git + branches: + - master + - stable + """ scm = XML.SubElement(xml_parent, 'scm', {'class': 'hudson.plugins.git.GitSCM'}) XML.SubElement(scm, 'configVersion').text = '2' diff --git a/jenkins_jobs/modules/triggers.py b/jenkins_jobs/modules/triggers.py index 00052900b..7e2799545 100644 --- a/jenkins_jobs/modules/triggers.py +++ b/jenkins_jobs/modules/triggers.py @@ -12,42 +12,69 @@ # License for the specific language governing permissions and limitations # under the License. -# Jenkins Job module for gerrit triggers -# To use add the following into your YAML: -# trigger: -# triggerOnPatchsetUploadedEvent: 'false' -# triggerOnChangeMergedEvent: 'false' -# triggerOnCommentAddedEvent: 'true' -# triggerOnRefUpdatedEvent: 'false' -# triggerApprovalCategory: 'APRV' -# triggerApprovalValue: 1 -# overrideVotes: 'true' -# gerritBuildSuccessfulVerifiedValue: 1 -# gerritBuildFailedVerifiedValue: -1 -# -# failureMessage: 'This change was unable to be automatically merged -# with the current state of the repository. Please rebase your change -# and upload a new patchset.' -# -# projects: -# - projectCompareType: 'PLAIN' -# projectPattern: 'openstack/nova' -# branchCompareType: 'ANT' -# branchPattern: '**' -# - projectCompareType: 'PLAIN' -# projectPattern: 'openstack/glance' -# branchCompareType: 'ANT' -# branchPattern: '**' -# ... -# -# triggerApprovalCategory and triggerApprovalValue only required -# if triggerOnCommentAddedEvent: 'true' + +""" +Triggers define what causes a jenkins job to start buliding. + +**Component**: triggers + :Macro: trigger + :Entry Point: jenkins_jobs.triggers + +Example:: + + job: + name: test_job + + triggers: + - timed: '@daily' +""" + import xml.etree.ElementTree as XML import jenkins_jobs.modules.base def gerrit(parser, xml_parent, data): + """yaml: gerrit + Trigger on a Gerrit event. + + :arg bool triggerOnPatchsetUploadedEvent: Trigger on patchset upload + :arg bool triggerOnChangeMergedEvent: Trigger on change merged + :arg bool triggerOnCommentAddedEvent: Trigger on comment added + :arg bool triggerOnRefUpdatedEvent: Trigger on ref-updated + :arg str triggerApprovalCategory: Approval category for comment added + :arg int triggerApprovalValue: Approval value for comment added + :arg bool overrideVotes: Override default vote values + :arg int gerritBuildSuccessfulVerifiedValue: Successful ''Verified'' value + :arg int gerritBuildFailedVerifiedValue: Failed ''Verified'' value + :arg str failureMessage: Message to leave on failure + :arg list projects: list of projects to match + + :Project: * **projectCompareType** (`str`) -- ''PLAIN'' or ''ANT'' + * **projectPattern** (`str`) -- Project name pattern to match + * **branchComprareType** (`str`) -- ''PLAIN'' or ''ANT'' + * **branchPattern** ('str') -- Branch name pattern to match + + You may select one or more gerrit events upon which to trigger. + You must also supply at least one project and branch, optionally + more. If you select the comment-added trigger, you should alse + indicate which approval category and value you want to trigger the + job. + + Example:: + + trigger: + - gerrit: + triggerOnCommentAddedEvent: true + triggerApprovalCategory: 'APRV' + triggerApprovalValue: 1 + projects: + - projectCompareType: 'PLAIN' + projectPattern: 'test-project' + branchCompareType: 'ANT' + branchPattern: '**' + """ + projects = data['projects'] gtrig = XML.SubElement(xml_parent, 'com.sonyericsson.hudson.plugins.gerrit.trigger.' @@ -94,26 +121,33 @@ def gerrit(parser, xml_parent, data): XML.SubElement(gtrig, 'customUrl') -# Jenkins Job module for scm polling triggers -# To use add the following into your YAML: -# trigger: -# pollscm: '@midnight' -# or -# pollscm: '*/15 * * * *' - def pollscm(parser, xml_parent, data): + """yaml: pollscm + Poll the SCM to determine if there has been a change. + + :Parameter: the polling interval (cron syntax) + + Example: + + trigger: + - pollscm: "\*/15 * * * \*" + """ + scmtrig = XML.SubElement(xml_parent, 'hudson.triggers.SCMTrigger') XML.SubElement(scmtrig, 'spec').text = data -# Jenkins Job module for timed triggers -# To use add the following into your YAML: -# trigger: -# timed: '@midnight' -# or -# timed: '*/15 * * * *' - def timed(parser, xml_parent, data): + """yaml: pollscm + Poll the SCM to determine if there has been a change. + + :Parameter: the polling interval (cron syntax) + + Example: + + trigger: + - pollscm: "@midnight" + """ scmtrig = XML.SubElement(xml_parent, 'hudson.triggers.TimerTrigger') XML.SubElement(scmtrig, 'spec').text = data diff --git a/jenkins_jobs/modules/wrappers.py b/jenkins_jobs/modules/wrappers.py index b8446f615..ecbe31933 100644 --- a/jenkins_jobs/modules/wrappers.py +++ b/jenkins_jobs/modules/wrappers.py @@ -12,13 +12,43 @@ # License for the specific language governing permissions and limitations # under the License. -# Jenkins Job module for wrappers + +""" +Wrappers can alter the way the build is run as well as the build output. + +**Component**: wrappers + :Macro: wrapper + :Entry Point: jenkins_jobs.wrappers + +Example:: + + job: + name: test_job + + wrappers: + - timeout: + timeout: 90 + fail: true +""" import xml.etree.ElementTree as XML import jenkins_jobs.modules.base def timeout(parser, xml_parent, data): + """yaml: timeout + Abort the build if it runs too long. + + :arg int timeout: Abort the build after this number of minutes + :arg bool fail: Mark the build as failed (default false) + + Example:: + + wrappers: + - timeout: + timeout: 90 + fail: true + """ twrapper = XML.SubElement(xml_parent, 'hudson.plugins.build__timeout.BuildTimeoutWrapper') tminutes = XML.SubElement(twrapper, 'timeoutMinutes') @@ -32,11 +62,27 @@ def timeout(parser, xml_parent, data): def timestamps(parser, xml_parent, data): + """yaml: timestamps + Add timestamps to the console log. + + Example:: + + wrappers: + - timestamps + """ XML.SubElement(xml_parent, 'hudson.plugins.timestamper.TimestamperBuildWrapper') def ansicolor(parser, xml_parent, data): + """yaml: ansicolor + Translate ANSI color codes to HTML in the console log. + + Example:: + + wrappers: + - ansicolor + """ XML.SubElement(xml_parent, 'hudson.plugins.ansicolor.AnsiColorBuildWrapper') diff --git a/jenkins_jobs/modules/zuul.py b/jenkins_jobs/modules/zuul.py index e07394b2e..a7aa9f504 100644 --- a/jenkins_jobs/modules/zuul.py +++ b/jenkins_jobs/modules/zuul.py @@ -12,11 +12,34 @@ # License for the specific language governing permissions and limitations # under the License. -# Jenkins Job module for Zuul +""" +The Zuul module adds triggers that configure jobs for use with Zuul_. + +.. _Zuul: http://ci.openstack.org/zuul/ +""" + +def zuul(): + """yaml: zuul + Configure this job to be triggered by Zuul. + + Example:: + + triggers: + - zuul + """ + +def zuul_post(): + """yaml: zuul-post + Configure this post-merge job to be triggered by Zuul. + + Example:: + + triggers: + - zuul-post + """ import jenkins_jobs.modules.base - ZUUL_PARAMETERS = [ {'string': {'description': 'Zuul provided key to link builds with Gerrit events', diff --git a/jenkins_jobs/sphinx/__init__.py b/jenkins_jobs/sphinx/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/jenkins_jobs/sphinx/yaml.py b/jenkins_jobs/sphinx/yaml.py new file mode 100644 index 000000000..ec7767fb9 --- /dev/null +++ b/jenkins_jobs/sphinx/yaml.py @@ -0,0 +1,137 @@ +# Copyright 2012 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# Most of this code originated in sphinx.domains.python and +# sphinx.ext.autodoc and has been only slightly adapted for use in +# subclasses here. + +# :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. +# :license: BSD, see LICENSE for details. + +import re +from sphinx.ext.autodoc import Documenter, FunctionDocumenter +from sphinx.domains.python import PyModulelevel +from sphinx import addnodes +from sphinx.locale import l_, _ + +yaml_sig_re = re.compile('yaml:\s*(.*)') + +class PyYAMLFunction(PyModulelevel): + def handle_signature(self, sig, signode): + """Transform a Python signature into RST nodes. + + Return (fully qualified name of the thing, classname if any). + + If inside a class, the current class name is handled intelligently: + * it is stripped from the displayed name if present + * it is added to the full name (return value) if not present + """ + name_prefix = None + name = sig + arglist = None + retann = None + + # determine module and class name (if applicable), as well as full name + modname = self.options.get( + 'module', self.env.temp_data.get('py:module')) + classname = self.env.temp_data.get('py:class') + + fullname=name + + signode['module'] = modname + signode['class'] = classname + signode['fullname'] = fullname + + sig_prefix = self.get_signature_prefix(sig) + if sig_prefix: + signode += addnodes.desc_annotation(sig_prefix, sig_prefix) + + if name_prefix: + signode += addnodes.desc_addname(name_prefix, name_prefix) + + anno = self.options.get('annotation') + + signode += addnodes.desc_name(name, name) + if not arglist: + if self.needs_arglist(): + # for callables, add an empty parameter list + signode += addnodes.desc_parameterlist() + if retann: + signode += addnodes.desc_returns(retann, retann) + if anno: + signode += addnodes.desc_annotation(' ' + anno, ' ' + anno) + return fullname, name_prefix + + _pseudo_parse_arglist(signode, arglist) + if retann: + signode += addnodes.desc_returns(retann, retann) + if anno: + signode += addnodes.desc_annotation(' ' + anno, ' ' + anno) + return fullname, name_prefix + + def get_index_text(self, modname, name_cls): + return _('%s (in module %s)') % (name_cls[0], modname) + +class YAMLFunctionDocumenter(FunctionDocumenter): + priority = FunctionDocumenter.priority + 10 + objtype = 'yamlfunction' + directivetype = 'yamlfunction' + + @classmethod + def can_document_member(cls, member, membername, isattr, parent): + if not FunctionDocumenter.can_document_member(member, membername, + isattr, parent): + return False + if yaml_sig_re.match(member.__doc__): + return True + return False + + def _find_signature(self, encoding=None): + docstrings = Documenter.get_doc(self, encoding, 2) + if len(docstrings) != 1: + return + doclines = docstrings[0] + setattr(self, '__new_doclines', doclines) + if not doclines: + return + # match first line of docstring against signature RE + match = yaml_sig_re.match(doclines[0]) + if not match: + return + name = match.group(1) + # ok, now jump over remaining empty lines and set the remaining + # lines as the new doclines + i = 1 + while i < len(doclines) and not doclines[i].strip(): + i += 1 + setattr(self, '__new_doclines', doclines[i:]) + return name + + def get_doc(self, encoding=None, ignore=1): + lines = getattr(self, '__new_doclines', None) + if lines is not None: + return [lines] + return Documenter.get_doc(self, encoding, ignore) + + def format_signature(self): + result = self._find_signature() + self._name = result + return '' + + def format_name(self): + return self._name + +def setup(app): + app.add_autodocumenter(YAMLFunctionDocumenter) + app.add_directive_to_domain('py', 'yamlfunction', PyYAMLFunction) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..0bf9222a9 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[build_sphinx] +all_files = 1 +build-dir = doc/build +source-dir = doc/source + diff --git a/setup.py b/setup.py index 360021e6d..d07783f47 100644 --- a/setup.py +++ b/setup.py @@ -44,14 +44,14 @@ setup(name='jenkins_job_builder', 'authenticated_build', ], 'jenkins_jobs.parameters': [ - 'string=jenkins_jobs.modules.properties:string_param', - 'bool=jenkins_jobs.modules.properties:bool_param', - 'file=jenkins_jobs.modules.properties:file_param', - 'text=jenkins_jobs.modules.properties:text_param', - 'label=jenkins_jobs.modules.properties:label_param', + 'string=jenkins_jobs.modules.parameters:string_param', + 'bool=jenkins_jobs.modules.parameters:bool_param', + 'file=jenkins_jobs.modules.parameters:file_param', + 'text=jenkins_jobs.modules.parameters:text_param', + 'label=jenkins_jobs.modules.parameters:label_param', ], 'jenkins_jobs.notifications': [ - 'http=jenkins_jobs.modules.properties:http_endpoint', + 'http=jenkins_jobs.modules.notifications:http_endpoint', ], 'jenkins_jobs.publishers': [ 'archive=jenkins_jobs.modules.publishers:archive', @@ -82,6 +82,8 @@ setup(name='jenkins_job_builder', 'builders=jenkins_jobs.modules.builders:Builders', 'logrotate=jenkins_jobs.modules.logrotate:LogRotate', 'properties=jenkins_jobs.modules.properties:Properties', + 'parameters=jenkins_jobs.modules.parameters:Parameters', + 'notifications=jenkins_jobs.modules.notifications:Notifications', 'publishers=jenkins_jobs.modules.publishers:Publishers', 'scm=jenkins_jobs.modules.scm:SCM', 'triggers=jenkins_jobs.modules.triggers:Triggers', diff --git a/tools/test-requires b/tools/test-requires new file mode 100644 index 000000000..6966869c7 --- /dev/null +++ b/tools/test-requires @@ -0,0 +1 @@ +sphinx diff --git a/test.sh b/tools/test.sh similarity index 100% rename from test.sh rename to tools/test.sh diff --git a/tox.ini b/tox.ini index ede2ac922..4d2801e02 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,10 @@ envlist = pep8, pyflakes [tox:jenkins] downloadcache = ~/cache/pip +[testenv] +deps = -r{toxinidir}/tools/pip-requires + -r{toxinidir}/tools/test-requires + [testenv:pep8] deps = pep8==1.2 commands = pep8 --repeat --show-source --exclude=.venv,.tox,dist,doc,build . @@ -13,11 +17,9 @@ deps = pyflakes commands = pyflakes jenkins_jobs jenkins-jobs setup.py [testenv:compare-xml-old] -deps = -r{toxinidir}/tools/pip-requires commands = ./jenkins-jobs test -o .test/old/out/ .test/old/config/ [testenv:compare-xml-new] -deps = -r{toxinidir}/tools/pip-requires commands = ./jenkins-jobs test -o .test/new/out/ .test/new/config/ [testenv:venv]