Merge "Replace singleton lists with None defaults" into feature/zuulv3
diff --git a/doc/source/admin/drivers/gerrit.rst b/doc/source/admin/drivers/gerrit.rst
index 296e47f..48f8a33 100644
--- a/doc/source/admin/drivers/gerrit.rst
+++ b/doc/source/admin/drivers/gerrit.rst
@@ -145,9 +145,9 @@
   This may be used for any event.  It requires that a certain kind of
   approval be present for the current patchset of the change (the
   approval could be added by the event in question).  It follows the
-  same syntax as the :ref:`"approval" pipeline requirement
-  <gerrit-pipeline-require-approval>`. For each specified criteria
-  there must exist a matching approval.
+  same syntax as :attr:`pipeline.require.<gerrit
+  source>.approval`. For each specified criteria there must exist a
+  matching approval.
 
 **reject-approval**
   This takes a list of approvals in the same format as
@@ -192,14 +192,12 @@
 named *my-gerrit* must have a Code Review vote of +2 in order to be
 enqueued into the pipeline.
 
-.. zuul:configobject:: pipeline.require.<source>
+.. attr:: pipeline.require.<gerrit source>
 
    The dictionary passed to the Gerrit pipeline `require` attribute
    supports the following attributes:
 
-   .. _gerrit-pipeline-require-approval:
-
-   .. zuul:attr:: approval
+   .. attr:: approval
 
       This requires that a certain kind of approval be present for the
       current patchset of the change (the approval could be added by
@@ -207,24 +205,24 @@
       which are optional and are combined together so that there must
       be an approval matching all specified requirements.
 
-      .. zuul:attr:: username
+      .. attr:: username
 
          If present, an approval from this username is required.  It is
          treated as a regular expression.
 
-      .. zuul:attr:: email
+      .. attr:: email
 
          If present, an approval with this email address is required.  It is
          treated as a regular expression.
 
-      .. zuul:attr:: older-than
+      .. attr:: older-than
 
          If present, the approval must be older than this amount of time
          to match.  Provide a time interval as a number with a suffix of
          "w" (weeks), "d" (days), "h" (hours), "m" (minutes), "s"
          (seconds).  Example ``48h`` or ``2d``.
 
-      .. zuul:attr:: newer-than
+      .. attr:: newer-than
 
          If present, the approval must be newer than this amount
          of time to match.  Same format as "older-than".
@@ -235,33 +233,33 @@
       may either be a single value or a list: ``Verified: [1, 2]``
       would match either a +1 or +2 vote.
 
-   .. zuul:attr:: open
+   .. attr:: open
 
       A boolean value (``true`` or ``false``) that indicates whether
       the change must be open or closed in order to be enqueued.
 
-   .. zuul:attr:: current-patchset
+   .. attr:: current-patchset
 
       A boolean value (``true`` or ``false``) that indicates whether the
       change must be the current patchset in order to be enqueued.
 
-   .. zuul:attr:: status
+   .. attr:: status
 
       A string value that corresponds with the status of the change
       reported by the trigger.
 
-.. zuul:configobject:: pipeline.reject.<source>
+.. attr:: pipeline.reject.<gerrit source>
 
    The `reject` attribute is the mirror of the `require` attribute.  It
    also accepts a dictionary under the connection name.  This
    dictionary supports the following attributes:
 
-   .. zuul:attr:: approval
+   .. attr:: approval
 
       This takes a list of approvals. If an approval matches the
       provided criteria the change can not be entered into the
-      pipeline. It follows the same syntax as the :ref:`approval
-      pipeline requirement above <gerrit-pipeline-require-approval>`.
+      pipeline. It follows the same syntax as
+      :attr:`pipeline.require.<gerrit source>.approval`.
 
       Example to reject a change with any negative vote::
 
diff --git a/doc/source/admin/drivers/github.rst b/doc/source/admin/drivers/github.rst
index 6619322..298e6b4 100644
--- a/doc/source/admin/drivers/github.rst
+++ b/doc/source/admin/drivers/github.rst
@@ -216,14 +216,12 @@
 named *my-github* must have an approved code review in order to be
 enqueued into the pipeline.
 
-.. zuul:configobject:: pipeline.require.<source>
+.. attr:: pipeline.require.<github source>
 
    The dictionary passed to the GitHub pipeline `require` attribute
    supports the following attributes:
 
-   .. _github-pipeline-require-review:
-
-   .. zuul:attr:: review
+   .. attr:: review
 
       This requires that a certain kind of code review be present for
       the pull request (it could be added by the event in question).
@@ -231,46 +229,46 @@
       are combined together so that there must be a code review
       matching all specified requirements.
 
-      .. zuul:attr:: username
+      .. attr:: username
 
          If present, a code review from this username is required.  It
          is treated as a regular expression.
 
-      .. zuul:attr:: email
+      .. attr:: email
 
          If present, a code review with this email address is
          required.  It is treated as a regular expression.
 
-      .. zuul:attr:: older-than
+      .. attr:: older-than
 
          If present, the code review must be older than this amount of
          time to match.  Provide a time interval as a number with a
          suffix of "w" (weeks), "d" (days), "h" (hours), "m"
          (minutes), "s" (seconds).  Example ``48h`` or ``2d``.
 
-      .. zuul:attr:: newer-than
+      .. attr:: newer-than
 
          If present, the code review must be newer than this amount of
          time to match.  Same format as "older-than".
 
-      .. zuul:attr:: type
+      .. attr:: type
 
          If present, the code review must match this type (or types).
 
          .. TODO: what types are valid?
 
-      .. zuul:attr:: permission
+      .. attr:: permission
 
          If present, the author of the code review must have this
          permission (or permissions).  The available values are
          ``read``, ``write``, and ``admin``.
 
-   .. zuul:attr:: open
+   .. attr:: open
 
       A boolean value (``true`` or ``false``) that indicates whether
       the change must be open or closed in order to be enqueued.
 
-   .. zuul:attr:: current-patchset
+   .. attr:: current-patchset
 
       A boolean value (``true`` or ``false``) that indicates whether
       the item must be associated with the latest commit in the pull
@@ -279,26 +277,26 @@
       .. TODO: this could probably be expanded upon -- under what
          circumstances might this happen with github
 
-   .. zuul:attr:: status
+   .. attr:: status
 
       A string value that corresponds with the status of the pull
       request.  The syntax is ``user:status:value``.
 
-   .. zuul:attr:: label
+   .. attr:: label
 
       A string value indicating that the pull request must have the
       indicated label (or labels).
 
 
-.. zuul:configobject:: pipeline.reject.<source>
+.. attr:: pipeline.reject.<github source>
 
    The `reject` attribute is the mirror of the `require` attribute.  It
    also accepts a dictionary under the connection name.  This
    dictionary supports the following attributes:
 
-   .. zuul:attr:: review
+   .. attr:: review
 
       This takes a list of code reviews.  If a code review matches the
       provided criteria the pull request can not be entered into the
-      pipeline.  It follows the same syntax as the :ref:`review
-      pipeline requirement above <github-pipeline-require-review>`.
+      pipeline.  It follows the same syntax as
+      :attr:`pipeline.require.<github source>.review`
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 80cde65..7c0d587 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -34,6 +34,8 @@
 #extensions = ['sphinx.ext.intersphinx']
 #intersphinx_mapping = {'python': ('http://docs.python.org/2.7', None)}
 
+primary_domain = 'zuul'
+
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
 
diff --git a/doc/source/user/config.rst b/doc/source/user/config.rst
index 2ef581a..5ca8677 100644
--- a/doc/source/user/config.rst
+++ b/doc/source/user/config.rst
@@ -122,24 +122,22 @@
 
 .. TODO: See TODO for more annotated examples of common pipeline configurations.
 
-.. zuul:configobject:: pipeline
+.. attr:: pipeline
 
    The attributes available on a pipeline are as follows (all are
    optional unless otherwise specified):
 
-   .. zuul:attr:: name
+   .. attr:: name
       :required:
 
       This is used later in the project definition to indicate what jobs
       should be run for events in the pipeline.
 
-   .. zuul:attr:: manager
+   .. attr:: manager
       :required:
 
       There are currently two schemes for managing pipelines:
 
-      .. _independent_pipeline_manager:
-
       .. zuul:value:: independent
 
          Every event in this pipeline should be treated as independent
@@ -158,8 +156,6 @@
          pipeline. In that case, the changes have already merged, so
          the results can not affect any other events in the pipeline.
 
-      .. _dependent_pipeline_manager:
-
       .. zuul:value:: dependent
 
          The dependent pipeline manager is designed for gating.  It
@@ -184,7 +180,7 @@
          For more detail on the theory and operation of Zuul's
          dependent pipeline manager, see: :doc:`gating`.
 
-   .. zuul:attr:: allow-secrets
+   .. attr:: allow-secrets
 
       This is a boolean which can be used to prevent jobs which
       require secrets from running in this pipeline.  Some pipelines
@@ -196,34 +192,34 @@
 
       For more information, see :ref:`secret`.
 
-   .. zuul:attr:: description
+   .. attr:: description
 
       This field may be used to provide a textual description of the
       pipeline.  It may appear in the status page or in documentation.
 
-   .. zuul:attr:: success-message
+   .. attr:: success-message
 
       The introductory text in reports when all the voting jobs are
       successful.  Defaults to "Build successful."
 
-   .. zuul:attr:: failure-message
+   .. attr:: failure-message
 
       The introductory text in reports when at least one voting job
       fails.  Defaults to "Build failed."
 
-   .. zuul:attr:: merge-failure-message
+   .. attr:: merge-failure-message
 
       The introductory text in the message reported when a change
       fails to merge with the current state of the repository.
       Defaults to "Merge failed."
 
-   .. zuul:attr:: footer-message
+   .. attr:: footer-message
 
       Supplies additional information after test results.  Useful for
       adding information about the CI system such as debugging and
       contact details.
 
-   .. zuul:attr:: trigger
+   .. attr:: trigger
 
       At least one trigger source must be supplied for each pipeline.
       Triggers are not exclusive -- matching events may be placed in
@@ -236,7 +232,7 @@
 
    .. _pipeline-require:
 
-   .. zuul:attr:: require
+   .. attr:: require
 
       If this section is present, it establishes pre-requisites for
       any kind of item entering the Pipeline.  Regardless of how the
@@ -251,7 +247,7 @@
 
    .. _pipeline-reject:
 
-   .. zuul:attr:: reject
+   .. attr:: reject
 
       If this section is present, it establishes pre-requisites that
       can block an item from being enqueued. It can be considered a
@@ -261,7 +257,7 @@
       type of the connection will dictate which options are available.
       See :ref:`drivers`.
 
-   .. zuul:attr:: dequeue-on-new-patchset
+   .. attr:: dequeue-on-new-patchset
 
       Normally, if a new patchset is uploaded to a change that is in a
       pipeline, the existing entry in the pipeline will be removed
@@ -269,7 +265,7 @@
       merge as well.  To suppress this behavior (and allow jobs to
       continue running), set this to ``false``.  Default: ``true``.
 
-   .. zuul:attr:: ignore-dependencies
+   .. attr:: ignore-dependencies
 
       In any kind of pipeline (dependent or independent), Zuul will
       attempt to enqueue all dependencies ahead of the current change
@@ -279,7 +275,7 @@
       pipeline, set this to ``true``.  This option is ignored by
       dependent pipelines.  The default is: ``false``.
 
-   .. zuul:attr:: precedence
+   .. attr:: precedence
 
       Indicates how the build scheduler should prioritize jobs for
       different pipelines.  Each pipeline may have one precedence,
@@ -295,7 +291,7 @@
    driver which implements it.  See :ref:`drivers` for more
    information.
 
-   .. zuul:attr:: success
+   .. attr:: success
 
       Describes where Zuul should report to if all the jobs complete
       successfully.  This section is optional; if it is omitted, Zuul
@@ -305,25 +301,25 @@
       connection name. The options available depend on the driver for
       the supplied connection.
 
-   .. zuul:attr:: failure
+   .. attr:: failure
 
       These reporters describe what Zuul should do if at least one job
       fails.
 
-   .. zuul:attr:: merge-failure
+   .. attr:: merge-failure
 
       These reporters describe what Zuul should do if it is unable to
       merge in the patchset. If no merge-failure reporters are listed
       then the ``failure`` reporters will be used to notify of
       unsuccessful merges.
 
-   .. zuul:attr:: start
+   .. attr:: start
 
       These reporters describe what Zuul should do when a change is
       added to the pipeline.  This can be used, for example, to reset
       a previously reported result.
 
-   .. zuul:attr:: disabled
+   .. attr:: disabled
 
       These reporters describe what Zuul should do when a pipeline is
       disabled.  See ``disable-after-consecutive-failures``.
@@ -333,7 +329,7 @@
    due to a problem with an external dependency, or unusually high
    non-deterministic test failures).
 
