Convert zuul.projects to a dict

This follows-on from I4476b9d4915d107e29b91229287865bff0ada305 where
we are converting zuul.projects to a dict for easier access.

With the dependent changes, there should be no in-tree users of
zuul.projects so we can do this switch.  We will then convert
zuul._project users back to zuul.projects for the final removal
(I283e66293110aa3bc99acb1cbc6eb3e0cc11f750).

This updates documentation, and also gives some samples of how to use
the variables (the "| list" is a bit of a gotcha trick -- python3's
values() returns a view, so it is necessary for iteration).

Depends-On: Id9a7c137ca5bed25d81087201091157c8401576a
Depends-On: I9d88f405f34d1c5f75ebf4f52cedfaaab20c3bda
Change-Id: I3c011f72933e98ccbf8badf0e9197c8659766c51
diff --git a/doc/source/user/jobs.rst b/doc/source/user/jobs.rst
index 0639b8b..0932abe 100644
--- a/doc/source/user/jobs.rst
+++ b/doc/source/user/jobs.rst
@@ -220,14 +220,15 @@
          `src/git.example.com/org/project`.
 
    .. var:: projects
-      :type: list
+      :type: dict
 
       A list of all projects prepared by Zuul for the item.  It
       includes, at least, the item's own project.  It also includes
       the projects of any items this item depends on, as well as the
       projects that appear in :attr:`job.required-projects`.
 
-      This is a list of dictionaries, with each element consisting of:
+      This is a dictionary of dictionaries.  Each value has a key of
+      the `canonical_name`, then each entry consists of:
 
       .. var:: name
 
@@ -264,6 +265,20 @@
          This may be influenced by the branch or tag associated with
          the item as well as the job configuration.
 
+      For example, to access the source directory of a single known
+      project, you might use::
+
+        {{ zuul.projects['git.example.com/org/project'].src_dir }}
+
+      To iterate over the project list, you might write a task
+      something like::
+
+        - name: Sample project iteration
+          debug:
+            msg: "Project {{ item.name }} is at {{ item.src_dir }}
+          with_items: {{ zuul.projects.values() | list }}
+
+
    .. var:: _projects
       :type: dict
 
diff --git a/zuul/executor/client.py b/zuul/executor/client.py
index 09321e4..3779099 100644
--- a/zuul/executor/client.py
+++ b/zuul/executor/client.py
@@ -183,8 +183,7 @@
         if (hasattr(item.change, 'newrev') and item.change.newrev
             and item.change.newrev != '0' * 40):
             zuul_params['newrev'] = item.change.newrev
-        zuul_params['projects'] = []  # Set below
-        zuul_params['_projects'] = {}  # transitional to convert to dict
+        zuul_params['projects'] = {}  # Set below
         zuul_params['items'] = dependent_changes
 
         params = dict()
@@ -257,7 +256,7 @@
                 params['projects'].append(make_project_dict(project))
                 projects.add(project)
         for p in projects:
-            zuul_params['_projects'][p.canonical_name] = (dict(
+            zuul_params['projects'][p.canonical_name] = (dict(
                 name=p.name,
                 short_name=p.name.split('/')[-1],
                 # Duplicate this into the dict too, so that iterating
@@ -269,12 +268,10 @@
             ))
         # We are transitioning "projects" from a list to a dict
         # indexed by canonical name, as it is much easier to access
-        # values in ansible.  Existing callers are converted to
-        # "_projects", then once "projects" is unused we switch it,
-        # then convert callers back.  Finally when "_projects" is
-        # unused it will be removed.
-        for cn, p in zuul_params['_projects'].items():
-            zuul_params['projects'].append(p)
+        # values in ansible.  Existing callers have been converted to
+        # "_projects" and "projects" is swapped; we will convert users
+        # back to "projects" and remove this soon.
+        zuul_params['_projects'] = zuul_params['projects']
 
         build = Build(job, uuid)
         build.parameters = params