-   .. zuul:attr:: disable-after-consecutive-failures
+   .. attr:: disable-after-consecutive-failures
 
       If set, a pipeline can enter a ''disabled'' state if too many
       changes in a row fail. When this value is exceeded the pipeline
@@ -342,7 +338,7 @@
       ``disabled`` reporters.  (No ``start`` reports are made when a
       pipeline is disabled).
 
-   .. zuul:attr:: window
+   .. attr:: window
 
       Dependent pipeline managers only. Zuul can rate limit dependent
       pipelines in a manner similar to TCP flow control.  Jobs are
@@ -352,13 +348,13 @@
       be a positive integer value. A value of ``0`` disables rate
       limiting on the DependentPipelineManager.  Default: ``20``.
 
-   .. zuul:attr:: window-floor
+   .. attr:: window-floor
 
       Dependent pipeline managers only. This is the minimum value for
       the window described above. Should be a positive non zero
       integer value.  Default: ``3``.
 
-   .. zuul:attr:: window-increase-type
+   .. attr:: window-increase-type
 
       Dependent pipeline managers only. This value describes how the
       window should grow when changes are successfully merged by
@@ -369,13 +365,13 @@
       previous window value and the result will become the window
       size.  Default: ``linear``.
 
-   .. zuul:attr:: window-increase-factor
+   .. attr:: window-increase-factor
 
       Dependent pipeline managers only. The value to be added or
       multiplied against the previous window value to determine the
       new window after successful change merges.  Default: ``1``.
 
-   .. zuul:attr:: window-decrease-type
+   .. attr:: window-decrease-type
 
       Dependent pipeline managers only. This value describes how the
       window should shrink when changes are not able to be merged by
@@ -386,7 +382,7 @@
       previous window value and the result will become the window
       size.  Default: ``exponential``.
 
-   .. zuul:attr:: window-decrease-factor
+   .. attr:: window-decrease-factor
 
       Dependent pipline managers only. The value to be subtracted or
       divided against the previous window value to determine the new
diff --git a/doc/source/user/gating.rst b/doc/source/user/gating.rst
index 3398892..c1d04a7 100644
--- a/doc/source/user/gating.rst
+++ b/doc/source/user/gating.rst
@@ -41,7 +41,7 @@
 developers to create changes at a rate faster than they can be tested
 and merged.
 
-Zuul's :ref:`dependent pipeline manager<dependent_pipeline_manager>`
+Zuul's :value:`dependent pipeline manager<pipeline.manager.dependent>`
 allows for parallel execution of test jobs for gating while ensuring
 changes are tested correctly, exactly as if they had been tested one
 at a time.  It does this by performing speculative execution of test
diff --git a/zuul/ansible/callback/zuul_stream.py b/zuul/ansible/callback/zuul_stream.py
index 078e1c9..9dd724d 100644
--- a/zuul/ansible/callback/zuul_stream.py
+++ b/zuul/ansible/callback/zuul_stream.py
@@ -285,10 +285,16 @@
         self._log("")
 
     def v2_runner_on_skipped(self, result):
-        reason = result._result.get('skip_reason')
-        if reason:
-            # No reason means it's an item, which we'll log differently
-            self._log_message(result, status='skipping', msg=reason)
+        if result._task.loop:
+            self._items_done = False
+            self._deferred_result = dict(result._result)
+        else:
+            reason = result._result.get('skip_reason')
+            if reason:
+                # No reason means it's an item, which we'll log differently
+                self._log_message(result, status='skipping', msg=reason)
+                # Log an extra blank line to get space after each skip
+                self._log("")
 
     def v2_runner_item_on_skipped(self, result):
         reason = result._result.get('skip_reason')
@@ -297,14 +303,14 @@
         else:
             self._log_message(result, status='skipping')
 
+        if self._deferred_result:
+            self._process_deferred(result)
+
     def v2_runner_on_ok(self, result):
         if (self._play.strategy == 'free'
                 and self._last_task_banner != result._task._uuid):
             self._print_task_banner(result._task)
 
-        if result._task.action in ('include', 'include_role', 'setup'):
-            return
-
         result_dict = dict(result._result)
 
         self._clean_results(result_dict, result._task.action)
@@ -388,8 +394,6 @@
 
         if self._deferred_result:
             self._process_deferred(result)
-        # Log an extra blank line to get space after each task
-        self._log("")
 
     def v2_runner_item_on_failed(self, result):
         result_dict = dict(result._result)
@@ -434,10 +438,13 @@
         self._items_done = True
         result_dict = self._deferred_result
         self._deferred_result = None
+        status = result_dict.get('status')
 
-        self._log_message(
-            result, "All items complete",
-            status=result_dict['status'])
+        if status:
+            self._log_message(result, "All items complete", status=status)
+
+        # Log an extra blank line to get space after each task
+        self._log("")
 
     def _print_task_banner(self, task):
 
diff --git a/zuul/sphinx/zuul.py b/zuul/sphinx/zuul.py
index a4fb127..7798720 100644
--- a/zuul/sphinx/zuul.py
+++ b/zuul/sphinx/zuul.py
@@ -14,21 +14,22 @@
 
 from sphinx import addnodes
 from sphinx.domains import Domain
+from sphinx.roles import XRefRole
 from sphinx.directives import ObjectDescription
+from sphinx.util.nodes import make_refnode
+from docutils import nodes
+
+from typing import Dict # noqa
 
 
 class ZuulConfigObject(ObjectDescription):
     object_names = {
         'attr': 'attribute',
-        'configobject': 'configuration object',
     }
 
     def get_path(self):
-        obj = self.env.ref_context.get('zuul:configobject')
         attr_path = self.env.ref_context.get('zuul:attr_path', [])
         path = []
-        if obj:
-            path.append(obj)
         if attr_path:
             path.extend(attr_path)
         return path
@@ -49,6 +50,15 @@
             signode['ids'].append(targetname)
             signode['first'] = (not self.names)
             self.state.document.note_explicit_target(signode)
+            objects = self.env.domaindata['zuul']['objects']
+            if targetname in objects:
+                self.state_machine.reporter.warning(
+                    'duplicate object description of %s, ' % targetname +
+                    'other instance in ' +
+                    self.env.doc2path(objects[targetname][0]) +
+                    ', use :noindex: for one of them',
+                    line=self.lineno)
+            objects[targetname] = (self.env.docname, self.objtype)
 
         objname = self.object_names.get(self.objtype, self.objtype)
         if self.parent_pathname:
@@ -60,17 +70,6 @@
                                           targetname, '', None))
 
 
-class ZuulConfigobjectDirective(ZuulConfigObject):
-    has_content = True
-
-    def before_content(self):
-        self.env.ref_context['zuul:configobject'] = self.names[-1]
-
-    def handle_signature(self, sig, signode):
-        signode += addnodes.desc_name(sig, sig)
-        return sig
-
-
 class ZuulAttrDirective(ZuulConfigObject):
     has_content = True
 
@@ -110,11 +109,35 @@
     label = 'Zuul'
 
     directives = {
-        'configobject': ZuulConfigobjectDirective,
         'attr': ZuulAttrDirective,
         'value': ZuulValueDirective,
     }
 
+    roles = {
+        'attr': XRefRole(innernodeclass=nodes.inline,  # type: ignore
+                         warn_dangling=True),
+        'value': XRefRole(innernodeclass=nodes.inline,  # type: ignore
+                          warn_dangling=True),
+    }
+
+    initial_data = {
+        'objects': {},
+    }  # type: Dict[str, Dict]
+
+    def resolve_xref(self, env, fromdocname, builder, type, target,
+                     node, contnode):
+        objects = self.data['objects']
+        name = type + '-' + target
+        obj = objects.get(name)
+        if obj:
+            return make_refnode(builder, fromdocname, obj[0], name,
+                                contnode, name)
+
+    def clear_doc(self, docname):
+        for fullname, (fn, _l) in list(self.data['objects'].items()):
+            if fn == docname:
+                del self.data['objects'][fullname]
+
 
 def setup(app):
     app.add_domain(ZuulDomain)