Browse code

Collections docs generation backport (#70515)

* Build documentation for Ansible-2.10 (formerly known as ACD).

Builds plugin docs from collections whose source is on galaxy

The new command downloads collections from galaxy, then finds the
plugins inside of them to get the documentation for those plugins.

* Update the python syntax checks
* docs builds can now require python 3.6+.

* Move plugin formatter code out to an external tool, antsibull-docs.
Collection owners want to be able to extract docs for their own
websites as well.
* The jinja2 filters, tests, and other support code have moved to antsibull
* Remove document_plugins as that has now been integrated into antsibull-docs

* Cleanup and bugfix to other build script code:
* The Commands class needed to have its metaclass set for abstractmethod
to work correctly
* Fix lint issues in some command plugins

* Add the docs/docsite/rst/collections to .gitignore as
everything in that directory will be generated so we don't want any of
it saved in the git repository
* gitignore the build dir and remove edit docs link on module pages

* Add docs/rst/collections as a directory to remove on make clean
* Split the collections docs from the main docs

* remove version and edit on github
* remove version banner for just collections
* clarify examples need collection keyword defined

* Remove references to plugin documentation locations that no longer exist.
* Perhaps the pages in plugins/*.rst should be deprecated
altogether and their content moved?
* If not, perhaps we want to rephrase and link into the collection
documentation?
* Or perhaps we want to link to the plugins which are present in
collections/ansible/builtin?

* Remove PYTHONPATH from the build-ansible calls
One of the design goals of the build-ansible.py script was for it to
automatically set its library path to include the checkout of ansible
and the library of code to implement itself. Because it automatically
includes the checkout of ansible, we don't need to set PYTHONPATH in
the Makefile any longer.

* Create a command to only build ansible-base plugin docs
* When building docs for devel, only build the ansible-base docs for
now. This is because antsibull needs support for building a "devel
tree" of docs. This can be changed once that is implemented
* When building docs for the sanity tests, only build the ansible-base
plugin docs for now. Those are the docs which are in this repo so
that seems appropriate for now.

* Docs: User guide overhaul, part 5 (#70307)

(cherry picked from commit db354c03002440bbcb286b4897307dbb981d02db)

* Need to return any error code from running antsibull-docs (#70763)

This way we fail early if there's a problem

(cherry picked from commit 1e3989c9f7919cbcfe82733711e13b93c026c2d8)

Co-authored-by: Alicia Cozine <879121+acozine@users.noreply.github.com>

Toshio Kuratomi authored on 2020/07/21 06:28:35
Showing 67 changed files
... ...
@@ -37,6 +37,8 @@ docs/docsite/rst/cli/ansible.rst
37 37
 docs/docsite/rst/dev_guide/collections_galaxy_meta.rst
38 38
 docs/docsite/rst/dev_guide/testing/sanity/index.rst.new
39 39
 docs/docsite/rst/modules/*.rst
40
+docs/docsite/rst/collections/*.rst
41
+docs/docsite/rst/collections/*/*.rst
40 42
 docs/docsite/rst/playbooks_directives.rst
41 43
 docs/docsite/rst/plugins_by_category.rst
42 44
 docs/docsite/rst/plugins/*/*.rst
... ...
@@ -275,7 +275,7 @@ linkcheckdocs:
275 275
 .PHONY: generate_rst
276 276
 generate_rst: lib/ansible/cli/*.py
277 277
 	mkdir -p ./docs/man/man1/ ; \
278
-	PYTHONPATH=./lib $(GENERATE_CLI) --template-file=docs/templates/man.j2 --output-dir=docs/man/man1/ --output-format man lib/ansible/cli/*.py
278
+	$(GENERATE_CLI) --template-file=docs/templates/man.j2 --output-dir=docs/man/man1/ --output-format man lib/ansible/cli/*.py
279 279
 
280 280
 
281 281
 docs: generate_rst
... ...
@@ -1,6 +1,6 @@
1 1
 OS := $(shell uname -s)
2 2
 SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()"):
3
-PLUGIN_FORMATTER=../../hacking/build-ansible.py document-plugins
3
+PLUGIN_FORMATTER=../../hacking/build-ansible.py docs-build
4 4
 TESTING_FORMATTER=../bin/testing_formatter.sh
5 5
 KEYWORD_DUMPER=../../hacking/build-ansible.py document-keywords
6 6
 CONFIG_DUMPER=../../hacking/build-ansible.py document-config
... ...
@@ -12,23 +12,35 @@ else
12 12
 CPUS ?= $(shell nproc)
13 13
 endif
14 14
 
15
-# Sets the build output directory if it's not already specified
15
+# Sets the build output directory for the main docsite if it's not already specified
16 16
 ifndef BUILDDIR
17 17
 	BUILDDIR = _build
18 18
 endif
19 19
 
20
-MODULE_ARGS=
20
+# Backwards compat for separate VARS
21
+PLUGIN_ARGS=
21 22
 ifdef MODULES
22
-	MODULE_ARGS = -l $(MODULES)
23
+ifndef PLUGINS
24
+	PLUGIN_ARGS = -l $(MODULES)
25
+else
26
+	PLUGIN_ARGS = -l $(MODULES),$(PLUGINS)
23 27
 endif
24
-
25
-PLUGIN_ARGS=
28
+else
26 29
 ifdef PLUGINS
27 30
 	PLUGIN_ARGS = -l $(PLUGINS)
28 31
 endif
32
+endif
33
+
29 34
 
30 35
 DOC_PLUGINS ?= become cache callback cliconf connection httpapi inventory lookup netconf shell strategy vars
31 36
 
37
+PYTHON=python
38
+# fetch version from project release.py as single source-of-truth
39
+VERSION := $(shell $(PYTHON) ../../packaging/release/versionhelper/version_helper.py --raw || echo error)
40
+ifeq ($(findstring error,$(VERSION)), error)
41
+$(error "version_helper failed")
42
+endif
43
+
32 44
 assertrst:
33 45
 ifndef rst
34 46
 	$(error specify document or pattern with rst=somefile.rst)
... ...
@@ -38,7 +50,8 @@ all: docs
38 38
 
39 39
 docs: htmldocs
40 40
 
41
-generate_rst: collections_meta config cli keywords modules plugins testing
41
+generate_rst: collections_meta config cli keywords plugins testing
42
+base_generate_rst: collections_meta config cli keywords base_plugins testing
42 43
 
43 44
 htmldocs: generate_rst
44 45
 	CPUS=$(CPUS) $(MAKE) -f Makefile.sphinx html
... ...
@@ -46,9 +59,12 @@ htmldocs: generate_rst
46 46
 singlehtmldocs: generate_rst
47 47
 	CPUS=$(CPUS) $(MAKE) -f Makefile.sphinx singlehtml
48 48
 
49
+base_singlehtmldocs: base_generate_rst
50
+	CPUS=$(CPUS) $(MAKE) -f Makefile.sphinx singlehtml
51
+
49 52
 linkcheckdocs: generate_rst
50 53
 		CPUS=$(CPUS) $(MAKE) -f Makefile.sphinx linkcheck
51
-		
54
+
52 55
 webdocs: docs
53 56
 
54 57
 #TODO: leaving htmlout removal for those having older versions, should eventually be removed also
... ...
@@ -58,7 +74,7 @@ clean:
58 58
 	-rm -rf $(BUILDDIR)/html
59 59
 	-rm -rf htmlout
60 60
 	-rm -rf module_docs
61
-	-rm -rf _build
61
+	-rm -rf $(BUILDDIR)
62 62
 	-rm -f .buildinfo
63 63
 	-rm -f objects.inv
64 64
 	-rm -rf *.doctrees
... ...
@@ -70,43 +86,44 @@ clean:
70 70
 	find . -type f \( -name "*~" -or -name "#*" \) -delete
71 71
 	find . -type f \( -name "*.swp" \) -delete
72 72
 	@echo "Cleaning up generated rst"
73
-	rm -f rst/modules/*_by_category.rst
74
-	rm -f rst/modules/list_of_*.rst
75
-	rm -f rst/modules/*_maintained.rst
76
-	rm -f rst/modules/*_module.rst
77
-	rm -f rst/modules/*_plugin.rst
78 73
 	rm -f rst/playbooks_directives.rst
79
-	rm -f rst/plugins/*/*.rst
80 74
 	rm -f rst/reference_appendices/config.rst
81 75
 	rm -f rst/reference_appendices/playbooks_keywords.rst
82 76
 	rm -f rst/dev_guide/collections_galaxy_meta.rst
83 77
 	rm -f rst/cli/*.rst
78
+	rm -rf rst/collections/*
79
+	@echo "Cleaning up legacy generated rst locations"
80
+	rm -rf rst/modules
81
+	rm -f rst/plugins/*/*.rst
84 82
 
85 83
 .PHONY: docs clean
86 84
 
87 85
 collections_meta: ../templates/collections_galaxy_meta.rst.j2
88
-	PYTHONPATH=../../lib $(COLLECTION_DUMPER) --template-file=../templates/collections_galaxy_meta.rst.j2 --output-dir=rst/dev_guide/ ../../lib/ansible/galaxy/data/collections_galaxy_meta.yml
86
+	$(COLLECTION_DUMPER) --template-file=../templates/collections_galaxy_meta.rst.j2 --output-dir=rst/dev_guide/ ../../lib/ansible/galaxy/data/collections_galaxy_meta.yml
89 87
 
90 88
 # TODO: make generate_man output dir cli option
91 89
 cli:
92 90
 	mkdir -p rst/cli
93
-	PYTHONPATH=../../lib $(GENERATE_CLI) --template-file=../templates/cli_rst.j2 --output-dir=rst/cli/ --output-format rst ../../lib/ansible/cli/*.py
91
+	$(GENERATE_CLI) --template-file=../templates/cli_rst.j2 --output-dir=rst/cli/ --output-format rst ../../lib/ansible/cli/*.py
94 92
 
95 93
 keywords: ../templates/playbooks_keywords.rst.j2
96
-	PYTHONPATH=../../lib $(KEYWORD_DUMPER) --template-dir=../templates --output-dir=rst/reference_appendices/ ./keyword_desc.yml
94
+	$(KEYWORD_DUMPER) --template-dir=../templates --output-dir=rst/reference_appendices/ ./keyword_desc.yml
97 95
 
98 96
 config: ../templates/config.rst.j2
99
-	PYTHONPATH=../../lib $(CONFIG_DUMPER) --template-file=../templates/config.rst.j2 --output-dir=rst/reference_appendices/ ../../lib/ansible/config/base.yml
100
-
101
-modules: ../templates/plugin.rst.j2
102
-	PYTHONPATH=../../lib $(PLUGIN_FORMATTER) -t rst --template-dir=../templates --module-dir=../../lib/ansible/modules -o rst/modules/ $(MODULE_ARGS)
103
-
104
-plugins: ../templates/plugin.rst.j2
105
-	@echo "looping over doc plugins"
106
-	for plugin in $(DOC_PLUGINS); \
107
-	do \
108
-		PYTHONPATH=../../lib $(PLUGIN_FORMATTER) -t rst --plugin-type $$plugin --template-dir=../templates --module-dir=../../lib/ansible/plugins/$$plugin -o rst $(PLUGIN_ARGS); \
109
-	done
97
+	$(CONFIG_DUMPER) --template-file=../templates/config.rst.j2 --output-dir=rst/reference_appendices/ ../../lib/ansible/config/base.yml
98
+
99
+# For now, if we're building on devel, just build base docs.  In the future we'll want to build docs that
100
+# are the latest versions on galaxy (using a different antsibull-docs subcommand)
101
+plugins:
102
+	if expr match "$(VERSION)" '.*[.]dev[0-9]\+$$' &> /dev/null; then \
103
+		$(PLUGIN_FORMATTER) base -o rst $(PLUGIN_ARGS);\
104
+	else \
105
+		$(PLUGIN_FORMATTER) full -o rst $(PLUGIN_ARGS);\
106
+	fi
107
+
108
+# This only builds the plugin docs included with ansible-base
109
+base_plugins:
110
+	$(PLUGIN_FORMATTER) base -o rst $(PLUGIN_ARGS);\
110 111
 
111 112
 testing:
112 113
 	$(TESTING_FORMATTER)
... ...
@@ -10,26 +10,27 @@
10 10
       element.appendChild(para);
11 11
       document.write('</div>');
12 12
     }
13
-
14
-    // Create a banner if we're not the latest version
15
-    current_url = window.location.href;
16
-    if ((current_url.search("latest") > -1) || (current_url.search("/{{ latest_version }}/") > -1)) {
17
-     // no banner for latest release
18
-    } else if (current_url.search("devel") > -1) {
19
-      document.write('<div id="banner_id" class="admonition caution">');
20
-      para = document.createElement('p');
21
-      banner_text=document.createTextNode("You are reading the *devel* version of the Ansible documentation - most module documentation is currently missing as the modules have moved to collections. Until docs catches up to this change, use the version selection to the left if you want module documentation or the latest stable release version. The *devel* version is not guaranteed stable.");
22
-      para.appendChild(banner_text);
23
-      element = document.getElementById('banner_id');
24
-      element.appendChild(para);
25
-      document.write('</div>');
26
-    } else {
27
-      document.write('<div id="banner_id" class="admonition caution">');
28
-      para = document.createElement('p');
29
-      banner_text=document.createTextNode("You are reading an older version of the Ansible documentation. Use the version selection to the left if you want the latest stable released version.");
30
-      para.appendChild(banner_text);
31
-      element = document.getElementById('banner_id');
32
-      element.appendChild(para);
33
-      document.write('</div>');
34
-    }
13
+    {% if (not READTHEDOCS) and (available_versions is defined) %}
14
+      // Create a banner if we're not the latest version
15
+      current_url = window.location.href;
16
+      if ((current_url.search("latest") > -1) || (current_url.search("/{{ latest_version }}/") > -1)) {
17
+       // no banner for latest release
18
+      } else if (current_url.search("devel") > -1) {
19
+        document.write('<div id="banner_id" class="admonition caution">');
20
+        para = document.createElement('p');
21
+        banner_text=document.createTextNode("You are reading the *devel* version of the Ansible documentation - this version is not guaranteed stable. Use the version selection to the left if you want the latest stable released version.");
22
+        para.appendChild(banner_text);
23
+        element = document.getElementById('banner_id');
24
+        element.appendChild(para);
25
+        document.write('</div>');
26
+      } else {
27
+        document.write('<div id="banner_id" class="admonition caution">');
28
+        para = document.createElement('p');
29
+        banner_text=document.createTextNode("You are reading an older version of the Ansible documentation. Use the version selection to the left if you want the latest stable released version.");
30
+        para.appendChild(banner_text);
31
+        element = document.getElementById('banner_id');
32
+        element.appendChild(para);
33
+        document.write('</div>');
34
+      }
35
+    {% endif %}
35 36
   </script>
... ...
@@ -1,7 +1,7 @@
1 1
 <!--- Based on https://github.com/rtfd/sphinx_rtd_theme/pull/438/files -->
2 2
 {# Creates dropdown version selection in the top-left navigation. #}
3 3
 <div class="version">
4
-  {% if not READTHEDOCS %}
4
+  {% if (not READTHEDOCS) and (available_versions is defined) %}
5 5
     <div class="version-dropdown">
6 6
       <select class="version-list" id="version-list" onchange="javascript:location.href = this.value;">
7 7
         <script> x = document.getElementById("version-list"); </script>
8 8
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+# We also need an example of modules hosted in Automation Hub
1
+# We'll likely move to data hosted in botmeta instead of a standalone file but
2
+# we'll need all of these same details.
3
+module:
4
+    purefa_user:
5
+        source: 'https://galaxy.ansible.com/'
6
+        fqcn: 'purestorage.flasharray'
7
+    purefa_vg:
8
+        source: 'https://galaxy.ansible.com/'
9
+        fqcn: 'purestorage.flasharray'
10
+    gcp_compute_firewall_info:
11
+        source: 'https://galaxy.ansible.com/'
12
+        fqcn: 'google.cloud'
13
+module_utils:
14
+    purefa:
15
+        source: 'https://galaxy.ansible.com/'
16
+        fqcn: 'purestorage.flasharray'
... ...
@@ -6,3 +6,4 @@ sphinx==2.1.2
6 6
 sphinx-notfound-page
7 7
 Pygments >= 2.4.0
8 8
 straight.plugin # Needed for hacking/build-ansible.py which is the backend build script
9
+antsibull >= 0.15.0
... ...
@@ -277,6 +277,10 @@ autoclass_content = 'both'
277 277
 intersphinx_mapping = {'python': ('https://docs.python.org/2/', (None, '../python2.inv')),
278 278
                        'python3': ('https://docs.python.org/3/', (None, '../python3.inv')),
279 279
                        'jinja2': ('http://jinja.palletsprojects.com/', (None, '../jinja2.inv')),
280
+                       'collections': ('https://docs.ansible.com/collections/',
281
+                                       (None, '../collections.inv',
282
+                                        'http://docs.testing.ansible.com/collections/objects.inv',
283
+                                        '../_collections_build/html/objects.inv')),
280 284
                        'ansible_2_9': ('https://docs.ansible.com/ansible/2.9/', (None, '../ansible_2_9.inv')),
281 285
                        'ansible_2_8': ('https://docs.ansible.com/ansible/2.8/', (None, '../ansible_2_8.inv')),
282 286
                        'ansible_2_7': ('https://docs.ansible.com/ansible/2.7/', (None, '../ansible_2_7.inv')),
... ...
@@ -74,7 +74,7 @@ Ansible releases a new major release of Ansible approximately three to four time
74 74
    :maxdepth: 1
75 75
    :caption: Reference & Appendices
76 76
 
77
-   ../modules/modules_by_category
77
+   collections/index
78 78
    reference_appendices/playbooks_keywords
79 79
    reference_appendices/common_return_values
80 80
    reference_appendices/config
... ...
@@ -10,7 +10,7 @@ Ansible Network modules extend the benefits of simple, powerful, agentless autom
10 10
 
11 11
 If you're new to Ansible, or new to using Ansible for network management, start with :ref:`network_getting_started`. If you are already familiar with network automation with Ansible, see :ref:`network_advanced`.
12 12
 
13
-For documentation on using a particular network module, consult the :ref:`list of all network modules<network_modules>`. Some network modules are maintained by the Ansible community - here's a list of :ref:`network modules maintained by the Ansible Network Team<network_supported>`.
13
+For documentation on using a particular network module, consult the :ref:`list of all network modules<network_modules>`. Network modules for various hardware are supported by different teams including the hardware vendors themselves, volunteers from the Ansible community, and the Ansible Network Team.
14 14
 
15 15
 .. toctree::
16 16
    :maxdepth: 3
... ...
@@ -483,4 +483,4 @@ If you receive an connection error please double check the inventory and playboo
483 483
 
484 484
   * :ref:`network_guide`
485 485
   * :ref:`intro_inventory`
486
-  * :ref:`Vault best practices <best_practices_for_variables_and_vaults>`
486
+  * :ref:`Keeping vaulted variables visible <tip_for_variables_and_vaults>`
... ...
@@ -47,11 +47,6 @@ Plugin List
47 47
 You can use ``ansible-doc -t become -l`` to see the list of available plugins.
48 48
 Use ``ansible-doc -t become <plugin name>`` to see specific documentation and examples.
49 49
 
50
-.. toctree:: :maxdepth: 1
51
-    :glob:
52
-
53
-    become/*
54
-
55 50
 .. seealso::
56 51
 
57 52
    :ref:`about_playbooks`
... ...
@@ -118,11 +118,6 @@ Plugin List
118 118
 You can use ``ansible-doc -t cache -l`` to see the list of available plugins.
119 119
 Use ``ansible-doc -t cache <plugin name>`` to see specific documentation and examples.
120 120
 
121
-.. toctree:: :maxdepth: 1
122
-    :glob:
123
-
124
-    cache/*
125
-
126 121
 .. seealso::
127 122
 
128 123
    :ref:`action_plugins`
... ...
@@ -79,12 +79,6 @@ Plugin list
79 79
 You can use ``ansible-doc -t callback -l`` to see the list of available plugins.
80 80
 Use ``ansible-doc -t callback <plugin name>`` to see specific documents and examples.
81 81
 
82
-.. toctree:: :maxdepth: 1
83
-    :glob:
84
-
85
-    callback/*
86
-
87
-
88 82
 .. seealso::
89 83
 
90 84
    :ref:`action_plugins`
... ...
@@ -58,12 +58,6 @@ You can use ``ansible-doc -t connection -l`` to see the list of available plugin
58 58
 Use ``ansible-doc -t connection <plugin name>`` to see detailed documentation and examples.
59 59
 
60 60
 
61
-.. toctree:: :maxdepth: 1
62
-    :glob:
63
-
64
-    connection/*
65
-
66
-
67 61
 .. seealso::
68 62
 
69 63
    :ref:`Working with Playbooks<working_with_playbooks>`
... ...
@@ -162,11 +162,6 @@ Plugin List
162 162
 You can use ``ansible-doc -t inventory -l`` to see the list of available plugins.
163 163
 Use ``ansible-doc -t inventory <plugin name>`` to see plugin-specific documentation and examples.
164 164
 
165
-.. toctree:: :maxdepth: 1
166
-    :glob:
167
-
168
-    inventory/*
169
-
170 165
 .. seealso::
171 166
 
172 167
    :ref:`about_playbooks`
... ...
@@ -138,11 +138,6 @@ Plugin list
138 138
 You can use ``ansible-doc -t lookup -l`` to see the list of available plugins. Use ``ansible-doc -t lookup <plugin name>`` to see specific documents and examples.
139 139
 
140 140
 
141
-.. toctree:: :maxdepth: 1
142
-    :glob:
143
-
144
-    lookup/*
145
-
146 141
 .. seealso::
147 142
 
148 143
    :ref:`about_playbooks`
... ...
@@ -33,11 +33,6 @@ In this case, you will also want to update the :ref:`ansible_shell_executable <a
33 33
 You can further control the settings for each plugin via other configuration options
34 34
 detailed in the plugin themselves (linked below).
35 35
 
36
-.. toctree:: :maxdepth: 1
37
-    :glob:
38
-
39
-    shell/*
40
-
41 36
 .. seealso::
42 37
 
43 38
    :ref:`about_playbooks`
... ...
@@ -59,11 +59,6 @@ You can use ``ansible-doc -t strategy -l`` to see the list of available plugins.
59 59
 Use ``ansible-doc -t strategy <plugin name>`` to see plugin-specific specific documentation and examples.
60 60
 
61 61
 
62
-.. toctree:: :maxdepth: 1
63
-    :glob:
64
-
65
-    strategy/*
66
-
67 62
 .. seealso::
68 63
 
69 64
    :ref:`about_playbooks`
... ...
@@ -57,11 +57,6 @@ You can use ``ansible-doc -t vars -l`` to see the list of available plugins.
57 57
 Use ``ansible-doc -t vars <plugin name>`` to see specific plugin-specific documentation and examples.
58 58
 
59 59
 
60
-.. toctree:: :maxdepth: 1
61
-    :glob:
62
-
63
-    vars/*
64
-
65 60
 .. seealso::
66 61
 
67 62
    :ref:`action_plugins`
... ...
@@ -698,7 +698,7 @@ Please see the section below for a link to IRC and the Google Group, where you c
698 698
    :ref:`working_with_playbooks`
699 699
        An introduction to playbooks
700 700
    :ref:`playbooks_best_practices`
701
-       Best practices advice
701
+       Tips and tricks for playbooks
702 702
    `User Mailing List <https://groups.google.com/group/ansible-project>`_
703 703
        Have a question?  Stop by the google group!
704 704
    `irc.freenode.net <http://irc.freenode.net>`_
... ...
@@ -494,7 +494,7 @@ when a term comes up on the mailing list.
494 494
    :ref:`working_with_playbooks`
495 495
        An introduction to playbooks
496 496
    :ref:`playbooks_best_practices`
497
-       Best practices advice
497
+       Tips and tricks for playbooks
498 498
    `User Mailing List <https://groups.google.com/group/ansible-devel>`_
499 499
        Have a question?  Stop by the google group!
500 500
    `irc.freenode.net <http://irc.freenode.net>`_
... ...
@@ -234,7 +234,7 @@ For more information, see `this systemd issue
234 234
 Become and network automation
235 235
 =============================
236 236
 
237
-As of version 2.6, Ansible supports ``become`` for privilege escalation (entering ``enable`` mode or privileged EXEC mode) on all :ref:`Ansible-maintained platforms<network_supported>` that support ``enable`` mode. Using ``become`` replaces the ``authorize`` and ``auth_pass`` options in a ``provider`` dictionary.
237
+As of version 2.6, Ansible supports ``become`` for privilege escalation (entering ``enable`` mode or privileged EXEC mode) on all Ansible-maintained network platforms that support ``enable`` mode. Using ``become`` replaces the ``authorize`` and ``auth_pass`` options in a ``provider`` dictionary.
238 238
 
239 239
 You must set the connection type to either ``connection: network_cli`` or ``connection: httpapi`` to use ``become`` for privilege escalation on network devices. Check the :ref:`platform_options` and :ref:`network_modules` documentation for details.
240 240
 
... ...
@@ -5,7 +5,8 @@
5 5
 Using collections
6 6
 *****************
7 7
 
8
-Collections are a distribution format for Ansible content that can include playbooks, roles, modules, and plugins.
8
+Collections are a distribution format for Ansible content that can include playbooks, roles, modules, and plugins. As modules move from the core Ansible repository into collections, the module documentation will move to the `collections documentation page <https://docs.ansible.com/collections/>`_
9
+
9 10
 You can install and use collections through `Ansible Galaxy <https://galaxy.ansible.com>`_.
10 11
 
11 12
 * For details on how to *develop* collections see :ref:`developing_collections`.
... ...
@@ -543,7 +543,7 @@ ansible_port
543 543
 ansible_user
544 544
     The user name to use when connecting to the host
545 545
 ansible_password
546
-    The password to use to authenticate to the host (never store this variable in plain text; always use a vault. See :ref:`best_practices_for_variables_and_vaults`)
546
+    The password to use to authenticate to the host (never store this variable in plain text; always use a vault. See :ref:`tip_for_variables_and_vaults`)
547 547
 
548 548
 
549 549
 Specific to the SSH connection:
... ...
@@ -575,7 +575,7 @@ ansible_become_method
575 575
 ansible_become_user
576 576
     Equivalent to ``ansible_sudo_user`` or ``ansible_su_user``, allows to set the user you become through privilege escalation
577 577
 ansible_become_password
578
-    Equivalent to ``ansible_sudo_password`` or ``ansible_su_password``, allows you to set the privilege escalation password (never store this variable in plain text; always use a vault. See :ref:`best_practices_for_variables_and_vaults`)
578
+    Equivalent to ``ansible_sudo_password`` or ``ansible_su_password``, allows you to set the privilege escalation password (never store this variable in plain text; always use a vault. See :ref:`tip_for_variables_and_vaults`)
579 579
 ansible_become_exe
580 580
     Equivalent to ``ansible_sudo_exe`` or ``ansible_su_exe``, allows you to set the executable for the escalation method selected
581 581
 ansible_become_flags
... ...
@@ -7,9 +7,8 @@ Working With Modules
7 7
    :maxdepth: 1
8 8
 
9 9
    modules_intro
10
-   ../reference_appendices/common_return_values
11 10
    modules_support
12
-   ../modules/modules_by_category
11
+   ../reference_appendices/common_return_values
13 12
 
14 13
 
15 14
 Ansible ships with a number of modules (called the 'module library')
... ...
@@ -4,68 +4,66 @@
4 4
 Module Maintenance & Support
5 5
 ****************************
6 6
 
7
+If you are using a module and you discover a bug, you may want to know where to report that bug, who is responsible for fixing it, and how you can track changes to the module. If you are a Red Hat subscriber, you may want to know whether you can get support for the issue you are facing.
8
+
9
+Starting in Ansible 2.10, most modules live in collections. The distribution method for each collection reflects the maintenance and support for the modules in that collection.
10
+
7 11
 .. contents::
8
-  :depth: 2
9 12
   :local:
10 13
 
11 14
 Maintenance
12 15
 ===========
13 16
 
14
-To clarify who maintains each included module, adding features and fixing bugs, each included module now has associated metadata that provides information about maintenance.
15
-
16
-Core
17
-
18
-:ref:`Core Maintained<core_supported>` modules are maintained by the Ansible Engineering Team.
19
-These modules are integral to the basic foundations of the Ansible distribution.
17
+.. table::
18
+   :class: documentation-table
20 19
 
21
-Network
20
+   ============================= ========================================== ==========================
21
+   Collection                    Code location                              Maintained by
22
+   ============================= ========================================== ==========================
23
+   ansible.builtin               `ansible/ansible repo`_ on GitHub          core team
22 24
 
23
-:ref:`Network Maintained<network_supported>` modules are are maintained by the Ansible Network Team. Please note there are additional networking modules that are categorized as Certified or Community not maintained by Ansible.
25
+   distributed on Galaxy         various; follow ``repo`` link              community or partners
24 26
 
27
+   distributed on Automation Hub various; follow ``repo`` link              content team or partners
28
+   ============================= ========================================== ==========================
25 29
 
26
-Certified
30
+.. _ansible/ansible repo: https://github.com/ansible/ansible/tree/devel/lib/ansible/modules
27 31
 
28
-`Certified <https://access.redhat.com/articles/3642632>`_ modules are maintained by Ansible Partners.
32
+Issue Reporting
33
+===============
29 34
 
30
-Community
35
+If you find a bug that affects a plugin in the main Ansible repo:
31 36
 
32
-:ref:`Community Maintained<community_supported>` modules are submitted and maintained by the Ansible community.  These modules are not maintained by Ansible, and are included as a convenience.
37
+  #. Confirm that you are running the latest stable version of Ansible or the devel branch.
38
+  #. Look at the `issue tracker in the Ansible repo <https://github.com/ansible/ansible/issues>`_ to see if an issue has already been filed.
39
+  #. Create an issue if one does not already exist. Include as much detail as you can about the behavior you discovered.
33 40
 
34
-Issue Reporting
35
-===============
41
+If you find a bug that affects a plugin in a Galaxy collection:
36 42
 
37
-If you believe you have found a bug in a module and are already running the latest stable or development version of Ansible, first look at the `issue tracker in the Ansible repo <https://github.com/ansible/ansible/issues>`_ to see if an issue has already been filed. If not, please file one.
43
+  #. Find the collection on Galaxy.
44
+  #. Find the issue tracker for the collection.
45
+  #. Look there to see if an issue has already been filed.
46
+  #. Create an issue if one does not already exist. Include as much detail as you can about the behavior you discovered.
38 47
 
39
-Should you have a question rather than a bug report, inquiries are welcome on the `ansible-project Google group <https://groups.google.com/forum/#%21forum/ansible-project>`_ or on Ansible's "#ansible" channel, located on irc.freenode.net.
48
+Some partner collections may be hosted in private repositories.
40 49
 
41
-For development-oriented topics, use the `ansible-devel Google group <https://groups.google.com/forum/#%21forum/ansible-devel>`_ or Ansible's #ansible and #ansible-devel channels, located on irc.freenode.net. You should also read the :ref:`Community Guide <ansible_community_guide>`, :ref:`Testing Ansible <developing_testing>`, and the :ref:`Developer Guide <developer_guide>`.
50
+If you are not sure whether the behavior you see is a bug, if you have questions, if you want to discuss development-oriented topics, or if you just want to get in touch, use one of our Google groups or IRC channels to  :ref:`communicate with Ansiblers <communication>`.
42 51
 
43
-The modules are hosted on GitHub in a subdirectory of the `Ansible <https://github.com/ansible/ansible/tree/devel/lib/ansible/modules>`_ repo.
52
+If you find a bug that affects a module in an Automation Hub collection:
44 53
 
45
-NOTE: If you have a Red Hat Ansible Automation product subscription, please follow the standard issue reporting process via the `Red Hat Customer Portal <https://access.redhat.com/>`_.
54
+  #. If the collection offers an Issue Tracker link on Automation Hub, click there and open an issue on the collection repository. If it does not, follow the standard process for reporting issues on the `Red Hat Customer Portal <https://access.redhat.com/>`_. You must have a subscription to the Red Hat Ansible Automation Platform to create an issue on the portal.
46 55
 
47 56
 Support
48 57
 =======
49 58
 
50
-For more information on how included Ansible modules are supported by Red Hat,
51
-please refer to the following `knowledge base article <https://access.redhat.com/articles/3166901>`_ as well as other resources on the `Red Hat Customer Portal. <https://access.redhat.com/>`_
59
+All plugins that remain in ansible-base and all collections hosted in Automation Hub are supported by Red Hat. No other plugins or collections are supported by Red Hat. If you have a subscription to the Red Hat Ansible Automation Platform, you can find more information and resources on the `Red Hat Customer Portal. <https://access.redhat.com/>`_
52 60
 
53 61
 .. seealso::
54 62
 
55
-   :ref:`Module index<modules_by_category>`
56
-       A complete list of all available modules.
57 63
    :ref:`intro_adhoc`
58 64
        Examples of using modules in /usr/bin/ansible
59 65
    :ref:`working_with_playbooks`
60 66
        Examples of using modules with /usr/bin/ansible-playbook
61
-   :ref:`developing_modules`
62
-       How to write your own modules
63
-   `List of Ansible Certified Modules <https://access.redhat.com/articles/3642632>`_
64
-       High level list of Ansible certified modules from Partners
65 67
    `Mailing List <https://groups.google.com/group/ansible-project>`_
66 68
        Questions? Help? Ideas?  Stop by the list on Google Groups
67 69
    `irc.freenode.net <http://irc.freenode.net>`_
... ...
@@ -4,20 +4,21 @@
4 4
 Search paths in Ansible
5 5
 ***********************
6 6
 
7
-Absolute paths are not an issue as they always have a known start, but relative paths ... well, they are relative.
7
+You can control the paths Ansible searches to find resources on your control node (including configuration, modules, roles, ssh keys, and more) as well as resources on the remote nodes you are managing. Use absolute paths to tell Ansible where to find resources whenever you can. However, absolute paths are not always practical. This page covers how Ansible interprets relative search paths, along with ways to troubleshoot when Ansible cannot find the resource you need.
8
+
9
+.. contents::
10
+   :local:
8 11
 
9 12
 Config paths
10 13
 ============
11 14
 
12
-By default these should be relative to the config file, some are specifically relative to the 'cwd' or the playbook and should have this noted in their description. Things like ssh keys are left to use 'cwd' because it mirrors how the underlying tools would use it.
15
+By default these should be relative to the config file, some are specifically relative to the current working directory or the playbook and should have this noted in their description. Things like ssh keys are left to use the current working directory because it mirrors how the underlying tools would use it.
13 16
 
14 17
 
15 18
 Task paths
16 19
 ==========
17 20
 
18
-Here things start getting complicated, there are 2 different scopes to consider, task evaluation (paths are all local, like in lookups) and task execution, which is normally on the remote, unless an action plugin is involved.
19
-
20
-Some tasks that require 'local' resources use action plugins (template and copy are examples of these), in which case the path is also local.
21
+Task paths include two different scopes: task evaluation and task execution. For task evaluation, all paths are local, like in lookups. For task execution, which usually happens on the remote nodes, local paths do not usually apply. However, if a task uses an action plugin, it uses a local path. The template and copy modules are examples of modules that use action plugins, and therefore use local paths.
21 22
 
22 23
 The magic of 'local' paths
23 24
 --------------------------
... ...
@@ -32,12 +33,10 @@ i.e ::
32 32
     play search path is playdir/{files|vars|templates}/, playdir/.
33 33
 
34 34
 
35
-The current working directory (cwd) is not searched. If you see it, it just happens to coincide with one of the paths above.
36
-If you `include` a task file from a role, it  will NOT trigger role behavior, this only happens when running as a role, `include_role` will work.
37
-A new variable `ansible_search_path` var will have the search path used, in order (but without the appended subdirs). Using 5 "v"s (`-vvvvv`) should show the detail of the search as it happens.
35
+By default, Ansible does not search the current working directory unless it happens to coincide with one of the paths above. If you `include` a task file from a role, it  will NOT trigger role behavior, this only happens when running as a role, `include_role` will work. A new variable `ansible_search_path` var will have the search path used, in order (but without the appended subdirs). Using 5 "v"s (`-vvvvv`) should show the detail of the search as it happens.
38 36
 
39 37
 As for includes, they try the path of the included file first and fall back to the play/role that includes them.
40 38
 
41 39
 
42 40
 
43
-.. note:  The 'cwd' might vary depending on the connection plugin and if the action is local or remote. For the remote it is normally the directory on which the login shell puts the user. For local it is either the directory you executed ansible from or in some cases the playbook directory.
41
+.. note:  The current working directory might vary depending on the connection plugin and if the action is local or remote. For the remote it is normally the directory on which the login shell puts the user. For local it is either the directory you executed ansible from or in some cases the playbook directory.
... ...
@@ -70,7 +70,7 @@ Separate production and staging inventory
70 70
 
71 71
 You can keep your production environment separate from development, test, and staging environments by using separate inventory files or directories for each environment. This way you pick with -i what you are targeting. Keeping all your environments in one file can lead to surprises!
72 72
 
73
-.. _best_practices_for_variables_and_vaults:
73
+.. _tip_for_variables_and_vaults:
74 74
 
75 75
 Keep vaulted variables safely visible
76 76
 -------------------------------------
... ...
@@ -464,7 +464,7 @@ Possible values (sample, not complete list)::
464 464
    :ref:`playbooks_reuse_roles`
465 465
        Playbook organization by roles
466 466
    :ref:`playbooks_best_practices`
467
-       Best practices in playbooks
467
+       Tips and tricks for playbooks
468 468
    :ref:`playbooks_variables`
469 469
        All about variables
470 470
    `User Mailing List <https://groups.google.com/group/ansible-devel>`_
... ...
@@ -1,9 +1,9 @@
1 1
 .. _playbooks_delegation:
2 2
 
3
-Delegation and local actions
4
-============================
3
+Controlling where tasks run: delegation and local actions
4
+=========================================================
5 5
 
6
-By default Ansible executes all tasks on the machines that match the ``hosts`` line of your playbook. If you want to run some tasks on a different machine, you can use delegation. For example, when updating webservers, you might want to retrieve information from your database servers. In this scenario, your play would target the webservers group and you would delegate the database tasks to your dbservers group. With delegation, you can perform a task on one host on behalf of another, or execute tasks locally on behalf of remote hosts.
6
+By default Ansible gathers facts and executes all tasks on the machines that match the ``hosts`` line of your playbook. This page shows you how to delegate tasks to a different machine or group, delegate facts to specific machines or groups, or run an entire playbook locally. Using these approaches, you can manage inter-related environments precisely and efficiently. For example, when updating your webservers, you might need to remove them from a load-balanced pool temporarily. You cannot perform this task on the webservers themselves. By delegating the task to localhost, you keep all the tasks within the same play.
7 7
 
8 8
 .. contents::
9 9
    :local:
... ...
@@ -99,52 +99,10 @@ Delegating Ansible tasks is like delegating tasks in the real world - your groce
99 99
 
100 100
 This task gathers facts for the machines in the dbservers group and assigns the facts to those machines, even though the play targets the app_servers group. This way you can lookup `hostvars['dbhost1']['ansible_default_ipv4']['address']` even though dbservers were not part of the play, or left out by using `--limit`.
101 101
 
102
-.. _run_once:
103
-
104
-Run once
105
-
106
-If you want a task to run only on the first host in your batch of hosts, set ``run_once`` to true on that task::
107
-
108
-    ---
109
-    # ...
110
-
111
-      tasks:
112
-
113
-        # ...
114
-
115
-        - command: /opt/application/upgrade_db.py
116
-          run_once: true
117
-
118
-        # ...
119
-
120
-Ansible executes this task on the first host in the current batch and applies all results and facts to all the hosts in the same batch. This approach is similar to applying a conditional to a task such as::
121
-
122
-        - command: /opt/application/upgrade_db.py
123
-          when: inventory_hostname == webservers[0]
124
-
125
-However, with ``run_once``, the results are applied to all the hosts. To specify an individual host to execute on, delegate the task::
126
-
127
-        - command: /opt/application/upgrade_db.py
128
-          run_once: true
129
-          delegate_to: web01.example.org
130
-
131
-As always with delegation, the action will be executed on the delegated host, but the information is still that of the original host in the task.
132
-
133
-.. note::
134
-     When used together with "serial", tasks marked as "run_once" will be run on one host in *each* serial batch. If the task must run only once regardless of "serial" mode, use
135
-     :code:`when: inventory_hostname == ansible_play_hosts_all[0]` construct.
136
-
137
-.. note::
138
-    Any conditional (i.e `when:`) will use the variables of the 'first host' to decide if the task runs or not, no other hosts will be tested.
139
-
140
-.. note::
141
-    If you want to avoid the default behavior of setting the fact for all hosts, set `delegate_facts: True` for the specific task or block.
142
-
143 102
 .. _local_playbooks:
144 103
 
145 104
 Local playbooks
146
-```````````````
105
+---------------
147 106
 
148 107
 It may be useful to use a playbook locally on a remote host, rather than by connecting over SSH.  This can be useful for assuring the configuration of a system by putting a playbook in a crontab.  This may also be used
149 108
 to run a playbook inside an OS installer, such as an Anaconda kickstart.
... ...
@@ -165,11 +123,12 @@ use the default remote connection type::
165 165
     under {{ ansible_playbook_python }}. Be sure to set ansible_python_interpreter: "{{ ansible_playbook_python }}" in
166 166
     host_vars/localhost.yml, for example. You can avoid this issue by using ``local_action`` or ``delegate_to: localhost`` instead.
167 167
 
168
-
169 168
 .. seealso::
170 169
 
171 170
    :ref:`playbooks_intro`
172 171
        An introduction to playbooks
172
+   :ref:`playbooks_strategies`
173
+       More ways to control how and where Ansible executes
173 174
    `Ansible Examples on GitHub <https://github.com/ansible/ansible-examples>`_
174 175
        Many examples of full-stack deployments
175 176
    `User Mailing List <https://groups.google.com/group/ansible-devel>`_
... ...
@@ -231,7 +231,7 @@ You can also use blocks to define responses to task errors. This approach is sim
231 231
    :ref:`playbooks_intro`
232 232
        An introduction to playbooks
233 233
    :ref:`playbooks_best_practices`
234
-       Best practices in playbooks
234
+       Tips and tricks for playbooks
235 235
    :ref:`playbooks_conditionals`
236 236
        Conditional statements in playbooks
237 237
    :ref:`playbooks_variables`
... ...
@@ -1644,7 +1644,7 @@ This can then be used to reference hashes in Pod specifications::
1644 1644
    :ref:`playbooks_reuse_roles`
1645 1645
        Playbook organization by roles
1646 1646
    :ref:`playbooks_best_practices`
1647
-       Best practices in playbooks
1647
+       Tips and tricks for playbooks
1648 1648
    `User Mailing List <https://groups.google.com/group/ansible-devel>`_
1649 1649
        Have a question?  Stop by the google group!
1650 1650
    `irc.freenode.net <http://irc.freenode.net>`_
... ...
@@ -737,7 +737,7 @@ the filter ``slaac()`` generates an IPv6 address for a given network and a MAC A
737 737
    :ref:`playbooks_reuse_roles`
738 738
        Playbook organization by roles
739 739
    :ref:`playbooks_best_practices`
740
-       Best practices in playbooks
740
+    	 Tips and tricks for playbooks
741 741
    `User Mailing List <https://groups.google.com/group/ansible-devel>`_
742 742
        Have a question?  Stop by the google group!
743 743
    `irc.freenode.net <http://irc.freenode.net>`_
... ...
@@ -430,7 +430,7 @@ Migrating from with_X to loop
430 430
    :ref:`playbooks_reuse_roles`
431 431
        Playbook organization by roles
432 432
    :ref:`playbooks_best_practices`
433
-       Best practices in playbooks
433
+       Tips and tricks for playbooks
434 434
    :ref:`playbooks_conditionals`
435 435
        Conditional statements in playbooks
436 436
    :ref:`playbooks_variables`
... ...
@@ -188,7 +188,7 @@ Imports are processed before the play begins, so the name of the import no longe
188 188
    :ref:`playbooks_loops`
189 189
        Loops in playbooks
190 190
    :ref:`playbooks_best_practices`
191
-       Various tips about managing playbooks in the real world
191
+       Tips and tricks for playbooks
192 192
    :ref:`ansible_galaxy`
193 193
        How to share roles on galaxy, role management
194 194
    `GitHub Ansible examples <https://github.com/ansible/ansible-examples>`_
... ...
@@ -15,7 +15,7 @@ The content on this page has been moved to :ref:`playbooks_reuse`.
15 15
    :ref:`working_with_playbooks`
16 16
        Review the basic Playbook language features
17 17
    :ref:`playbooks_best_practices`
18
-       Various tips about managing playbooks in the real world
18
+       Tips and tricks for playbooks
19 19
    :ref:`playbooks_variables`
20 20
        All about variables in playbooks
21 21
    :ref:`playbooks_conditionals`
... ...
@@ -441,7 +441,7 @@ Read the `Ansible Galaxy documentation <https://galaxy.ansible.com/docs/>`_ page
441 441
    :ref:`working_with_playbooks`
442 442
        Review the basic Playbook language features
443 443
    :ref:`playbooks_best_practices`
444
-       Tips for managing playbooks in the real world
444
+       Tips and tricks for playbooks
445 445
    :ref:`playbooks_variables`
446 446
        Variables in playbooks
447 447
    :ref:`playbooks_conditionals`
... ...
@@ -19,7 +19,7 @@ As you write more playbooks and roles, you might have some special use cases. Fo
19 19
    ../plugins/plugins
20 20
    playbooks_prompts
21 21
    playbooks_tags
22
-   playbooks_vault
22
+   vault
23 23
    playbooks_startnstep
24 24
    ../reference_appendices/playbooks_keywords
25 25
    playbooks_lookups
... ...
@@ -36,7 +36,7 @@ or pass it on the command line: `ansible-playbook -f 30 my_playbook.yml`.
36 36
 Using keywords to control execution
37 37
 -----------------------------------
38 38
 
39
-In addition to strategies, several :ref:`keywords<playbook_keywords>` also affect play execution. You can set a number, a percentage, or a list of numbers of hosts you want to manage at a time with ``serial``. Ansible completes the play on the specified number or percentage of hosts before starting the next batch of hosts. You can restrict the number of workers allotted to a block or task with ``throttle``. You can control how Ansible selects the next host in a group to execute against with ``order``. These keywords are not strategies. They are directives or options applied to a play, block, or task.
39
+In addition to strategies, several :ref:`keywords<playbook_keywords>` also affect play execution. You can set a number, a percentage, or a list of numbers of hosts you want to manage at a time with ``serial``. Ansible completes the play on the specified number or percentage of hosts before starting the next batch of hosts. You can restrict the number of workers allotted to a block or task with ``throttle``. You can control how Ansible selects the next host in a group to execute against with ``order``. You can run a task on a single host with ``run_once``. These keywords are not strategies. They are directives or options applied to a play, block, or task.
40 40
 
41 41
 .. _rolling_update_batch_size:
42 42
 
... ...
@@ -160,10 +160,54 @@ shuffle:
160 160
 
161 161
 Other keywords that affect play execution include ``ignore_errors``, ``ignore_unreachable``, and ``any_errors_fatal``. These options are documented in :ref:`playbooks_error_handling`.
162 162
 
163
+.. _run_once:
164
+
165
+Running on a single machine with ``run_once``
166
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
167
+
168
+If you want a task to run only on the first host in your batch of hosts, set ``run_once`` to true on that task::
169
+
170
+    ---
171
+    # ...
172
+
173
+      tasks:
174
+
175
+        # ...
176
+
177
+        - command: /opt/application/upgrade_db.py
178
+          run_once: true
179
+
180
+        # ...
181
+
182
+Ansible executes this task on the first host in the current batch and applies all results and facts to all the hosts in the same batch. This approach is similar to applying a conditional to a task such as::
183
+
184
+        - command: /opt/application/upgrade_db.py
185
+          when: inventory_hostname == webservers[0]
186
+
187
+However, with ``run_once``, the results are applied to all the hosts. To run the task on a specific host, instead of the first host in the batch, delegate the task::
188
+
189
+        - command: /opt/application/upgrade_db.py
190
+          run_once: true
191
+          delegate_to: web01.example.org
192
+
193
+As always with :ref:`delegation <playbooks_delegation>`, the action will be executed on the delegated host, but the information is still that of the original host in the task.
194
+
195
+.. note::
196
+     When used together with ``serial``, tasks marked as ``run_once`` will be run on one host in *each* serial batch. If the task must run only once regardless of ``serial`` mode, use
197
+     :code:`when: inventory_hostname == ansible_play_hosts_all[0]` construct.
198
+
199
+.. note::
200
+    Any conditional (i.e `when:`) will use the variables of the 'first host' to decide if the task runs or not, no other hosts will be tested.
201
+
202
+.. note::
203
+    If you want to avoid the default behavior of setting the fact for all hosts, set ``delegate_facts: True`` for the specific task or block.
204
+
163 205
 .. seealso::
164 206
 
165 207
    :ref:`about_playbooks`
166 208
        An introduction to playbooks
209
+   :ref:`playbooks_delegation`
210
+       Running tasks on or assigning facts to specific machines
167 211
    :ref:`playbooks_reuse_roles`
168 212
        Playbook organization by roles
169 213
    `User Mailing List <https://groups.google.com/group/ansible-devel>`_
... ...
@@ -48,7 +48,7 @@ fmt
48 48
    :ref:`playbooks_reuse_roles`
49 49
        Playbook organization by roles
50 50
    :ref:`playbooks_best_practices`
51
-       Best practices in playbooks
51
+       Tips and tricks for playbooks
52 52
    `User Mailing List <https://groups.google.com/group/ansible-devel>`_
53 53
        Have a question?  Stop by the google group!
54 54
    `irc.freenode.net <http://irc.freenode.net>`_
... ...
@@ -386,7 +386,7 @@ The following tasks are illustrative of the tests meant to check the status of t
386 386
    :ref:`playbooks_reuse_roles`
387 387
        Playbook organization by roles
388 388
    :ref:`playbooks_best_practices`
389
-       Best practices in playbooks
389
+       Tips and tricks for playbooks
390 390
    `User Mailing List <https://groups.google.com/group/ansible-devel>`_
391 391
        Have a question?  Stop by the google group!
392 392
    `irc.freenode.net <http://irc.freenode.net>`_
... ...
@@ -437,7 +437,7 @@ For information about advanced YAML syntax used to declare variables and have mo
437 437
    :ref:`playbooks_reuse_roles`
438 438
        Playbook organization by roles
439 439
    :ref:`playbooks_best_practices`
440
-       Best practices in playbooks
440
+       Tips and tricks for playbooks
441 441
    :ref:`special_variables`
442 442
        List of special variables
443 443
    `User Mailing List <https://groups.google.com/group/ansible-devel>`_
... ...
@@ -1,157 +1,6 @@
1
-.. _playbooks_vault:
1
+:orphan:
2 2
 
3
-Using Vault in playbooks
3
+Using vault in playbooks
4 4
 ========================
5 5
 
6
-.. contents:: Topics
7
-
8
-The "Vault" is a feature of Ansible that allows you to keep sensitive data such as passwords or keys protected at rest, rather than as plaintext in playbooks or roles. These vaults can then be distributed or placed in source control.
9
-
10
-There are 2 types of vaulted content and each has their own uses and limitations:
11
-
12
-:Vaulted files:
13
-    * The full file is encrypted in the vault, this can contain Ansible variables or any other type of content.
14
-    * It will always be decrypted when loaded or referenced, Ansible cannot know if it needs the content unless it decrypts it.
15
-    * It can be used for inventory, anything that loads variables (i.e vars_files, group_vars, host_vars, include_vars, etc)
16
-      and some actions that deal with files (i.e M(copy), M(assemble), M(script), etc).
17
-
18
-:Single encrypted variable:
19
-    * Only specific variables are encrypted inside a normal 'variable file'.
20
-    * Does not work for other content, only variables.
21
-    * Decrypted on demand, so you can have vaulted variables with different vault secrets and only provide those needed.
22
-    * You can mix vaulted and non vaulted variables in the same file, even inline in a play or role.
23
-
24
-.. warning::
25
-    * Vault ONLY protects data 'at rest'.  Once decrypted, play and plugin authors are responsible for avoiding any secret disclosure,
26
-      see :ref:`no_log <keep_secret_data>` for details on hiding output.
27
-
28
-To enable this feature, a command line tool, :ref:`ansible-vault` is used to edit files, and a command line flag :option:`--ask-vault-pass <ansible-vault-create --ask-vault-pass>`, :option:`--vault-password-file <ansible-vault-create --vault-password-file>` or :option:`--vault-id <ansible-playbook --vault-id>` is used. You can also modify your ``ansible.cfg`` file to specify the location of a password file or configure Ansible to always prompt for the password. These options require no command line flag usage.
29
-
30
-For best practices advice, refer to :ref:`best_practices_for_variables_and_vaults`.
31
-
32
-Running a Playbook With Vault
33
-`````````````````````````````
34
-
35
-To run a playbook that contains vault-encrypted data files, you must provide the vault password.
36
-
37
-To specify the vault-password interactively::
38
-
39
-    ansible-playbook site.yml --ask-vault-pass
40
-
41
-This prompt will then be used to decrypt (in memory only) any vault encrypted files that are accessed.
42
-
43
-Alternatively, passwords can be specified with a file or a script (the script version will require Ansible 1.7 or later).  When using this flag, ensure permissions on the file are such that no one else can access your key and do not add your key to source control::
44
-
45
-    ansible-playbook site.yml --vault-password-file ~/.vault_pass.txt
46
-
47
-    ansible-playbook site.yml --vault-password-file ~/.vault_pass.py
48
-
49
-The password should be a string stored as a single line in the file.
50
-
51
-If you are using a script instead of a flat file, ensure that it is marked as executable, and that the password is printed to standard output.  If your script needs to prompt for data, prompts can be sent to standard error.
52
-
53
-.. note::
54
-   You can also set :envvar:`ANSIBLE_VAULT_PASSWORD_FILE` environment variable, e.g. ``ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass.txt`` and Ansible will automatically search for the password in that file.
55
-
56
-   This is something you may wish to do if using Ansible from a continuous integration system like Jenkins.
57
-
58
-The :option:`--vault-password-file <ansible-pull --vault-password-file>` option can also be used with the :ref:`ansible-pull` command if you wish, though this would require distributing the keys to your nodes, so understand the implications -- vault is more intended for push mode.
59
-
60
-
61
-Multiple Vault Passwords
62
-````````````````````````
63
-
64
-Ansible 2.4 and later support the concept of multiple vaults that are encrypted with different passwords
65
-Different vaults can be given a label to distinguish them (generally values like dev, prod etc.).
66
-
67
-The :option:`--ask-vault-pass <ansible-playbook --ask-vault-pass>` and
68
-:option:`--vault-password-file <ansible-playbook --vault-password-file>` options can be used as long as
69
-only a single password is needed for any given run.
70
-
71
-Alternatively the :option:`--vault-id <ansible-playbook --vault-id>` option can be used to provide the
72
-password and indicate which vault label it's for. This can be clearer when multiple vaults are used within
73
-a single inventory. For example:
74
-
75
-To be prompted for the 'dev' password:
76
-
77
-.. code-block:: bash
78
-
79
-    ansible-playbook site.yml --vault-id dev@prompt
80
-
81
-To get the 'dev' password from a file or script:
82
-
83
-.. code-block:: bash
84
-
85
-    ansible-playbook site.yml --vault-id dev@~/.vault_pass.txt
86
-
87
-    ansible-playbook site.yml --vault-id dev@~/.vault_pass.py
88
-
89
-If multiple vault passwords are required for a single run, :option:`--vault-id <ansible-playbook --vault-id>` must
90
-be used as it can be specified multiple times to provide the multiple passwords.  For example:
91
-
92
-To read the 'dev' password from a file and prompt for the 'prod' password:
93
-
94
-.. code-block:: bash
95
-
96
-    ansible-playbook site.yml --vault-id dev@~/.vault_pass.txt --vault-id prod@prompt
97
-
98
-The :option:`--ask-vault-pass <ansible-playbook --ask-vault-pass>` or
99
-:option:`--vault-password-file <ansible-playbook --vault-password-file>` options can be used to specify one of
100
-the passwords, but it's generally cleaner to avoid mixing these with :option:`--vault-id <ansible-playbook --vault-id>`.
101
-
102
-.. note::
103
-    By default the vault label (dev, prod etc.) is just a hint. Ansible will try to decrypt each
104
-    vault with every provided password.
105
-
106
-    Setting the config option :ref:`DEFAULT_VAULT_ID_MATCH` will change this behavior so that each password
107
-    is only used to decrypt data that was encrypted with the same label. See :ref:`specifying_vault_ids`
108
-    for more details.
109
-
110
-Vault Password Client Scripts
111
-`````````````````````````````
112
-
113
-Ansible 2.5 and later support using a single executable script to get different passwords depending on the
114
-vault label. These client scripts must have a file name that ends with :file:`-client`. For example:
115
-
116
-To get the dev password from the system keyring using the :file:`contrib/vault/vault-keyring-client.py` script:
117
-
118
-.. code-block:: bash
119
-
120
-    ansible-playbook --vault-id dev@contrib/vault/vault-keyring-client.py
121
-
122
-See :ref:`vault_password_client_scripts` for a complete explanation of this topic.
123
-
124
-
125
-.. _single_encrypted_variable:
126
-
127
-Single Encrypted Variable
128
-`````````````````````````
129
-
130
-As of version 2.3, Ansible can now use a vaulted variable that lives in an otherwise 'clear text' YAML file::
131
-
132
-    notsecret: myvalue
133
-    mysecret: !vault |
134
-              $ANSIBLE_VAULT;1.1;AES256
135
-              66386439653236336462626566653063336164663966303231363934653561363964363833313662
136
-              6431626536303530376336343832656537303632313433360a626438346336353331386135323734
137
-              62656361653630373231613662633962316233633936396165386439616533353965373339616234
138
-              3430613539666330390a313736323265656432366236633330313963326365653937323833366536
139
-              34623731376664623134383463316265643436343438623266623965636363326136
140
-    other_plain_text: othervalue
141
-
142
-To create a vaulted variable, use the :ref:`ansible-vault encrypt_string <ansible_vault_encrypt_string>` command. See :ref:`encrypt_string` for details.
143
-
144
-This vaulted variable will be decrypted with the supplied vault secret and used as a normal variable. The ``ansible-vault`` command line supports stdin and stdout for encrypting data on the fly, which can be used from your favorite editor to create these vaulted variables; you just have to be sure to add the ``!vault`` tag so both Ansible and YAML are aware of the need to decrypt. The ``|`` is also required, as vault encryption results in a multi-line string.
145
-
146
-.. note::
147
-   Inline vaults ONLY work on variables, you cannot use directly on a task's options.
148
-
149
-.. _encrypt_string:
150
-
151
-Using encrypt_string
152
-````````````````````
153
-
154
-This command will output a string in the above format ready to be included in a YAML file.
155
-The string to encrypt can be provided via stdin, command line arguments, or via an interactive prompt.
156
-
157
-See :ref:`encrypt_string_for_use_in_yaml`.
6
+The documentation regarding Ansible Vault has moved. The new location is here: :ref:`vault`. Please update any links you may have made directly to this page.
... ...
@@ -1,12 +1,9 @@
1 1
 .. _plugin_filtering_config:
2 2
 
3
-Plugin Filter Configuration
4
-===========================
3
+Blacklisting modules
4
+====================
5 5
 
6
-Ansible 2.5 adds the ability for a site administrator to blacklist modules that they do not want to
7
-be available to Ansible. This is configured via a yaml configuration file (by default,
8
-:file:`/etc/ansible/plugin_filters.yml`). Use ``plugin_filters_cfg`` configuration
9
-in ``defaults`` section to change this configuration file path. The format of the file is:
6
+If you want to avoid using certain modules, you can blacklist them to prevent Ansible from loading them. To blacklist plugins, create a yaml configuration file. The default location for this file is :file:`/etc/ansible/plugin_filters.yml`, or you can select a different path for the blacklist file using the :ref:`PLUGIN_FILTERS_CFG` setting in the ``defaults`` section of your ansible.cfg. Here is an example blacklist file:
10 7
 
11 8
 .. code-block:: YAML
12 9
 
... ...
@@ -20,12 +17,10 @@ in ``defaults`` section to change this configuration file path. The format of th
20 20
 
21 21
 The file contains two fields:
22 22
 
23
-* a version so that it will be possible to update the format while keeping backwards
24
-  compatibility in the future. The present version should be the string, ``"1.0"``
23
+  * A file version so that you can update the format while keeping backwards compatibility in the future. The present version should be the string, ``"1.0"``
25 24
 
26
-* a list of modules to blacklist.  Any module listed here will not be found by Ansible when it
27
-  searches for a module to invoke for a task.
25
+  * A list of modules to blacklist. Any module in this list will not be loaded by Ansible when it searches for a module to invoke for a task.
28 26
 
29 27
 .. note::
30 28
 
31
-    The ``stat`` module is required for Ansible to run. So, please make sure you do not add this module in a blacklist modules list.
29
+    You cannot blacklist the ``stat`` module, as it is required for Ansible to run.
... ...
@@ -1,209 +1,186 @@
1 1
 .. _vault:
2 2
 
3
-Ansible Vault
4
-=============
3
+*************************************
4
+Encrypting content with Ansible Vault
5
+*************************************
5 6
 
6
-.. contents:: Topics
7
+Ansible Vault encrypts variables and files so you can protect sensitive content such as passwords or keys rather than leaving it visible as plaintext in playbooks or roles. To use Ansible Vault you need one or more passwords to encrypt and decrypt content. If you store your vault passwords in a third-party tool such as a secret manager, you need a script to access them. Use the passwords with the :ref:`ansible-vault` command-line tool to create and view encrypted variables, create encrypted files, encrypt existing files, or edit, re-key, or decrypt files. You can then place encrypted content under source control and share it more safely.
7 8
 
8
-Ansible Vault is a feature of ansible that allows you to keep sensitive data such as passwords or keys in encrypted files, rather than as plaintext in playbooks or roles. These vault files can then be distributed or placed in source control.
9
-
10
-To enable this feature, a command line tool - :ref:`ansible-vault` - is used to edit files, and a command line flag (:option:`--ask-vault-pass <ansible-playbook --ask-vault-pass>`, :option:`--vault-password-file <ansible-playbook --vault-password-file>` or  :option:`--vault-id <ansible-playbook --vault-id>`) is used. Alternately, you may specify the location of a password file or command Ansible to always prompt for the password in your ansible.cfg file. These options require no command line flag usage.
11
-
12
-For best practices advice, refer to :ref:`best_practices_for_variables_and_vaults`.
13
-
14
-.. _what_can_be_encrypted_with_vault:
15
-
16
-What Can Be Encrypted With Vault
17
-````````````````````````````````
18
-
19
-File-level encryption
20
-^^^^^^^^^^^^^^^^^^^^^
21
-
22
-Ansible Vault can encrypt any structured data file used by Ansible.
9
+.. warning::
10
+    * Encryption with Ansible Vault ONLY protects 'data at rest'.  Once the content is decrypted ('data in use'), play and plugin authors are responsible for avoiding any secret disclosure, see :ref:`no_log <keep_secret_data>` for details on hiding output.
23 11
 
24
-This can include "group_vars/" or "host_vars/" inventory variables, variables loaded by "include_vars" or "vars_files", or variable files passed on the ansible-playbook command line with ``-e @file.yml`` or ``-e @file.json``.  Role variables and defaults are also included.
12
+You can use encrypted variables and files in ad-hoc commands and playbooks by supplying the passwords you used to encrypt them. You can modify your ``ansible.cfg`` file to specify the location of a password file or to always prompt for the password.
25 13
 
26
-Ansible tasks, handlers, and so on are also data so these can be encrypted with vault as well. To hide the names of variables that you're using, you can encrypt the task files in their entirety.
14
+.. contents::
15
+   :local:
27 16
 
28
-Ansible Vault can also encrypt arbitrary files, even binary files.  If a vault-encrypted file is
29
-given as the ``src`` argument to the :ref:`copy <copy_module>`, :ref:`template <template_module>`,
30
-:ref:`unarchive <unarchive_module>`, :ref:`script <script_module>` or :ref:`assemble
31
-<assemble_module>` modules, the file will be placed at the destination on the target host decrypted
32
-(assuming a valid vault password is supplied when running the play).
17
+Managing vault passwords
18
+========================
33 19
 
34
-.. note::
35
-    The advantages of file-level encryption are that it is easy to use and that password rotation is straightforward with :ref:`rekeying <rekeying_files>`.
36
-    The drawback is that the contents of files are no longer easy to access and read. This may be problematic if it is a list of tasks (when encrypting a variables file, :ref:`best practice <best_practices_for_variables_and_vaults>` is to keep references to these variables in a non-encrypted file).
20
+Managing your encrypted content is easier if you develop a strategy for managing your vault passwords. A vault password can be any string you choose. There is no special command to create a vault password. However, you need to keep track of your vault passwords. Each time you encrypt a variable or file with Ansible Vault, you must provide a password. When you use an encrypted variable or file in a command or playbook, you must provide the same password that was used to encrypt it. To develop a strategy for managing vault passwords, start with two questions:
37 21
 
22
+  * Do you want to encrypt all your content with the same password, or use different passwords for different needs?
23
+  * Where do you want to store your password or passwords?
38 24
 
39
-Variable-level encryption
40
-^^^^^^^^^^^^^^^^^^^^^^^^^
25
+Choosing between a single password and multiple passwords
26
+---------------------------------------------------------
41 27
 
42
-Ansible also supports encrypting single values inside a YAML file, using the `!vault` tag to let YAML and Ansible know it uses special processing. This feature is covered in more detail :ref:`below <encrypt_string_for_use_in_yaml>`.
43
-
44
-.. note::
45
-    The advantage of variable-level encryption is that files are still easily legible even if they mix plaintext and encrypted variables.
46
-    The drawback is that password rotation is not as simple as with file-level encryption: the :ref:`rekey <ansible_vault_rekey>` command does not work with this method.
28
+If you have a small team or few sensitive values, you can use a single password for everything you encrypt with Ansible Vault. Store your vault password securely in a file or a secret manager as described below.
47 29
 
30
+If you have a larger team or many sensitive values, you can use multiple passwords. For example, you can use different passwords for different users or different levels of access. Depending on your needs, you might want a different password for each encrypted file, for each directory, or for each environment. For example, you might have a playbook that includes two vars files, one for the dev environment and one for the production environment, encrypted with two different passwords. When you run the playbook, select the correct vault password for the environment you are targeting, using a vault ID.
48 31
 
49 32
 .. _vault_ids:
50 33
 
51
-Vault IDs and Multiple Vault Passwords
52
-``````````````````````````````````````
34
+Managing multiple passwords with vault IDs
35
+------------------------------------------
53 36
 
37
+If you use multiple vault passwords, you can differentiate one password from another with vault IDs. You use the vault ID in three ways:
54 38
 
55
-A vault ID is an identifier for one or more vault secrets;
56
-Ansible supports multiple vault passwords.
39
+  * Pass it with :option:`--vault-id <ansible-playbook --vault-id>` to the :ref:`ansible-vault` command when you create encrypted content
40
+  * Include it wherever you store the password for that vault ID (see :ref:`storing_vault_passwords`)
41
+  * Pass it with :option:`--vault-id <ansible-playbook --vault-id>` to the :ref:`ansible-playbook` command when you run a playbook that uses content you encrypted with that vault ID
57 42
 
58
-Vault IDs provide labels to distinguish between individual vault passwords.
43
+When you pass a vault ID as an option to the :ref:`ansible-vault` command, you add a label (a hint or nickname) to the encrypted content. This label documents which password you used to encrypt it. The encrypted variable or file includes the vault ID label in plain text in the header. The vault ID is the last element before the encrypted content. For example::
59 44
 
60
-To use vault IDs, you must provide an ID *label* of your choosing and a *source* to obtain its password (either ``prompt`` or a file path):
45
+    my_encrytped_var: !vault |
46
+              $ANSIBLE_VAULT;1.2;AES256;dev
47
+              30613233633461343837653833666333643061636561303338373661313838333565653635353162
48
+              3263363434623733343538653462613064333634333464660a663633623939393439316636633863
49
+              61636237636537333938306331383339353265363239643939666639386530626330633337633833
50
+              6664656334373166630a363736393262666465663432613932613036303963343263623137386239
51
+              6330
52
+
53
+In addition to the label, you must provide a source for the related password. The source can be a prompt, a file, or a script, depending on how you are storing your vault passwords. The pattern looks like this:
61 54
 
62 55
 .. code-block:: bash
63 56
 
64 57
    --vault-id label@source
65 58
 
66
-This switch is available for all Ansible commands that can interact with vaults: :ref:`ansible-vault`, :ref:`ansible-playbook`, etc.
67
-
68
-Vault-encrypted content can specify which vault ID it was encrypted with.
69
-
70
-For example, a playbook can now include a vars file encrypted with a 'dev' vault
71
-ID and a 'prod' vault ID.
72
-
73
-.. note:
74
-    Older versions of Ansible, before 2.4, only supported using one single vault password at a time.
59
+If your playbook uses multiple encrypted variables or files that you encrypted with different passwords, you must pass the vault IDs when you run that playbook. You can use :option:`--vault-id <ansible-playbook --vault-id>` by itself, with :option:`--vault-password-file <ansible-playbook --vault-password-file>`, or with :option:`--ask-vault-pass <ansible-playbook --ask-vault-pass>`. The pattern is the same as when you create encrypted content: include the label and the source for the matching password.
75 60
 
61
+See below for examples of encrypting content with vault IDs and using content encrypted with vault IDs. The :option:`--vault-id <ansible-playbook --vault-id>` option works with any Ansible command that interacts with vaults, including :ref:`ansible-vault`, :ref:`ansible-playbook`, and so on.
76 62
 
77
-.. _creating_files:
78
-
79
-Creating Encrypted Files
80
-````````````````````````
81
-
82
-To create a new encrypted data file, run the following command:
83
-
84
-.. code-block:: bash
85
-
86
-   ansible-vault create foo.yml
63
+Limitations of vault IDs
64
+^^^^^^^^^^^^^^^^^^^^^^^^
87 65
 
88
-First you will be prompted for a password. After providing a password, the tool will launch whatever editor you have defined with $EDITOR, and defaults to vi.  Once you are done with the editor session, the file will be saved as encrypted data.
66
+Ansible does not enforce using the same password every time you use a particular vault ID label. You can encrypt different variables or files with the same vault ID label but different passwords. This usually happens when you type the password at a prompt and make a mistake. It is possible to use different passwords with the same vault ID label on purpose. For example, you could use each label as a reference to a class of passwords, rather than a single password. In this scenario, you must always know which specific password or file to use in context. However, you are more likely to encrypt two files with the same vault ID label and different passwords by mistake. If you encrypt two files with the same label but different passwords by accident, you can :ref:`rekey <rekeying_files>` one file to fix the issue.
89 67
 
90
-The default cipher is AES (which is shared-secret based).
68
+Enforcing vault ID matching
69
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
91 70
 
92
-To create a new encrypted data file with the Vault ID 'password1' assigned to it and be prompted for the password, run:
71
+By default the vault ID label is only a hint to remind you which password you used to encrypt a variable or file. Ansible does not check that the vault ID in the header of the encrypted content matches the vault ID you provide when you use the content. Ansible decrypts all files and variables called by your command or playbook that are encrypted with the password you provide. To check the encrypted content and decrypt it only when the vault ID it contains matches the one you provide with ``--vault-id``, set the config option :ref:`DEFAULT_VAULT_ID_MATCH`. When you set :ref:`DEFAULT_VAULT_ID_MATCH`, each password is only used to decrypt data that was encrypted with the same label. This is efficient, predictable, and can reduce errors when different values are encrypted with different passwords.
93 72
 
94
-.. code-block:: bash
73
+.. note::
74
+   Even with the :ref:`DEFAULT_VAULT_ID_MATCH` setting enabled, Ansible does not enforce using the same password every time you use a particular vault ID label.
95 75
 
96
-   ansible-vault create --vault-id password1@prompt foo.yml
76
+.. _storing_vault_passwords:
97 77
 
78
+Storing and accessing vault passwords
79
+-------------------------------------
98 80
 
99
-.. _editing_encrypted_files:
81
+You can memorize your vault password, or manually copy vault passwords from any source and paste them at a command-line prompt, but most users store them securely and access them as needed from within Ansible. You have two options for storing vault passwords that work from within Ansible: in files, or in a third-party tool such as the system keyring or a secret manager. If you store your passwords in a third-party tool, you need a vault password client script to retrieve them from within Ansible.
100 82
 
101
-Editing Encrypted Files
102
-```````````````````````
83
+Storing passwords in files
84
+^^^^^^^^^^^^^^^^^^^^^^^^^^
103 85
 
104
-To edit an encrypted file in place, use the :ref:`ansible-vault edit <ansible_vault_edit>` command.
105
-This command will decrypt the file to a temporary file and allow you to edit
106
-the file, saving it back when done and removing the temporary file:
86
+To store a vault password in a file, enter the password as a string on a single line in the file. Make sure the permissions on the file are appropriate. Do not add password files to source control. If you have multiple passwords, you can store them all in a single file, as long as they all have vault IDs. For each password, create a separate line and enter the vault ID, a space, then the password as a string. For example:
107 87
 
108
-.. code-block:: bash
88
+.. code-block:: text
109 89
 
110
-   ansible-vault edit foo.yml
90
+   dev my_dev_pass
91
+   test my_test_pass
92
+   prod my_prod_pass
111 93
 
112
-To edit a file encrypted with the 'vault2' password file and assigned the 'pass2' vault ID:
113 94
 
114
-.. code-block:: bash
95
+.. _vault_password_client_scripts:
115 96
 
116
-   ansible-vault edit --vault-id pass2@vault2 foo.yml
97
+Storing passwords in third-party tools with vault password client scripts
98
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
117 99
 
100
+You can store your vault passwords on the system keyring, in a database, or in a secret manager and retrieve them from within Ansible using a vault password client script. Enter the password as a string on a single line. If your password has a vault ID, store it in a way that works with your password storage tool.
118 101
 
119
-.. _rekeying_files:
102
+To create a vault password client script:
120 103
 
121
-Rekeying Encrypted Files
122
-````````````````````````
104
+  * Create a file with a name ending in ``-client.py``
105
+  * Make the file executable
106
+  * Within the script itself:
107
+      * Print the passwords to standard output
108
+      * Accept a ``--vault-id`` option
109
+      * If the script prompts for data (for example, a database password), send the prompts to standard error
123 110
 
124
-Should you wish to change your password on a vault-encrypted file or files, you can do so with the rekey command:
111
+When you run a playbook that uses vault passwords stored in a third-party tool, specify the script as the source within the ``--vault-id`` flag. For example:
125 112
 
126 113
 .. code-block:: bash
127 114
 
128
-    ansible-vault rekey foo.yml bar.yml baz.yml
129
-
130
-This command can rekey multiple data files at once and will ask for the original
131
-password and also the new password.
115
+    ansible-playbook --vault-id dev@contrib/vault/vault-keyring-client.py
132 116
 
133
-To rekey files encrypted with the 'preprod2' vault ID and the 'ppold' file and be prompted for the new password:
117
+Ansible executes the client script with a ``--vault-id`` option so the script knows which vault ID label you specified. For example a script loading passwords from a secret manager can use the vault ID label to pick either the 'dev' or 'prod' password. The example command above results in the following execution of the client script:
134 118
 
135 119
 .. code-block:: bash
136 120
 
137
-    ansible-vault rekey --vault-id preprod2@ppold --new-vault-id preprod2@prompt foo.yml bar.yml baz.yml
138
-
139
-A different ID could have been set for the rekeyed files by passing it to ``--new-vault-id``.
121
+    contrib/vault/vault-keyring-client.py --vault-id dev
140 122
 
141
-.. _encrypting_files:
123
+For an example of a client script that loads passwords from the system keyring, see :file:`contrib/vault/vault-keyring-client.py`.
142 124
 
143
-Encrypting Unencrypted Files
144
-````````````````````````````
145 125
 
146
-If you have existing files that you wish to encrypt, use
147
-the :ref:`ansible-vault encrypt <ansible_vault_encrypt>` command.  This command can operate on multiple files at once:
126
+Encrypting content with Ansible Vault
127
+=====================================
148 128
 
149
-.. code-block:: bash
129
+Once you have a strategy for managing and storing vault passwords, you can start encrypting content. You can encrypt two types of content with Ansible Vault: variables and files. Encrypted content always includes the ``!vault`` tag, which tells Ansible and YAML that the content needs to be decrypted, and a ``|`` character, which allows multi-line strings. Encrypted content created with ``--vault-id`` also contains the vault ID label. For more details about the encryption process and the format of content encrypted with Ansible Vault, see :ref:`vault_format`. This table shows the main differences between encrypted variables and encrypted files:
150 130
 
151
-   ansible-vault encrypt foo.yml bar.yml baz.yml
131
+.. table::
132
+   :class: documentation-table
152 133
 
153
-To encrypt existing files with the 'project' ID and be prompted for the password:
134
+   ====================== ================================= ====================================
135
+   ..                     Encrypted variables                         Encrypted files
136
+   ====================== ================================= ====================================
137
+   How much is encrypted? Variables within a plaintext file The entire file
154 138
 
155
-.. code-block:: bash
139
+   When is it decrypted?  On demand, only when needed       Whenever loaded or referenced [#f1]_
156 140
 
157
-   ansible-vault encrypt --vault-id project@prompt foo.yml bar.yml baz.yml
141
+   What can be encrypted? Only variables                    Any structured data file
158 142
 
159
-.. note::
143
+   ====================== ================================= ====================================
160 144
 
161
-   It is technically possible to separately encrypt files or strings with the *same* vault ID but *different* passwords, if different password files or prompted passwords are provided each time.
162
-   This could be desirable if you use vault IDs as references to classes of passwords (rather than a single password) and you always know which specific password or file to use in context. However this may be an unnecessarily complex use-case.
163
-   If two files are encrypted with the same vault ID but different passwords by accident, you can use the :ref:`rekey <rekeying_files>` command to fix the issue.
145
+.. [#f1] Ansible cannot know if it needs content from an encrypted file unless it decrypts the file, so it decrypts all encrypted files referenced in your playbooks and roles.
164 146
 
147
+.. _encrypting_variables:
148
+.. _single_encrypted_variable:
165 149
 
166
-.. _decrypting_files:
150
+Encrypting individual variables with Ansible Vault
151
+--------------------------------------------------
167 152
 
168
-Decrypting Encrypted Files
169
-``````````````````````````
153
+You can encrypt single values inside a YAML file using the :ref:`ansible-vault encrypt_string <ansible_vault_encrypt_string>` command. For one way to keep your vaulted variables safely visible, see :ref:`tip_for_variables_and_vaults`.
170 154
 
171
-If you have existing files that you no longer want to keep encrypted, you can permanently decrypt
172
-them by running the :ref:`ansible-vault decrypt <ansible_vault_decrypt>` command.  This command will save them unencrypted
173
-to the disk, so be sure you do not want :ref:`ansible-vault edit <ansible_vault_edit>` instead:
155
+Advantages and disadvantages of encrypting variables
156
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
174 157
 
175
-.. code-block:: bash
158
+With variable-level encryption, your files are still easily legible. You can mix plaintext and encrypted variables, even inline in a play or role. However, password rotation is not as simple as with file-level encryption. You cannot :ref:`rekey <rekeying_files>` encrypted variables. Also, variable-level encryption only works on variables. If you want to encrypt tasks or other content, you must encrypt the entire file.
176 159
 
177
-    ansible-vault decrypt foo.yml bar.yml baz.yml
160
+.. _encrypt_string_for_use_in_yaml:
178 161
 
162
+Creating encrypted variables
163
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
179 164
 
180
-.. _viewing_files:
165
+The :ref:`ansible-vault encrypt_string <ansible_vault_encrypt_string>` command encrypts and formats any string you type (or copy or generate) into a format that can be included in a playbook, role, or variables file. To create a basic encrypted variable, pass three options to the :ref:`ansible-vault encrypt_string <ansible_vault_encrypt_string>` command:
181 166
 
182
-Viewing Encrypted Files
183
-```````````````````````
167
+  * a source for the vault password (prompt, file, or script, with or without a vault ID)
168
+  * the string to encrypt
169
+  * the string name (the name of the variable)
184 170
 
185
-If you want to view the contents of an encrypted file without editing it, you can use the :ref:`ansible-vault view <ansible_vault_view>` command:
171
+The pattern looks like this:
186 172
 
187 173
 .. code-block:: bash
188 174
 
189
-    ansible-vault view foo.yml bar.yml baz.yml
190
-
191
-
192
-.. _encrypt_string_for_use_in_yaml:
193
-
194
-Use encrypt_string to create encrypted variables to embed in yaml
195
-`````````````````````````````````````````````````````````````````
196
-
197
-The :ref:`ansible-vault encrypt_string <ansible_vault_encrypt_string>` command will encrypt and format a provided string into a format
198
-that can be included in :ref:`ansible-playbook` YAML files.
175
+    ansible-vault encrypt_string <password_source> '<string_to_encrypt>' --name '<string_name_of_variable>'
199 176
 
200
-To encrypt a string provided as a cli arg:
177
+For example, to encrypt the string 'foobar' using the only password stored in 'a_password_file' and name the variable 'the_secret':
201 178
 
202 179
 .. code-block:: bash
203 180
 
204 181
     ansible-vault encrypt_string --vault-password-file a_password_file 'foobar' --name 'the_secret'
205 182
 
206
-Result::
183
+The command above creates this content::
207 184
 
208 185
     the_secret: !vault |
209 186
           $ANSIBLE_VAULT;1.1;AES256
... ...
@@ -213,13 +190,13 @@ Result::
213 213
           3438626666666137650a353638643435666633633964366338633066623234616432373231333331
214 214
           6564
215 215
 
216
-To use a vault-id label for 'dev' vault-id:
216
+To encrypt the string 'foooodev', add the vault ID label 'dev' with the 'dev' vault password stored in 'a_password_file', and call the encrypted variable 'the_dev_secret':
217 217
 
218 218
 .. code-block:: bash
219 219
 
220 220
     ansible-vault encrypt_string --vault-id dev@a_password_file 'foooodev' --name 'the_dev_secret'
221 221
 
222
-Result::
222
+The command above creates this content::
223 223
 
224 224
     the_dev_secret: !vault |
225 225
               $ANSIBLE_VAULT;1.2;AES256;dev
... ...
@@ -229,17 +206,17 @@ Result::
229 229
               6664656334373166630a363736393262666465663432613932613036303963343263623137386239
230 230
               6330
231 231
 
232
-To encrypt a string read from stdin and name it 'db_password':
232
+To encrypt the string 'letmein' read from stdin, add the vault ID 'test' using the 'test' vault password stored in `a_password_file`, and name the variable 'test_db_password':
233 233
 
234 234
 .. code-block:: bash
235 235
 
236
-    echo -n 'letmein' | ansible-vault encrypt_string --vault-id dev@a_password_file --stdin-name 'db_password'
236
+    echo -n 'letmein' | ansible-vault encrypt_string --vault-id test@a_password_file --stdin-name 'test_db_password'
237 237
 
238 238
 .. warning::
239 239
 
240
-   This method leaves the string in your shell history. Do not use it outside of testing.
240
+   Typing secret content directly at the command line (without a prompt) leaves the secret string in your shell history. Do not do this outside of testing.
241 241
 
242
-Result::
242
+The command above creates this output::
243 243
 
244 244
     Reading plaintext input from stdin. (ctrl-d to end input)
245 245
     db_password: !vault |
... ...
@@ -250,24 +227,25 @@ Result::
250 250
               6565633133366366360a326566323363363936613664616364623437336130623133343530333739
251 251
               3039
252 252
 
253
-To be prompted for a string to encrypt, encrypt it, and give it the name 'new_user_password':
254
-
253
+To be prompted for a string to encrypt, encrypt it with the 'dev' vault password from 'a_password_file', name the variable 'new_user_password' and give it the vault ID label 'dev':
255 254
 
256 255
 .. code-block:: bash
257 256
 
258 257
     ansible-vault encrypt_string --vault-id dev@a_password_file --stdin-name 'new_user_password'
259 258
 
260
-Output::
259
+The command above triggers this prompt:
260
+
261
+.. code-block:: bash
261 262
 
262 263
     Reading plaintext input from stdin. (ctrl-d to end input)
263 264
 
264
-User enters 'hunter2' and hits ctrl-d.
265
+Type the string to encrypt (for example, 'hunter2'), hit ctrl-d, and wait.
265 266
 
266 267
 .. warning::
267 268
 
268
-   Do not press Enter after supplying the string. That will add a newline to the encrypted value.
269
+   Do not press ``Enter`` after supplying the string to encrypt. That will add a newline to the encrypted value.
269 270
 
270
-Result::
271
+The sequence above creates this output::
271 272
 
272 273
     new_user_password: !vault |
273 274
               $ANSIBLE_VAULT;1.2;AES256;dev
... ...
@@ -277,186 +255,263 @@ Result::
277 277
               3866363862363335620a376466656164383032633338306162326639643635663936623939666238
278 278
               3161
279 279
 
280
-See also :ref:`single_encrypted_variable`
280
+You can add the output from any of the examples above to any playbook, variables file, or role for future use. Encrypted variables are larger than plain-text variables, but they protect your sensitive content while leaving the rest of the playbook, variables file, or role in plain text so you can easily read it.
281
+
282
+Viewing encrypted variables
283
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
281 284
 
282
-After you added the encrypted value to a var file (vars.yml), you can see the original value using the debug module.
285
+You can view the original value of an encrypted variable using the debug module. You must pass the password that was used to encrypt the variable. For example, if you stored the variable created by the last example above in a file called 'vars.yml', you could view the unencrypted value of that variable like this:
283 286
 
284 287
 .. code-block:: console
285 288
 
286
-   ansible localhost -m debug -a var="new_user_password" -e "@vars.yml" --ask-vault-pass
287
-   Vault password:
289
+   ansible localhost -m debug -a var="new_user_password" -e "@vars.yml" --vault-id dev@a_password_file
288 290
 
289 291
    localhost | SUCCESS => {
290 292
        "new_user_password": "hunter2"
291 293
    }
292 294
 
293 295
 
294
-.. _providing_vault_passwords:
296
+Encrypting files with Ansible Vault
297
+-----------------------------------
298
+
299
+Ansible Vault can encrypt any structured data file used by Ansible, including:
300
+
301
+  * group variables files from inventory
302
+  * host variables files from inventory
303
+  * variables files passed to ansible-playbook with ``-e @file.yml`` or ``-e @file.json``
304
+  * variables files loaded by ``include_vars`` or ``vars_files``
305
+  * variables files in roles
306
+  * defaults files in roles
307
+  * tasks files
308
+  * handlers files
309
+  * binary files or other arbitrary files
295 310
 
296
-Providing Vault Passwords
297
-`````````````````````````
311
+The full file is encrypted in the vault.
298 312
 
299
-When all data is encrypted using a single password the :option:`--ask-vault-pass <ansible-playbook --ask-vault-pass>`
300
-or :option:`--vault-password-file <ansible-playbook --vault-password-file>` cli options should be used.
313
+Advantages and disadvantages of encrypting files
314
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
315
+
316
+File-level encryption is easy to use. Password rotation for encrypted files is straightforward with the :ref:`rekey <rekeying_files>` command. Encrypting files can hide not only sensitive values, but the names of the variables you use. However, with file-level encryption the contents of files are no longer easy to access and read. This may be a problem with encrypted tasks files. When encrypting a variables file, see :ref:`tip_for_variables_and_vaults` for one way to keep references to these variables in a non-encrypted file. Ansible always decrypts the entire encrypted file when it is when loaded or referenced, because Ansible cannot know if it needs the content unless it decrypts it.
317
+
318
+.. _creating_files:
319
+
320
+Creating encrypted files
321
+^^^^^^^^^^^^^^^^^^^^^^^^
301 322
 
302
-For example, to use a password store in the text file :file:`/path/to/my/vault-password-file`:
323
+To create a new encrypted data file called 'foo.yml' with the 'test' vault password from 'multi_password_file':
303 324
 
304 325
 .. code-block:: bash
305 326
 
306
-    ansible-playbook --vault-password-file /path/to/my/vault-password-file site.yml
327
+   ansible-vault create --vault-id test@multi_password_file foo.yml
328
+
329
+The tool launches an editor (whatever editor you have defined with $EDITOR, default editor is vi). Add the content. When you close the the editor session, the file is saved as encrypted data. The file header reflects the vault ID used to create it:
330
+
331
+.. code-block:: text
307 332
 
308
-To prompt for a password:
333
+   ``$ANSIBLE_VAULT;1.2;AES256;test``
334
+
335
+To create a new encrypted data file with the vault ID 'my_new_password' assigned to it and be prompted for the password:
309 336
 
310 337
 .. code-block:: bash
311 338
 
312
-    ansible-playbook --ask-vault-pass site.yml
339
+   ansible-vault create --vault-id my_new_password@prompt foo.yml
340
+
341
+Again, add content to the file in the editor and save. Be sure to store the new password you created at the prompt, so you can find it when you want to decrypt that file.
342
+
343
+.. _encrypting_files:
344
+
345
+Encrypting existing files
346
+^^^^^^^^^^^^^^^^^^^^^^^^^
313 347
 
314
-To get the password from a vault password executable script :file:`my-vault-password.py`:
348
+To encrypt an existing file, use the :ref:`ansible-vault encrypt <ansible_vault_encrypt>` command. This command can operate on multiple files at once. For example:
315 349
 
316 350
 .. code-block:: bash
317 351
 
318
-    ansible-playbook --vault-password-file my-vault-password.py
352
+   ansible-vault encrypt foo.yml bar.yml baz.yml
319 353
 
320
-The config option :ref:`DEFAULT_VAULT_PASSWORD_FILE` can be used to specify a vault password file so that the
321
-:option:`--vault-password-file <ansible-playbook --vault-password-file>` cli option does not have to be
322
-specified every time.
354
+To encrypt existing files with the 'project' ID and be prompted for the password:
323 355
 
356
+.. code-block:: bash
324 357
 
325
-.. _specifying_vault_ids:
358
+   ansible-vault encrypt --vault-id project@prompt foo.yml bar.yml baz.yml
326 359
 
327
-Labelling Vaults
328
-^^^^^^^^^^^^^^^^
329 360
 
330
-Since Ansible 2.4 the :option:`--vault-id <ansible-playbook --vault-id>` can be used to indicate which vault ID
331
-('dev', 'prod', 'cloud', etc) a password is for as well as how to source the password (prompt, a file path, etc).
361
+.. _viewing_files:
332 362
 
333
-By default the vault-id label is only a hint, any values encrypted with the password will be decrypted.
334
-The config option :ref:`DEFAULT_VAULT_ID_MATCH` can be set to require the vault id to match the vault ID
335
-used when the value was encrypted.
336
-This can reduce errors when different values are encrypted with different passwords.
363
+Viewing encrypted files
364
+^^^^^^^^^^^^^^^^^^^^^^^
337 365
 
338
-For example, to use a password file :file:`dev-password` for the vault-id 'dev':
366
+To view the contents of an encrypted file without editing it, you can use the :ref:`ansible-vault view <ansible_vault_view>` command:
339 367
 
340 368
 .. code-block:: bash
341 369
 
342
-    ansible-playbook --vault-id dev@dev-password site.yml
370
+    ansible-vault view foo.yml bar.yml baz.yml
343 371
 
344
-To prompt for the password for the 'dev' vault ID:
372
+
373
+.. _editing_encrypted_files:
374
+
375
+Editing encrypted files
376
+^^^^^^^^^^^^^^^^^^^^^^^
377
+
378
+To edit an encrypted file in place, use the :ref:`ansible-vault edit <ansible_vault_edit>` command. This command decrypts the file to a temporary file, allows you to edit the content, then saves and re-encrypts the content and removes the temporary file when you close the editor. For example:
345 379
 
346 380
 .. code-block:: bash
347 381
 
348
-    ansible-playbook --vault-id dev@prompt site.yml
382
+   ansible-vault edit foo.yml
349 383
 
350
-To get the 'dev' vault ID password from an executable script :file:`my-vault-password.py`:
384
+To edit a file encrypted with the ``vault2`` password file and assigned the vault ID ``pass2``:
351 385
 
352 386
 .. code-block:: bash
353 387
 
354
-    ansible-playbook --vault-id dev@my-vault-password.py
388
+   ansible-vault edit --vault-id pass2@vault2 foo.yml
389
+
355 390
 
391
+.. _rekeying_files:
356 392
 
357
-The config option :ref:`DEFAULT_VAULT_IDENTITY_LIST` can be used to specify a default vault ID and password source
358
-so that the :option:`--vault-id <ansible-playbook --vault-id>` cli option does not have to be specified every time.
393
+Changing the password and/or vault ID on encrypted files
394
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
359 395
 
396
+To change the password on an encrypted file or files, use the :ref:`rekey <ansible_vault_rekey>` command:
360 397
 
361
-The :option:`--vault-id <ansible-playbook --vault-id>` option can also be used without specifying a vault-id.
362
-This behaviour is equivalent to :option:`--ask-vault-pass <ansible-playbook --ask-vault-pass>` or
363
-:option:`--vault-password-file <ansible-playbook --vault-password-file>` so is rarely used.
398
+.. code-block:: bash
364 399
 
365
-For example, to use a password file :file:`dev-password`:
400
+    ansible-vault rekey foo.yml bar.yml baz.yml
401
+
402
+This command can rekey multiple data files at once and will ask for the original password and also the new password. To set a different ID for the rekeyed files, pass the new ID to ``--new-vault-id``. For example, to rekey a list of files encrypted with the 'preprod1' vault ID from the 'ppold' file to the 'preprod2' vault ID and be prompted for the new password:
366 403
 
367 404
 .. code-block:: bash
368 405
 
369
-    ansible-playbook --vault-id dev-password site.yml
406
+    ansible-vault rekey --vault-id preprod1@ppold --new-vault-id preprod2@prompt foo.yml bar.yml baz.yml
407
+
408
+
409
+.. _decrypting_files:
410
+
411
+Decrypting encrypted files
412
+^^^^^^^^^^^^^^^^^^^^^^^^^^
413
+
414
+If you have an encrypted file that you no longer want to keep encrypted, you can permanently decrypt it by running the :ref:`ansible-vault decrypt <ansible_vault_decrypt>` command. This command will save the file unencrypted to the disk, so be sure you do not want to :ref:`edit <ansible_vault_edit>` it instead.
415
+
416
+.. code-block:: bash
417
+
418
+    ansible-vault decrypt foo.yml bar.yml baz.yml
419
+
420
+
421
+.. _playbooks_vault:
422
+.. _providing_vault_passwords:
423
+
424
+Using encrypted variables and files
425
+===================================
426
+
427
+When you run a task or playbook that uses encrypted variables or files, you must provide the passwords to decrypt the variables or files. You can do this at the command line or in the playbook itself.
428
+
429
+Passing a single password
430
+-------------------------
431
+
432
+If all the encrypted variables and files your task or playbook needs use a single password, you can use the :option:`--ask-vault-pass <ansible-playbook --ask-vault-pass>` or :option:`--vault-password-file <ansible-playbook --vault-password-file>` cli options.
370 433
 
371 434
 To prompt for the password:
372 435
 
373 436
 .. code-block:: bash
374 437
 
375
-    ansible-playbook --vault-id @prompt site.yml
438
+    ansible-playbook --ask-vault-pass site.yml
376 439
 
377
-To get the password from an executable script :file:`my-vault-password.py`:
440
+To retrieve the password from the :file:`/path/to/my/vault-password-file` file:
378 441
 
379 442
 .. code-block:: bash
380 443
 
381
-    ansible-playbook --vault-id my-vault-password.py
444
+    ansible-playbook --vault-password-file /path/to/my/vault-password-file site.yml
382 445
 
383
-.. note::
384
-    Prior to Ansible 2.4, the :option:`--vault-id <ansible-playbook --vault-id>` option is not supported
385
-    so :option:`--ask-vault-pass <ansible-playbook --ask-vault-pass>` or
386
-    :option:`--vault-password-file <ansible-playbook --vault-password-file>` must be used.
446
+To get the password from the vault password client script :file:`my-vault-password-client.py`:
387 447
 
448
+.. code-block:: bash
449
+
450
+    ansible-playbook --vault-password-file my-vault-password-client.py
388 451
 
389
-Multiple Vault Passwords
390
-^^^^^^^^^^^^^^^^^^^^^^^^
391 452
 
392
-Ansible 2.4 and later support using multiple vault passwords, :option:`--vault-id <ansible-playbook --vault-id>` can
393
-be provided multiple times.
453
+.. _specifying_vault_ids:
454
+
455
+Passing vault IDs
456
+-----------------
394 457
 
395
-For example, to use a 'dev' password read from a file and to be prompted for the 'prod' password:
458
+You can also use the :option:`--vault-id <ansible-playbook --vault-id>` option to pass a single password with its vault label. This approach is clearer when multiple vaults are used within a single inventory.
459
+
460
+To prompt for the password for the 'dev' vault ID:
396 461
 
397 462
 .. code-block:: bash
398 463
 
399
-    ansible-playbook --vault-id dev@dev-password --vault-id prod@prompt site.yml
464
+    ansible-playbook --vault-id dev@prompt site.yml
465
+
466
+To retrieve the password for the 'dev' vault ID from the :file:`dev-password` file:
467
+
468
+.. code-block:: bash
469
+
470
+    ansible-playbook --vault-id dev@dev-password site.yml
400 471
 
401
-By default the vault ID labels (dev, prod etc.) are only hints, Ansible will attempt to decrypt vault content
402
-with each password. The password with the same label as the encrypted data will be tried first, after that
403
-each vault secret will be tried in the order they were provided on the command line.
472
+To get the password for the 'dev' vault ID from the vault password client script :file:`my-vault-password-client.py`:
404 473
 
405
-Where the encrypted data doesn't have a label, or the label doesn't match any of the provided labels, the
406
-passwords will be tried in the order they are specified.
474
+.. code-block:: bash
407 475
 
408
-In the above case, the 'dev' password will be tried first, then the 'prod' password for cases
409
-where Ansible doesn't know which vault ID is used to encrypt something.
476
+    ansible-playbook --vault-id dev@my-vault-password-client.py
410 477
 
411
-To add a vault ID label to the encrypted data use the :option:`--vault-id <ansible-vault-create --vault-id>` option
412
-with a label when encrypting the data.
478
+Passing multiple vault passwords
479
+--------------------------------
413 480
 
414
-The :ref:`DEFAULT_VAULT_ID_MATCH` config option can be set so that Ansible will only use the password with
415
-the same label as the encrypted data. This is more efficient and may be more predictable when multiple
416
-passwords are used.
481
+If your task or playbook requires multiple encrypted variables or files that you encrypted with different vault IDs, you must use the :option:`--vault-id <ansible-playbook --vault-id>` option, passing multiple ``--vault-id`` options to specify the vault IDs ('dev', 'prod', 'cloud', 'db') and sources for the passwords (prompt, file, script). . For example, to use a 'dev' password read from a file and to be prompted for the 'prod' password:
417 482
 
418
-The config option :ref:`DEFAULT_VAULT_IDENTITY_LIST` can have multiple values which is equivalent to multiple :option:`--vault-id <ansible-playbook --vault-id>` cli options.
483
+.. code-block:: bash
419 484
 
420
-The :option:`--vault-id <ansible-playbook --vault-id>` can be used in lieu of the :option:`--vault-password-file <ansible-playbook --vault-password-file>` or :option:`--ask-vault-pass <ansible-playbook --ask-vault-pass>` options,
421
-or it can be used in combination with them.
485
+    ansible-playbook --vault-id dev@dev-password --vault-id prod@prompt site.yml
422 486
 
423
-When using :ref:`ansible-vault` commands that encrypt content (:ref:`ansible-vault encrypt <ansible_vault_encrypt>`, :ref:`ansible-vault encrypt_string <ansible_vault_encrypt_string>`, etc)
424
-only one vault-id can be used.
487
+By default the vault ID labels (dev, prod etc.) are only hints. Ansible attempts to decrypt vault content with each password. The password with the same label as the encrypted data will be tried first, after that each vault secret will be tried in the order they were provided on the command line.
425 488
 
489
+Where the encrypted data has no label, or the label does not match any of the provided labels, the passwords will be tried in the order they are specified. In the example above, the 'dev' password will be tried first, then the 'prod' password for cases where Ansible doesn't know which vault ID is used to encrypt something.
426 490
 
427
-.. _vault_password_client_scripts:
491
+Using ``--vault-id`` without a vault ID
492
+---------------------------------------
428 493
 
429
-Vault Password Client Scripts
430
-`````````````````````````````
494
+The :option:`--vault-id <ansible-playbook --vault-id>` option can also be used without specifying a vault-id. This behavior is equivalent to :option:`--ask-vault-pass <ansible-playbook --ask-vault-pass>` or :option:`--vault-password-file <ansible-playbook --vault-password-file>` so is rarely used.
431 495
 
432
-When implementing a script to obtain a vault password it may be convenient to know which vault ID label was
433
-requested. For example a script loading passwords from a secret manager may want to use the vault ID label to pick
434
-either the 'dev' or 'prod' password.
496
+For example, to use a password file :file:`dev-password`:
497
+
498
+.. code-block:: bash
499
+
500
+    ansible-playbook --vault-id dev-password site.yml
435 501
 
436
-Since Ansible 2.5 this is supported through the use of Client Scripts. A Client Script is an executable script
437
-with a name ending in ``-client``. Client Scripts are used to obtain vault passwords in the same way as any other
438
-executable script. For example:
502
+To prompt for the password:
439 503
 
440 504
 .. code-block:: bash
441 505
 
442
-    ansible-playbook --vault-id dev@contrib/vault/vault-keyring-client.py
506
+    ansible-playbook --vault-id @prompt site.yml
443 507
 
444
-The difference is in the implementation of the script. Client Scripts are executed with a ``--vault-id`` option
445
-so they know which vault ID label was requested. So the above Ansible execution results in the below execution
446
-of the Client Script:
508
+To get the password from an executable script :file:`my-vault-password-client.py`:
447 509
 
448 510
 .. code-block:: bash
449 511
 
450
-    contrib/vault/vault-keyring-client.py --vault-id dev
512
+    ansible-playbook --vault-id my-vault-password-client.py
513
+
451 514
 
452
-:file:`contrib/vault/vault-keyring-client.py` is an example of Client Script that loads passwords from the
453
-system keyring.
515
+Configuring defaults for using encrypted content
516
+================================================
454 517
 
518
+Setting a default vault ID
519
+--------------------------
520
+
521
+If you use one vault ID more frequently than any other, you can set the config option :ref:`DEFAULT_VAULT_IDENTITY_LIST` to specify a default vault ID and password source. Ansible will use the default vault ID and source any time you do not specify :option:`--vault-id <ansible-playbook --vault-id>`. You can set multiple values for this option. Setting multiple values is equivalent to passing multiple :option:`--vault-id <ansible-playbook --vault-id>` cli options.
522
+
523
+Setting a default password source
524
+---------------------------------
525
+
526
+If you use one vault password file more frequently than any other, you can set the :ref:`DEFAULT_VAULT_PASSWORD_FILE` config option or the :envvar:`ANSIBLE_VAULT_PASSWORD_FILE` environment variable to specify that file. For example, if you set ``ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass.txt``, Ansible will automatically search for the password in that file. This is useful if, for example, you use Ansible from a continuous integration system such as Jenkins.
527
+
528
+When are encrypted files made visible?
529
+======================================
530
+
531
+In general, content you encrypt with Ansible Vault remains encrypted after execution. However, there is one exception. If you pass an encrypted file as the ``src`` argument to the :ref:`copy <copy_module>`, :ref:`template <template_module>`, :ref:`unarchive <unarchive_module>`, :ref:`script <script_module>` or :ref:`assemble <assemble_module>` module, the file will not be encrypted on the target host (assuming you supply the correct vault password when you run the play). This behavior is intended and useful. You can encrypt a configuration file or template to avoid sharing the details of your configuration, but when you copy that configuration to servers in your environment, you want it to be decrypted so local users and processes can access it.
455 532
 
456 533
 .. _speeding_up_vault:
457 534
 
458
-Speeding Up Vault Operations
459
-````````````````````````````
535
+Speeding up Ansible Vault
536
+=========================
460 537
 
461 538
 If you have many encrypted files, decrypting them at startup may cause a perceptible delay. To speed this up, install the cryptography package:
462 539
 
... ...
@@ -467,14 +522,10 @@ If you have many encrypted files, decrypting them at startup may cause a percept
467 467
 
468 468
 .. _vault_format:
469 469
 
470
-Vault Format
471
-````````````
470
+Format of files encrypted with Ansible Vault
471
+============================================
472 472
 
473
-A vault encrypted file is a UTF-8 encoded txt file.
474
-
475
-The file format includes a newline terminated header.
476
-
477
-For example::
473
+Ansible Vault creates UTF-8 encoded txt files. The file format includes a newline terminated header. For example::
478 474
 
479 475
     $ANSIBLE_VAULT;1.1;AES256
480 476
 
... ...
@@ -482,25 +533,23 @@ or::
482 482
 
483 483
     $ANSIBLE_VAULT;1.2;AES256;vault-id-label
484 484
 
485
-The header contains the vault format id, the vault format version, the vault cipher, and a vault-id label (with format version 1.2), separated by semi-colons ';'
486
-
487
-The first field ``$ANSIBLE_VAULT`` is the format id. Currently ``$ANSIBLE_VAULT`` is the only valid file format id. This is used to identify files that are vault encrypted (via vault.is_encrypted_file()).
485
+The header contains up to four elements, separated by semi-colons (``;``).
488 486
 
489
-The second field (``1.X``) is the vault format version. All supported versions of ansible will currently default to '1.1' or '1.2' if a labeled vault-id is supplied. 
487
+  1. The format ID (``$ANSIBLE_VAULT``). Currently ``$ANSIBLE_VAULT`` is the only valid format ID. The format ID identifies content that is encrypted with Ansible Vault (via vault.is_encrypted_file()).
490 488
 
491
-The '1.0' format is supported for reading only (and will be converted automatically to the '1.1' format on write). The format version is currently used as an exact string compare only (version numbers are not currently 'compared').
489
+  2. The vault format version (``1.X``). All supported versions of Ansible will currently default to '1.1' or '1.2' if a labeled vault ID is supplied. The '1.0' format is supported for reading only (and will be converted automatically to the '1.1' format on write). The format version is currently used as an exact string compare only (version numbers are not currently 'compared').
492 490
 
493
-The third field (``AES256``) identifies the cipher algorithm used to encrypt the data. Currently, the only supported cipher is 'AES256'. [vault format 1.0 used 'AES', but current code always uses 'AES256']
491
+  3. The cipher algorithm used to encrypt the data (``AES256``). Currently ``AES256`` is the only supported cipher algorithm. Vault format 1.0 used 'AES', but current code always uses 'AES256'.
494 492
 
495
-The fourth field (``vault-id-label``) identifies the vault-id label used to encrypt the data. For example using a vault-id of ``dev@prompt`` results in a vault-id-label of 'dev' being used.
493
+  4. The vault ID label used to encrypt the data (optional, ``vault-id-label``) For example, if you encrypt a file with ``--vault-id dev@prompt``, the vault-id-label is ``dev``.
496 494
 
497
-Note: In the future, the header could change. Anything after the vault id and version can be considered to depend on the vault format version. This includes the cipher id, and any additional fields that could be after that.
495
+Note: In the future, the header could change. Fields after the format ID and format version depend on the format version, and future vault format versions may add more cipher algorithm options and/or additional fields.
498 496
 
499 497
 The rest of the content of the file is the 'vaulttext'. The vaulttext is a text armored version of the
500
-encrypted ciphertext. Each line will be 80 characters wide, except for the last line which may be shorter.
498
+encrypted ciphertext. Each line is 80 characters wide, except for the last line which may be shorter.
501 499
 
502
-Vault Payload Format 1.1 - 1.2
503
-``````````````````````````````
500
+Ansible Vault payload format 1.1 - 1.2
501
+--------------------------------------
504 502
 
505 503
 The vaulttext is a concatenation of the ciphertext and a SHA256 digest with the result 'hexlifyied'.
506 504
 
... ...
@@ -537,5 +586,3 @@ hexlify()'ed result of:
537 537
 
538 538
       - the original plaintext
539 539
       - padding up to the AES256 blocksize. (The data used for padding is based on `RFC5652 <https://tools.ietf.org/html/rfc5652#section-6.3>`_)
540
-
541
-
... ...
@@ -496,7 +496,7 @@ Setup IIS Website
496 496
    :ref:`playbooks_intro`
497 497
        An introduction to playbooks
498 498
    :ref:`playbooks_best_practices`
499
-       Best practices advice
499
+       Tips and tricks for playbooks
500 500
    :ref:`List of Windows Modules <windows_modules>`
501 501
        Windows specific module list, all implemented in PowerShell
502 502
    `User Mailing List <https://groups.google.com/group/ansible-project>`_
... ...
@@ -229,7 +229,7 @@ host.
229 229
    :ref:`about_playbooks`
230 230
        An introduction to playbooks
231 231
    :ref:`playbooks_best_practices`
232
-       Best practices advice
232
+       Tips and tricks for playbooks
233 233
    `User Mailing List <https://groups.google.com/group/ansible-project>`_
234 234
        Have a question?  Stop by the google group!
235 235
    `irc.freenode.net <http://irc.freenode.net>`_
... ...
@@ -564,7 +564,7 @@ Here are the known ones:
564 564
    :ref:`about_playbooks`
565 565
        An introduction to playbooks
566 566
    :ref:`playbooks_best_practices`
567
-       Best practices advice
567
+       Tips and tricks for playbooks
568 568
    :ref:`List of Windows Modules <windows_modules>`
569 569
        Windows specific module list, all implemented in PowerShell
570 570
    `User Mailing List <https://groups.google.com/group/ansible-project>`_
... ...
@@ -504,7 +504,7 @@ guides for Windows modules differ substantially from those for standard standard
504 504
    :ref:`playbooks_intro`
505 505
        An introduction to playbooks
506 506
    :ref:`playbooks_best_practices`
507
-       Best practices advice
507
+       Tips and tricks for playbooks
508 508
    :ref:`List of Windows Modules <windows_modules>`
509 509
        Windows specific module list, all implemented in PowerShell
510 510
    `User Mailing List <https://groups.google.com/group/ansible-project>`_
... ...
@@ -904,7 +904,7 @@ Some of these limitations can be mitigated by doing one of the following:
904 904
    :ref:`playbooks_intro`
905 905
        An introduction to playbooks
906 906
    :ref:`playbooks_best_practices`
907
-       Best practices advice
907
+       Tips and tricks for playbooks
908 908
    :ref:`List of Windows Modules <windows_modules>`
909 909
        Windows specific module list, all implemented in PowerShell
910 910
    `User Mailing List <https://groups.google.com/group/ansible-project>`_
911 911
deleted file mode 100644
... ...
@@ -1,48 +0,0 @@
1
-{# avoids rST "isn't included in any toctree" errors for module docs #}
2
-:orphan:
3
-
4
-{% if title %}
5
-.. _@{ title.lower() + '_' + plugin_type + 's' }@:
6
-{% else %}
7
-.. _@{ plugin_type + 's' }@:
8
-{% endif %}
9
-
10
-{% if title %}
11
-@{ title }@ @{ plugin_type + 's' }@
12
-@{ '`' * title | length }@````````
13
-{% else %}
14
-@{ plugin_type + 's' }@
15
-```````
16
-{% endif %}
17
-
18
-{% if blurb %}
19
-@{ blurb }@
20
-
21
-{% endif %}
22
-
23
-{% if category['_modules'] %}
24
-
25
-{% for module in category['_modules'] | sort %}
26
-  * :ref:`@{ module }@_@{ plugin_type }@`{% if module_info[module]['deprecated'] %} **(D)**{% endif%} 
27
-{% endfor %}
28
-{% endif %}
29
-
30
-{% for name, info in subcategories.items() | sort %}
31
-
32
-.. _@{ name.lower() + '_' + title.lower() + '_' + plugin_type + 's' }@:
33
-
34
-@{ name.title() }@
35
-@{ '-' * name | length }@
36
-
37
-
38
-
39
-{% for module in info['_modules'] | sort %}
40
-  * :ref:`@{ module }@_@{ plugin_type }@`{% if module_info[module]['deprecated'] %} **(D)**{% endif%} 
41
-{% endfor %}
42
-
43
-{% endfor %}
44
-
45
-.. note::
46
-    - **(D)**: This marks a module as deprecated, which means a module is kept for backwards compatibility but usage is discouraged.
47
-      The module documentation details page may explain more about this rationale.
48
-
49 1
deleted file mode 100644
... ...
@@ -1,36 +0,0 @@
1
-.. _@{ title.lower() + '_' + plugin_type + 's' }@:
2
-
3
-@{ title }@ @{ plugin_type }@
4
-@{ '`' * title | length }@````````
5
-
6
-{% if blurb %}
7
-@{ blurb }@
8
-
9
-{% endif %}
10
-.. toctree:: :maxdepth: 1
11
-{% if category['_modules'] %}
12
-
13
-{% for module in category['_modules'] | sort %}
14
-  @{ module }@{% if module_info[module]['deprecated'] %} **(D)**{% endif%}{% if module_info[module]['doc']['short_description'] %} -- @{ module_info[module]['doc']['short_description'] }@{% endif %} <plugins/@{ module_info[module]['primary_category'] }@/@{ module }@>
15
-{% endfor %}
16
-{% endif %}
17
-
18
-{% for name, info in subcategories.items() | sort %}
19
-
20
-.. _@{ name.lower() + '_' + title.lower() + '_' + plugin_type + 's' }@:
21
-
22
-@{ name.title() }@
23
-@{ '-' * name | length }@
24
-
25
-.. toctree:: :maxdepth: 1
26
-
27
-{% for module in info['_modules'] | sort %}
28
-  :ref:`@{ module }@_@{ plugin_type }@`{% if module_info[module]['deprecated'] %} **(D)**{% endif%} -- @{ module_info[module]['doc']['short_description'] }@
29
-{% endfor %}
30
-
31
-{% endfor %}
32
-
33
-.. note::
34
-    - **(D)**: This marks a module as deprecated, which means a module is kept for backwards compatibility but usage is discouraged.
35
-      The module documentation details page may explain more about this rationale.
36
-
37 1
deleted file mode 100644
... ...
@@ -1,45 +0,0 @@
1
-.. _@{ slug }@:
2
-
3
-{# avoids rST "isn't included in any toctree" errors for module index docs #}
4
-:orphan:
5
-
6
-**************************@{ '*' * maintainers | length }@
7
-Modules Maintained by the @{ maintainers }@
8
-**************************@{ '*' * maintainers | length }@
9
-
10
-.. contents::
11
-   :local:
12
-
13
-{% for category, data in subcategories.items() | sort %}
14
-
15
-{% if category.lower() %}
16
-.. _@{ category.lower() + '_' + slug.lower() + '_categories' }@:
17
-{% else %}
18
-.. _@{ slug.lower() + '_categories' }@:
19
-{% endif %}
20
-
21
-@{ category.title() }@
22
-@{ '=' * category | length }@
23
-
24
-{% for name, info in data.items() | sort %}
25
-
26
-{% if name.lower() %}
27
-.. _@{ name.lower() + '_' + category + '_' + slug.lower() + '_' + plugin_type + 's' }@:
28
-{% else %}
29
-.. _@{ slug.lower() + '_' + category }@:
30
-{% endif %}
31
-
32
-@{ name.title() }@
33
-@{ '-' * name | length }@
34
-
35
-{% for module in info['_modules'] | sort %}
36
-  * :ref:`@{ module }@_@{plugin_type}@`{% if module_info[module]['deprecated'] %} **(D)** {% endif%} 
37
-{% endfor %}
38
-
39
-{% endfor %}
40
-
41
-{% endfor %}
42
-
43
-.. note::
44
-    - **(D)**: This marks a module as deprecated, which means a module is kept for backwards compatibility but usage is discouraged.
45
-      The module documentation details page may explain more about this rationale.
46 1
deleted file mode 100644
... ...
@@ -1,442 +0,0 @@
1
-:source: @{ source }@
2
-
3
-{# avoids rST "isn't included in any toctree" errors for module docs #}
4
-{% if plugin_type == 'module' %}
5
-:orphan:
6
-{% endif %}
7
-
8
-.. _@{ module }@_@{ plugin_type }@:
9
-{% for alias in aliases %}
10
-.. _@{ alias }@_@{ plugin_type }@:
11
-{% endfor %}
12
-
13
-{% if short_description %}
14
-{%   set title = module + ' -- ' + short_description | rst_ify %}
15
-{% else %}
16
-{%   set title = module %}
17
-{% endif %}
18
-
19
-@{ title }@
20
-@{ '+' * title|length }@
21
-
22
-{% if version_added is defined and version_added != '' -%}
23
-.. versionadded:: @{ version_added | default('') }@
24
-{% endif %}
25
-
26
-.. contents::
27
-   :local:
28
-   :depth: 1
29
-
30
-{# ------------------------------------------
31
- #
32
- # Please note: this looks like a core dump
33
- # but it isn't one.
34
- #
35
- --------------------------------------------#}
36
-{% if deprecated is defined -%}
37
-
38
-
39
-DEPRECATED
40
-{# use unknown here? skip the fields? #}
41
-:Removed in Ansible: version: @{ deprecated['removed_in'] | default('') | string | rst_ify }@
42
-:Why: @{ deprecated['why'] | default('') | rst_ify }@
43
-:Alternative: @{ deprecated['alternative'] | default('') | rst_ify }@
44
-
45
-
46
-{% endif %}
47
-
48
-Synopsis
49
-{% if description -%}
50
-
51
-{%   for desc in description %}
52
-- @{ desc | rst_ify }@
53
-{%   endfor %}
54
-
55
-{% endif %}
56
-
57
-{% if aliases is defined -%}
58
-Aliases: @{ ','.join(aliases) }@
59
-{% endif %}
60
-
61
-{% if requirements -%}
62
-
63
-Requirements
64
-{%   if plugin_type == 'module' %}
65
-The below requirements are needed on the host that executes this @{ plugin_type }@.
66
-{%   else %}
67
-The below requirements are needed on the local master node that executes this @{ plugin_type }@.
68
-{%   endif %}
69
-
70
-{%   for req in requirements %}
71
-- @{ req | rst_ify }@
72
-{%   endfor %}
73
-
74
-{% endif %}
75
-
76
-{% if options -%}
77
-
78
-Parameters
79
-
80
-.. raw:: html
81
-
82
-    <table  border=0 cellpadding=0 class="documentation-table">
83
-        {# Pre-compute the nesting depth to allocate columns -#}
84
-        @{ to_kludge_ns('maxdepth', 1) -}@
85
-        {% for key, value in options|dictsort recursive -%}
86
-            @{ to_kludge_ns('maxdepth', [loop.depth, from_kludge_ns('maxdepth')] | max) -}@
87
-            {% if value.suboptions -%}
88
-                {% if value.suboptions.items -%}
89
-                    @{ loop(value.suboptions.items()) -}@
90
-                {% elif value.suboptions[0].items -%}
91
-                    @{ loop(value.suboptions[0].items()) -}@
92
-                {% endif -%}
93
-            {% endif -%}
94
-        {% endfor -%}
95
-        {# Header of the documentation -#}
96
-        <tr>
97
-            <th colspan="@{ from_kludge_ns('maxdepth') }@">Parameter</th>
98
-            <th>Choices/<font color="blue">Defaults</font></th>
99
-            {% if plugin_type != 'module' %}
100
-                <th>Configuration</th>
101
-            {% endif %}
102
-            <th width="100%">Comments</th>
103
-        </tr>
104
-        {% for key, value in options|dictsort recursive %}
105
-            <tr>
106
-                {# indentation based on nesting level #}
107
-                {% for i in range(1, loop.depth) %}
108
-                    <td class="elbow-placeholder"></td>
109
-                {% endfor %}
110
-                {# parameter name with required and/or introduced label #}
111
-                <td colspan="@{ from_kludge_ns('maxdepth') - loop.depth0 }@">
112
-                    <div class="ansibleOptionAnchor" id="parameter-{% for part in value.full_key %}@{ part }@{% if not loop.last %}/{% endif %}{% endfor %}"></div>
113
-                    <b>@{ key }@</b>
114
-                    <a class="ansibleOptionLink" href="#parameter-{% for part in value.full_key %}@{ part }@{% if not loop.last %}/{% endif %}{% endfor %}" title="Permalink to this option"></a>
115
-                    <div style="font-size: small">
116
-                        <span style="color: purple">@{ value.type | documented_type }@</span>
117
-                        {% if value.get('elements') %} / <span style="color: purple">elements=@{ value.elements | documented_type }@</span>{% endif %}
118
-                        {% if value.get('required', False) %} / <span style="color: red">required</span>{% endif %}
119
-                    </div>
120
-                    {% if value.version_added %}<div style="font-style: italic; font-size: small; color: darkgreen">added in @{value.version_added}@</div>{% endif %}
121
-                </td>
122
-                {# default / choices #}
123
-                <td>
124
-                    {# Turn boolean values in 'yes' and 'no' values #}
125
-                    {% if value.default is sameas true %}
126
-                        {% set _x = value.update({'default': 'yes'}) %}
127
-                    {% elif value.default is sameas false %}
128
-                        {% set _x = value.update({'default': 'no'}) %}
129
-                    {% endif %}
130
-                    {% if value.type == 'bool' %}
131
-                        {% set _x = value.update({'choices': ['no', 'yes']}) %}
132
-                    {% endif %}
133
-                    {# Show possible choices and highlight details #}
134
-                    {% if value.choices %}
135
-                        <ul style="margin: 0; padding: 0"><b>Choices:</b>
136
-                            {% for choice in value.choices %}
137
-                                {# Turn boolean values in 'yes' and 'no' values #}
138
-                                {% if choice is sameas true %}
139
-                                    {% set choice = 'yes' %}
140
-                                {% elif choice is sameas false %}
141
-                                    {% set choice = 'no' %}
142
-                                {% endif %}
143
-                                {% if (value.default is not list and value.default == choice) or (value.default is list and choice in value.default) %}
144
-                                    <li><div style="color: blue"><b>@{ choice | escape }@</b>&nbsp;&larr;</div></li>
145
-                                {% else %}
146
-                                    <li>@{ choice | escape }@</li>
147
-                                {% endif %}
148
-                            {% endfor %}
149
-                        </ul>
150
-                    {% endif %}
151
-                    {# Show default value, when multiple choice or no choices #}
152
-                    {% if value.default is defined and value.default not in value.choices %}
153
-                        <b>Default:</b><br/><div style="color: blue">@{ value.default | tojson | escape }@</div>
154
-                    {% endif %}
155
-                </td>
156
-                {# configuration #}
157
-                {% if plugin_type != 'module' %}
158
-                    <td>
159
-                        {% if 'ini' in value %}
160
-                            <div> ini entries:
161
-                                {% for ini in value.ini %}
162
-                                    <p>[@{ ini.section }@]<br>@{ ini.key }@ = @{ value.default | default('VALUE') }@</p>
163
-                                {% endfor %}
164
-                            </div>
165
-                        {% endif %}
166
-                        {% if 'env' in value %}
167
-                            {% for env in value.env %}
168
-                                <div>env:@{ env.name }@</div>
169
-                            {% endfor %}
170
-                        {% endif %}
171
-                        {% if 'vars' in value %}
172
-                            {% for myvar in value.vars %}
173
-                                <div>var: @{ myvar.name }@</div>
174
-                            {% endfor %}
175
-                        {% endif %}
176
-                    </td>
177
-                {% endif %}
178
-                {# description #}
179
-                <td>
180
-                    {% for desc in value.description %}
181
-                        <div>@{ desc | replace('\n', '\n    ') | html_ify }@</div>
182
-                    {% endfor %}
183
-                    {% if 'aliases' in value and value.aliases %}
184
-                        <div style="font-size: small; color: darkgreen"><br/>aliases: @{ value.aliases|join(', ') }@</div>
185
-                    {% endif %}
186
-                </td>
187
-            </tr>
188
-            {% if value.suboptions %}
189
-                {% if value.suboptions.items %}
190
-                    @{ loop(value.suboptions|dictsort) }@
191
-                {% elif value.suboptions[0].items %}
192
-                    @{ loop(value.suboptions[0]|dictsort) }@
193
-                {% endif %}
194
-            {% endif %}
195
-        {% endfor %}
196
-    </table>
197
-    <br/>
198
-
199
-{% endif %}
200
-
201
-{% if notes -%}
202
-Notes
203
-
204
-.. note::
205
-{%   for note in notes %}
206
-   - @{ note | rst_ify }@
207
-{%   endfor %}
208
-
209
-{% endif %}
210
-
211
-{% if seealso -%}
212
-See Also
213
-
214
-.. seealso::
215
-
216
-{% for item in seealso %}
217
-{%   if item.module is defined and item.description is defined %}
218
-   :ref:`@{ item.module }@_module`
219
-       @{ item.description | rst_ify }@
220
-{%   elif item.module is defined %}
221
-   :ref:`@{ item.module }@_module`
222
-      The official documentation on the **@{ item.module }@** module.
223
-{%   elif item.name is defined and item.link is defined and item.description is defined %}
224
-   `@{ item.name }@ <@{ item.link }@>`_
225
-       @{ item.description | rst_ify }@
226
-{%   elif item.ref is defined and item.description is defined %}
227
-   :ref:`@{ item.ref }@`
228
-       @{ item.description | rst_ify }@
229
-{%   endif %}
230
-{% endfor %}
231
-
232
-{% endif %}
233
-
234
-{% if examples or plainexamples -%}
235
-
236
-Examples
237
-
238
-.. code-block:: yaml+jinja
239
-
240
-{%   for example in examples %}
241
-{%     if example['description'] %}@{ example['description'] | indent(4, True) }@{% endif %}
242
-@{ example['code'] | escape | indent(4, True) }@
243
-{%   endfor %}
244
-{%   if plainexamples %}@{ plainexamples | indent(4, True) }@{% endif %}
245
-
246
-{% endif %}
247
-
248
-{% if not returnfacts and returndocs and returndocs.ansible_facts is defined %}
249
-{%   set returnfacts = returndocs.ansible_facts.contains %}
250
-{%   set _x = returndocs.pop('ansible_facts', None) %}
251
-{% endif %}
252
-
253
-{% if returnfacts -%}
254
-
255
-Returned Facts
256
-Facts returned by this module are added/updated in the ``hostvars`` host facts and can be referenced by name just like any other host fact. They do not need to be registered in order to use them.
257
-
258
-.. raw:: html
259
-
260
-    <table border=0 cellpadding=0 class="documentation-table">
261
-        {# Pre-compute the nesting depth to allocate columns #}
262
-        @{ to_kludge_ns('maxdepth', 1) -}@
263
-        {% for key, value in returnfacts|dictsort recursive %}
264
-            @{ to_kludge_ns('maxdepth', [loop.depth, from_kludge_ns('maxdepth')] | max) -}@
265
-            {% if value.contains -%}
266
-                {% if value.contains.items -%}
267
-                    @{ loop(value.contains.items()) -}@
268
-                {% elif value.contains[0].items -%}
269
-                    @{ loop(value.contains[0].items()) -}@
270
-                {% endif -%}
271
-            {% endif -%}
272
-        {% endfor -%}
273
-        <tr>
274
-            <th colspan="@{ from_kludge_ns('maxdepth') }@">Fact</th>
275
-            <th>Returned</th>
276
-            <th width="100%">Description</th>
277
-        </tr>
278
-        {% for key, value in returnfacts|dictsort recursive %}
279
-            <tr>
280
-                {% for i in range(1, loop.depth) %}
281
-                    <td class="elbow-placeholder"></td>
282
-                {% endfor %}
283
-                <td colspan="@{ from_kludge_ns('maxdepth') - loop.depth0 }@" colspan="@{ from_kludge_ns('maxdepth') - loop.depth0 }@">
284
-                    <div class="ansibleOptionAnchor" id="return-{% for part in value.full_key %}@{ part }@{% if not loop.last %}/{% endif %}{% endfor %}"></div>
285
-                    <b>@{ key }@</b>
286
-                    <a class="ansibleOptionLink" href="#return-{% for part in value.full_key %}@{ part }@{% if not loop.last %}/{% endif %}{% endfor %}" title="Permalink to this fact"></a>
287
-                    <div style="font-size: small">
288
-                      <span style="color: purple">@{ value.type | documented_type }@</span>
289
-                      {% if value.elements %} / <span style="color: purple">elements=@{ value.elements | documented_type }@</span>{% endif %}
290
-                    </div>
291
-                    {% if value.version_added %}<div style="font-style: italic; font-size: small; color: darkgreen">added in @{value.version_added}@</div>{% endif %}
292
-                </td>
293
-                <td>@{ value.returned | html_ify }@</td>
294
-                <td>
295
-                    {% if value.description is string %}
296
-                        <div>@{ value.description | html_ify }@
297
-                        </div>
298
-                    {% else %}
299
-                        {% for desc in value.description %}
300
-                            <div>@{ desc | html_ify }@
301
-                            </div>
302
-                        {% endfor %}
303
-                    {% endif %}
304
-                    <br/>
305
-                    {% if value.sample is defined and value.sample %}
306
-                        <div style="font-size: smaller"><b>Sample:</b></div>
307
-                        {# TODO: The sample should be escaped, using | escape or | htmlify, but both mess things up beyond repair with dicts #}
308
-                        <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">@{ value.sample | replace('\n', '\n    ') | html_ify }@</div>
309
-                    {% endif %}
310
-                </td>
311
-            </tr>
312
-            {# ---------------------------------------------------------
313
-             # sadly we cannot blindly iterate through the child dicts,
314
-             # since in some documentations,
315
-             # lists are used instead of dicts. This handles both types
316
-             # ---------------------------------------------------------#}
317
-            {% if value.contains %}
318
-                {% if value.contains.items %}
319
-                    @{ loop(value.contains|dictsort) }@
320
-                {% elif value.contains[0].items %}
321
-                    @{ loop(value.contains[0]|dictsort) }@
322
-                {% endif %}
323
-            {% endif %}
324
-        {% endfor %}
325
-    </table>
326
-    <br/><br/>
327
-
328
-{% endif %}
329
-
330
-{% if returndocs -%}
331
-
332
-Return Values
333
-Common return values are documented :ref:`here <common_return_values>`, the following are the fields unique to this @{ plugin_type }@:
334
-
335
-.. raw:: html
336
-
337
-    <table border=0 cellpadding=0 class="documentation-table">
338
-        @{ to_kludge_ns('maxdepth', 1) -}@
339
-        {% for key, value in returndocs|dictsort recursive -%}
340
-            @{ to_kludge_ns('maxdepth', [loop.depth, from_kludge_ns('maxdepth')] | max) -}@
341
-            {% if value.contains -%}
342
-                {% if value.contains.items -%}
343
-                    @{ loop(value.contains.items()) -}@
344
-                {% elif value.contains[0].items -%}
345
-                    @{ loop(value.contains[0].items()) -}@
346
-                {% endif -%}
347
-            {% endif -%}
348
-        {% endfor -%}
349
-        <tr>
350
-            <th colspan="@{ from_kludge_ns('maxdepth') }@">Key</th>
351
-            <th>Returned</th>
352
-            <th width="100%">Description</th>
353
-        </tr>
354
-        {% for key, value in returndocs|dictsort recursive %}
355
-            <tr>
356
-                {% for i in range(1, loop.depth) %}
357
-                    <td class="elbow-placeholder">&nbsp;</td>
358
-                {% endfor %}
359
-                <td colspan="@{ from_kludge_ns('maxdepth') - loop.depth0 }@">
360
-                    <div class="ansibleOptionAnchor" id="return-{% for part in value.full_key %}@{ part }@{% if not loop.last %}/{% endif %}{% endfor %}"></div>
361
-                    <b>@{ key }@</b>
362
-                    <a class="ansibleOptionLink" href="#return-{% for part in value.full_key %}@{ part }@{% if not loop.last %}/{% endif %}{% endfor %}" title="Permalink to this return value"></a>
363
-                    <div style="font-size: small">
364
-                      <span style="color: purple">@{ value.type | documented_type }@</span>
365
-                      {% if value.elements %} / <span style="color: purple">elements=@{ value.elements | documented_type }@</span>{% endif %}
366
-                    </div>
367
-                    {% if value.version_added %}<div style="font-style: italic; font-size: small; color: darkgreen">added in @{value.version_added}@</div>{% endif %}
368
-                </td>
369
-                <td>@{ value.returned | html_ify }@</td>
370
-                <td>
371
-                    {% if value.description is string %}
372
-                        <div>@{ value.description | html_ify |indent(4) | trim}@</div>
373
-                    {% else %}
374
-                        {% for desc in value.description %}
375
-                            <div>@{ desc | html_ify |indent(4) | trim}@</div>
376
-                        {% endfor %}
377
-                    {% endif %}
378
-                    <br/>
379
-                    {% if value.sample is defined and value.sample %}
380
-                        <div style="font-size: smaller"><b>Sample:</b></div>
381
-                        {# TODO: The sample should be escaped, using |escape or |htmlify, but both mess things up beyond repair with dicts #}
382
-                        <div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">@{ value.sample | replace('\n', '\n    ') | html_ify }@</div>
383
-                    {% endif %}
384
-                </td>
385
-            </tr>
386
-            {# ---------------------------------------------------------
387
-             # sadly we cannot blindly iterate through the child dicts,
388
-             # since in some documentations,
389
-             # lists are used instead of dicts. This handles both types
390
-             # ---------------------------------------------------------#}
391
-            {% if value.contains %}
392
-                {% if value.contains.items %}
393
-                    @{ loop(value.contains|dictsort) }@
394
-                {% elif value.contains[0].items %}
395
-                    @{ loop(value.contains[0]|dictsort) }@
396
-                {% endif %}
397
-            {% endif %}
398
-        {% endfor %}
399
-    </table>
400
-    <br/><br/>
401
-
402
-{% endif %}
403
-
404
-Status
405
-
406
-{% if deprecated %}
407
-
408
-- This @{ plugin_type }@ will be removed in version @{ deprecated['removed_in'] | default('') | string | rst_ify }@. *[deprecated]*
409
-- For more information see `DEPRECATED`_.
410
-
411
-{% endif %}
412
-
413
-{% if author is defined -%}
414
-Authors
415
-~~~~~~~
416
-
417
-{%   for author_name in author %}
418
-- @{ author_name }@
419
-{%   endfor %}
420
-
421
-{% endif %}
422
-
423
-.. hint::
424
-{%   if plugin_type == 'module' %}
425
-    If you notice any issues in this documentation, you can `edit this document <https://github.com/ansible/ansible/edit/devel/lib/ansible/modules/@{ source }@?description=%23%23%23%23%23%20SUMMARY%0A%3C!---%20Your%20description%20here%20--%3E%0A%0A%0A%23%23%23%23%23%20ISSUE%20TYPE%0A-%20Docs%20Pull%20Request%0A%0A%2Blabel:%20docsite_pr>`_ to improve it.
426
-{% else %}
427
-    If you notice any issues in this documentation, you can `edit this document <https://github.com/ansible/ansible/edit/devel/lib/ansible/plugins/@{ plugin_type }@/@{ source }@?description=%23%23%23%23%23%20SUMMARY%0A%3C!---%20Your%20description%20here%20--%3E%0A%0A%0A%23%23%23%23%23%20ISSUE%20TYPE%0A-%20Docs%20Pull%20Request%0A%0A%2Blabel:%20docsite_pr>`_ to improve it.
428
-
429
-
430
-.. hint::
431
-    Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up.
432
-{% endif %}
433 1
deleted file mode 100644
... ...
@@ -1,18 +0,0 @@
1
-:source: @{ source }@
2
-
3
-{# avoids rST "isn't included in any toctree" errors for module docs #}
4
-:orphan:
5
-
6
-.. _@{ module }@_@{ plugin_type }@_alias_@{ alias }@:
7
-
8
-{% if short_description %}
9
-{%   set title = alias + ' -- ' + short_description | rst_ify %}
10
-{% else %}
11
-{%   set title = alias %}
12
-{% endif %}
13
-
14
-@{ title }@
15
-@{ '+' * title|length }@
16
-
17
-This is an alias for :ref:`@{ module }@ <@{ module }@_@{ plugin_type }@>`.
18
-This name has been **deprecated**. Please update your tasks to use the new name ``@{ module }@`` instead.
19 1
deleted file mode 100644
... ...
@@ -1,9 +0,0 @@
1
-Plugin Index
2
-============
3
-
4
-
5
-.. toctree:: :maxdepth: 1
6
-
7
-{% for name in categories %}
8
-   list_of_@{ name }@_plugins
9
-{% endfor %}
10 1
deleted file mode 100644
... ...
@@ -1,15 +0,0 @@
1
-.. _@{ slug }@:
2
-
3
-Plugins Maintained by the @{ maintainers }@
4
-``````````````````````````@{ '`' * maintainers | length }@
5
-
6
-.. toctree:: :maxdepth: 1
7
-
8
-{% for module in modules | sort %}
9
-  @{ module }@{% if module_info[module]['deprecated'] %} **(D)**{% endif %} - @{ module_info[module]['doc']['short_description'] }@ <plugins/@{ module_info[module]['primary_category'] }@/@{ module }@>
10
-{% endfor %}
11
-
12
-.. note::
13
-    - **(D)**: This marks a plugin as deprecated, which means a plugin is kept for backwards compatibility but usage is discouraged.
14
-      The plugin documentation details page may explain more about this rationale.
15
-
... ...
@@ -22,15 +22,25 @@ except ImportError:
22 22
 
23 23
 
24 24
 def build_lib_path(this_script=__file__):
25
-    """Return path to the common build library directory"""
25
+    """Return path to the common build library directory."""
26 26
     hacking_dir = os.path.dirname(this_script)
27 27
     libdir = os.path.abspath(os.path.join(hacking_dir, 'build_library'))
28 28
 
29 29
     return libdir
30 30
 
31 31
 
32
+def ansible_lib_path(this_script=__file__):
33
+    """Return path to the common build library directory."""
34
+    hacking_dir = os.path.dirname(this_script)
35
+    libdir = os.path.abspath(os.path.join(hacking_dir, '..', 'lib'))
36
+
37
+    return libdir
38
+
39
+
40
+sys.path.insert(0, ansible_lib_path())
32 41
 sys.path.insert(0, build_lib_path())
33 42
 
43
+
34 44
 from build_ansible import commands, errors
35 45
 
36 46
 
... ...
@@ -47,14 +57,15 @@ def create_arg_parser(program_name):
47 47
 
48 48
 def main():
49 49
     """
50
-    Main entrypoint of the script
50
+    Start our run.
51 51
 
52 52
     "It all starts here"
53 53
     """
54 54
     subcommands = load('build_ansible.command_plugins', subclasses=commands.Command)
55 55
 
56 56
     arg_parser = create_arg_parser(os.path.basename(sys.argv[0]))
57
-    arg_parser.add_argument('--debug', dest='debug', required=False, default=False, action='store_true',
57
+    arg_parser.add_argument('--debug', dest='debug', required=False, default=False,
58
+                            action='store_true',
58 59
                             help='Show tracebacks and other debugging information')
59 60
     subparsers = arg_parser.add_subparsers(title='Subcommands', dest='command',
60 61
                                            help='for help use build-ansible.py SUBCOMMANDS -h')
... ...
@@ -11,14 +11,13 @@ import os.path
11 11
 import pathlib
12 12
 
13 13
 import yaml
14
-from jinja2 import Environment, FileSystemLoader
15 14
 from ansible.module_utils.six import string_types
16 15
 from ansible.module_utils._text import to_bytes
16
+from antsibull.jinja2.environment import doc_environment
17 17
 
18 18
 # Pylint doesn't understand Python3 namespace modules.
19 19
 from ..change_detection import update_file_if_different  # pylint: disable=relative-beyond-top-level
20 20
 from ..commands import Command  # pylint: disable=relative-beyond-top-level
21
-from ..jinja2.filters import documented_type, rst_ify  # pylint: disable=relative-beyond-top-level
22 21
 
23 22
 
24 23
 DEFAULT_TEMPLATE_FILE = 'collections_galaxy_meta.rst.j2'
... ...
@@ -61,12 +60,7 @@ class DocumentCollectionMeta(Command):
61 61
 
62 62
         normalize_options(options)
63 63
 
64
-        env = Environment(loader=FileSystemLoader(template_dir),
65
-                          variable_start_string="@{",
66
-                          variable_end_string="}@",
67
-                          trim_blocks=True)
68
-        env.filters['documented_type'] = documented_type
69
-        env.filters['rst_ify'] = rst_ify
64
+        env = doc_environment(template_dir)
70 65
 
71 66
         template = env.get_template(template_file)
72 67
         output_name = os.path.join(output_dir, template_file.replace('.j2', ''))
73 68
new file mode 100644
... ...
@@ -0,0 +1,164 @@
0
+# coding: utf-8
1
+# Copyright: (c) 2020, Ansible Project
2
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
3
+
4
+# Make coding more python3-ish
5
+from __future__ import absolute_import, division, print_function
6
+
7
+import glob
8
+import os
9
+import os.path
10
+import pathlib
11
+import shutil
12
+from tempfile import TemporaryDirectory
13
+
14
+import yaml
15
+
16
+from ansible.release import __version__ as ansible_base__version__
17
+
18
+# Pylint doesn't understand Python3 namespace modules.
19
+# pylint: disable=relative-beyond-top-level
20
+from ..commands import Command
21
+# pylint: enable=relative-beyond-top-level
22
+
23
+
24
+__metaclass__ = type
25
+
26
+
27
+DEFAULT_TOP_DIR = pathlib.Path(__file__).parents[4]
28
+DEFAULT_OUTPUT_DIR = pathlib.Path(__file__).parents[4] / 'docs/docsite'
29
+
30
+
31
+#
32
+# Subcommand base
33
+#
34
+
35
+def generate_base_docs(args):
36
+    """Regenerate the documentation for all plugins listed in the plugin_to_collection_file."""
37
+    # imports here so that they don't cause unnecessary deps for all of the plugins
38
+    from antsibull.cli import antsibull_docs
39
+
40
+    with TemporaryDirectory() as tmp_dir:
41
+        #
42
+        # Construct a deps file with our version of ansible_base in it
43
+        #
44
+        modified_deps_file = os.path.join(tmp_dir, 'ansible.deps')
45
+
46
+        # The _acd_version doesn't matter
47
+        deps_file_contents = {'_acd_version': ansible_base__version__,
48
+                              '_ansible_base_version': ansible_base__version__}
49
+
50
+        with open(modified_deps_file, 'w') as f:
51
+            f.write(yaml.dump(deps_file_contents))
52
+
53
+        # Generate the plugin rst
54
+        return antsibull_docs.run(['antsibull-docs', 'stable', '--deps-file', modified_deps_file,
55
+                                   '--ansible-base-cache', str(args.top_dir),
56
+                                   '--dest-dir', args.output_dir])
57
+
58
+        # If we make this more than just a driver for antsibull:
59
+        # Run other rst generation
60
+        # Run sphinx build
61
+
62
+
63
+#
64
+# Subcommand full
65
+#
66
+
67
+def generate_full_docs(args):
68
+    """Regenerate the documentation for all plugins listed in the plugin_to_collection_file."""
69
+    # imports here so that they don't cause unnecessary deps for all of the plugins
70
+    import sh
71
+    from antsibull.cli import antsibull_docs
72
+    from packaging.version import Version
73
+
74
+    ansible_base_ver = Version(ansible_base__version__)
75
+    ansible_base_major_ver = '{0}.{1}'.format(ansible_base_ver.major, ansible_base_ver.minor)
76
+
77
+    with TemporaryDirectory() as tmp_dir:
78
+        sh.git(['clone', 'https://github.com/ansible-community/ansible-build-data'], _cwd=tmp_dir)
79
+        deps_files = glob.glob(os.path.join(tmp_dir, 'ansible-build-data',
80
+                                            ansible_base_major_ver, '*.deps'))
81
+        if not deps_files:
82
+            raise Exception('No deps files exist for version {0}'.format(ansible_base_major_ver))
83
+
84
+        # Find the latest version of the deps file for this version
85
+        latest = None
86
+        latest_ver = Version('0')
87
+        for filename in deps_files:
88
+            with open(filename, 'r') as f:
89
+                deps_data = yaml.safe_load(f.read())
90
+            new_version = Version(deps_data['_ansible_base_version'])
91
+            if new_version > latest_ver:
92
+                latest_ver = new_version
93
+                latest = filename
94
+
95
+        # Make a copy of the deps file so that we can set the ansible-base version to use
96
+        modified_deps_file = os.path.join(tmp_dir, 'ansible.deps')
97
+        shutil.copyfile(latest, modified_deps_file)
98
+
99
+        # Put our version of ansible-base into the deps file
100
+        with open(modified_deps_file, 'r') as f:
101
+            deps_data = yaml.safe_load(f.read())
102
+
103
+        deps_data['_ansible_base_version'] = ansible_base__version__
104
+
105
+        with open(modified_deps_file, 'w') as f:
106
+            f.write(yaml.dump(deps_data))
107
+
108
+        # Generate the plugin rst
109
+        return antsibull_docs.run(['antsibull-docs', 'stable', '--deps-file', modified_deps_file,
110
+                                   '--ansible-base-cache', str(args.top_dir),
111
+                                   '--dest-dir', args.output_dir])
112
+
113
+        # If we make this more than just a driver for antsibull:
114
+        # Run other rst generation
115
+        # Run sphinx build
116
+
117
+
118
+class CollectionPluginDocs(Command):
119
+    name = 'docs-build'
120
+    _ACTION_HELP = """Action to perform.
121
+        full: Regenerate the rst for the full ansible website.
122
+        base: Regenerate the rst for plugins in ansible-base and then build the website.
123
+        named: Regenerate the rst for the named plugins and then build the website.
124
+    """
125
+
126
+    @classmethod
127
+    def init_parser(cls, add_parser):
128
+        parser = add_parser(cls.name,
129
+                            description='Generate documentation for plugins in collections.'
130
+                            ' Plugins in collections will have a stub file in the normal plugin'
131
+                            ' documentation location that says the module is in a collection and'
132
+                            ' point to generated plugin documentation under the collections/'
133
+                            ' hierarchy.')
134
+        parser.add_argument('action', action='store', choices=('full', 'base', 'named'),
135
+                            default='full', help=cls._ACTION_HELP)
136
+        parser.add_argument("-o", "--output-dir", action="store", dest="output_dir",
137
+                            default=DEFAULT_OUTPUT_DIR,
138
+                            help="Output directory for generated doc files")
139
+        parser.add_argument("-t", "--top-dir", action="store", dest="top_dir",
140
+                            default=DEFAULT_TOP_DIR,
141
+                            help="Toplevel directory of this ansible-base checkout or expanded"
142
+                            " tarball.")
143
+        parser.add_argument("-l", "--limit-to-modules", '--limit-to', action="store",
144
+                            dest="limit_to", default=None,
145
+                            help="Limit building module documentation to comma-separated list of"
146
+                            " plugins. Specify non-existing plugin name for no plugins.")
147
+
148
+    @staticmethod
149
+    def main(args):
150
+        # normalize CLI args
151
+
152
+        if not args.output_dir:
153
+            args.output_dir = os.path.abspath(str(DEFAULT_OUTPUT_DIR))
154
+
155
+        if args.action == 'full':
156
+            return generate_full_docs(args)
157
+
158
+        if args.action == 'base':
159
+            return generate_base_docs(args)
160
+        # args.action == 'named' (Invalid actions are caught by argparse)
161
+        raise NotImplementedError('Building docs for specific files is not yet implemented')
162
+
163
+        # return 0
0 164
deleted file mode 100644
... ...
@@ -1,807 +0,0 @@
1
-# Copyright: (c) 2012, Jan-Piet Mens <jpmens () gmail.com>
2
-# Copyright: (c) 2012-2014, Michael DeHaan <michael@ansible.com> and others
3
-# Copyright: (c) 2017, Ansible Project
4
-
5
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
-
7
-from __future__ import absolute_import, division, print_function
8
-__metaclass__ = type
9
-
10
-
11
-import datetime
12
-import glob
13
-import json
14
-import os
15
-import re
16
-import sys
17
-import warnings
18
-from collections import defaultdict
19
-from copy import deepcopy
20
-from distutils.version import LooseVersion
21
-from functools import partial
22
-from pprint import PrettyPrinter
23
-
24
-try:
25
-    from html import escape as html_escape
26
-except ImportError:
27
-    # Python-3.2 or later
28
-    import cgi
29
-
30
-    def html_escape(text, quote=True):
31
-        return cgi.escape(text, quote)
32
-
33
-import jinja2
34
-import yaml
35
-from jinja2 import Environment, FileSystemLoader
36
-
37
-from ansible.errors import AnsibleError
38
-from ansible.module_utils._text import to_bytes
39
-from ansible.module_utils.common.collections import is_sequence
40
-from ansible.module_utils.parsing.convert_bool import boolean
41
-from ansible.module_utils.six import iteritems, string_types
42
-from ansible.plugins.loader import fragment_loader
43
-from ansible.utils import plugin_docs
44
-from ansible.utils.display import Display
45
-
46
-# Pylint doesn't understand Python3 namespace modules.
47
-from ..change_detection import update_file_if_different  # pylint: disable=relative-beyond-top-level
48
-from ..commands import Command  # pylint: disable=relative-beyond-top-level
49
-from ..jinja2.filters import do_max, documented_type, html_ify, rst_fmt, rst_ify, rst_xline  # pylint: disable=relative-beyond-top-level
50
-
51
-
52
-#####################################################################################
53
-# constants and paths
54
-
55
-# if a module is added in a version of Ansible older than this, don't print the version added information
56
-# in the module documentation because everyone is assumed to be running something newer than this already.
57
-TOO_OLD_TO_BE_NOTABLE = 2.4
58
-
59
-# Get parent directory of the directory this script lives in
60
-MODULEDIR = os.path.abspath(os.path.join(
61
-    os.path.dirname(os.path.realpath(__file__)), os.pardir, 'lib', 'ansible', 'modules'
62
-))
63
-
64
-# The name of the DOCUMENTATION template
65
-EXAMPLE_YAML = os.path.abspath(os.path.join(
66
-    os.path.dirname(os.path.realpath(__file__)), os.pardir, 'examples', 'DOCUMENTATION.yml'
67
-))
68
-
69
-DEPRECATED = b" (D)"
70
-
71
-pp = PrettyPrinter()
72
-display = Display()
73
-
74
-
75
-# kludge_ns gives us a kludgey way to set variables inside of loops that need to be visible outside
76
-# the loop.  We can get rid of this when we no longer need to build docs with less than Jinja-2.10
77
-# http://jinja.pocoo.org/docs/2.10/templates/#assignments
78
-# With Jinja-2.10 we can use jinja2's namespace feature, restoring the namespace template portion
79
-# of: fa5c0282a4816c4dd48e80b983ffc1e14506a1f5
80
-NS_MAP = {}
81
-
82
-
83
-def to_kludge_ns(key, value):
84
-    NS_MAP[key] = value
85
-    return ""
86
-
87
-
88
-def from_kludge_ns(key):
89
-    return NS_MAP[key]
90
-
91
-
92
-test_list = partial(is_sequence, include_strings=False)
93
-
94
-
95
-def normalize_options(value):
96
-    """Normalize boolean option value."""
97
-
98
-    if value.get('type') == 'bool' and 'default' in value:
99
-        try:
100
-            value['default'] = boolean(value['default'], strict=True)
101
-        except TypeError:
102
-            pass
103
-    return value
104
-
105
-
106
-def write_data(text, output_dir, outputname, module=None):
107
-    ''' dumps module output to a file or the screen, as requested '''
108
-
109
-    if output_dir is not None:
110
-        if module:
111
-            outputname = outputname % module
112
-
113
-        if not os.path.exists(output_dir):
114
-            os.makedirs(output_dir)
115
-        fname = os.path.join(output_dir, outputname)
116
-        fname = fname.replace(".py", "")
117
-
118
-        try:
119
-            updated = update_file_if_different(fname, to_bytes(text))
120
-        except Exception as e:
121
-            display.display("while rendering %s, an error occured: %s" % (module, e))
122
-            raise
123
-        if updated:
124
-            display.display("rendering: %s" % module)
125
-    else:
126
-        print(text)
127
-
128
-
129
-IS_STDOUT_TTY = sys.stdout.isatty()
130
-
131
-
132
-def show_progress(progress):
133
-    '''Show a little process indicator.'''
134
-    if IS_STDOUT_TTY:
135
-        sys.stdout.write('\r%s\r' % ("-/|\\"[progress % 4]))
136
-        sys.stdout.flush()
137
-
138
-
139
-def get_plugin_info(module_dir, limit_to=None, verbose=False):
140
-    '''
141
-    Returns information about plugins and the categories that they belong to
142
-
143
-    :arg module_dir: file system path to the top of the plugin directory
144
-    :kwarg limit_to: If given, this is a list of plugin names to
145
-        generate information for.  All other plugins will be ignored.
146
-    :returns: Tuple of two dicts containing module_info, categories, and
147
-        aliases and a set listing deprecated modules:
148
-
149
-        :module_info: mapping of module names to information about them.  The fields of the dict are:
150
-
151
-            :path: filesystem path to the module
152
-            :deprecated: boolean.  True means the module is deprecated otherwise not.
153
-            :aliases: set of aliases to this module name
154
-            :metadata: The modules metadata (as recorded in the module)
155
-            :doc: The documentation structure for the module
156
-            :seealso: The list of dictionaries with references to related subjects
157
-            :examples: The module's examples
158
-            :returndocs: The module's returndocs
159
-
160
-        :categories: maps category names to a dict.  The dict contains at
161
-            least one key, '_modules' which contains a list of module names in
162
-            that category.  Any other keys in the dict are subcategories with
163
-            the same structure.
164
-
165
-    '''
166
-
167
-    categories = dict()
168
-    module_info = defaultdict(dict)
169
-
170
-    # * windows powershell modules have documentation stubs in python docstring
171
-    #   format (they are not executed) so skip the ps1 format files
172
-    # * One glob level for every module level that we're going to traverse
173
-    files = (
174
-        glob.glob("%s/*.py" % module_dir) +
175
-        glob.glob("%s/*/*.py" % module_dir) +
176
-        glob.glob("%s/*/*/*.py" % module_dir) +
177
-        glob.glob("%s/*/*/*/*.py" % module_dir)
178
-    )
179
-
180
-    module_index = 0
181
-    for module_path in files:
182
-        # Do not list __init__.py files
183
-        if module_path.endswith('__init__.py'):
184
-            continue
185
-
186
-        # Do not list blacklisted modules
187
-        module = os.path.splitext(os.path.basename(module_path))[0]
188
-        if module in plugin_docs.BLACKLIST['MODULE'] or module == 'base':
189
-            continue
190
-
191
-        # If requested, limit module documentation building only to passed-in
192
-        # modules.
193
-        if limit_to is not None and module.lower() not in limit_to:
194
-            continue
195
-
196
-        deprecated = False
197
-        if module.startswith("_"):
198
-            if os.path.islink(module_path):
199
-                # Handle aliases
200
-                source = os.path.splitext(os.path.basename(os.path.realpath(module_path)))[0]
201
-                module = module.replace("_", "", 1)
202
-                if source.startswith("_"):
203
-                    source = source.replace("_", "", 1)
204
-                aliases = module_info[source].get('aliases', set())
205
-                aliases.add(module)
206
-                aliases_deprecated = module_info[source].get('aliases_deprecated', set())
207
-                aliases_deprecated.add(module)
208
-                # In case we just created this via get()'s fallback
209
-                module_info[source]['aliases'] = aliases
210
-                module_info[source]['aliases_deprecated'] = aliases_deprecated
211
-                continue
212
-            else:
213
-                # Handle deprecations
214
-                module = module.replace("_", "", 1)
215
-                deprecated = True
216
-
217
-        #
218
-        # Regular module to process
219
-        #
220
-
221
-        module_index += 1
222
-        show_progress(module_index)
223
-
224
-        # use ansible core library to parse out doc metadata YAML and plaintext examples
225
-        doc, examples, returndocs, metadata = plugin_docs.get_docstring(
226
-            module_path, fragment_loader, verbose=verbose, collection_name='ansible.builtin')
227
-
228
-        if metadata and 'removed' in metadata.get('status', []):
229
-            continue
230
-
231
-        category = categories
232
-
233
-        # Start at the second directory because we don't want the "vendor"
234
-        mod_path_only = os.path.dirname(module_path[len(module_dir):])
235
-
236
-        # Find the subcategory for each module
237
-        relative_dir = mod_path_only.split('/')[1]
238
-        sub_category = mod_path_only[len(relative_dir) + 2:]
239
-
240
-        primary_category = ''
241
-        module_categories = []
242
-        # build up the categories that this module belongs to
243
-        for new_cat in mod_path_only.split('/')[1:]:
244
-            if new_cat not in category:
245
-                category[new_cat] = dict()
246
-                category[new_cat]['_modules'] = []
247
-            module_categories.append(new_cat)
248
-            category = category[new_cat]
249
-
250
-        category['_modules'].append(module)
251
-
252
-        # the category we will use in links (so list_of_all_plugins can point to plugins/action_plugins/*'
253
-        if module_categories:
254
-            primary_category = module_categories[0]
255
-
256
-        if not doc:
257
-            display.error("*** ERROR: DOCUMENTATION section missing for %s. ***" % module_path)
258
-            continue
259
-
260
-        if 'options' in doc and doc['options'] is None:
261
-            display.error("*** ERROR: DOCUMENTATION.options must be a dictionary/hash when used. ***")
262
-            pos = getattr(doc, "ansible_pos", None)
263
-            if pos is not None:
264
-                display.error("Module position: %s, %d, %d" % doc.ansible_pos)
265
-            doc['options'] = dict()
266
-
267
-        for key, opt in doc.get('options', {}).items():
268
-            doc['options'][key] = normalize_options(opt)
269
-
270
-        # save all the information
271
-        module_info[module] = {'path': module_path,
272
-                               'source': os.path.relpath(module_path, module_dir),
273
-                               'deprecated': deprecated,
274
-                               'aliases': module_info[module].get('aliases', set()),
275
-                               'aliases_deprecated': module_info[module].get('aliases_deprecated', set()),
276
-                               'metadata': metadata,
277
-                               'doc': doc,
278
-                               'examples': examples,
279
-                               'returndocs': returndocs,
280
-                               'categories': module_categories,
281
-                               'primary_category': primary_category,
282
-                               'sub_category': sub_category,
283
-                               }
284
-
285
-    # keep module tests out of becoming module docs
286
-    if 'test' in categories:
287
-        del categories['test']
288
-
289
-    return module_info, categories
290
-
291
-
292
-def jinja2_environment(template_dir, typ, plugin_type):
293
-
294
-    env = Environment(loader=FileSystemLoader(template_dir),
295
-                      variable_start_string="@{",
296
-                      variable_end_string="}@",
297
-                      trim_blocks=True)
298
-    env.globals['xline'] = rst_xline
299
-
300
-    # Can be removed (and template switched to use namespace) when we no longer need to build
301
-    # with <Jinja-2.10
302
-    env.globals['to_kludge_ns'] = to_kludge_ns
303
-    env.globals['from_kludge_ns'] = from_kludge_ns
304
-    if 'max' not in env.filters:
305
-        # Jinja < 2.10
306
-        env.filters['max'] = do_max
307
-
308
-    if 'tojson' not in env.filters:
309
-        # Jinja < 2.9
310
-        env.filters['tojson'] = json.dumps
311
-
312
-    templates = {}
313
-    if typ == 'rst':
314
-        env.filters['rst_ify'] = rst_ify
315
-        env.filters['html_ify'] = html_ify
316
-        env.filters['fmt'] = rst_fmt
317
-        env.filters['xline'] = rst_xline
318
-        env.filters['documented_type'] = documented_type
319
-        env.tests['list'] = test_list
320
-        templates['plugin'] = env.get_template('plugin.rst.j2')
321
-        templates['plugin_deprecation_stub'] = env.get_template('plugin_deprecation_stub.rst.j2')
322
-
323
-        if plugin_type == 'module':
324
-            name = 'modules'
325
-        else:
326
-            name = 'plugins'
327
-
328
-        templates['category_list'] = env.get_template('%s_by_category.rst.j2' % name)
329
-        templates['support_list'] = env.get_template('%s_by_support.rst.j2' % name)
330
-        templates['list_of_CATEGORY_modules'] = env.get_template('list_of_CATEGORY_%s.rst.j2' % name)
331
-    else:
332
-        raise Exception("Unsupported format type: %s" % typ)
333
-
334
-    return templates
335
-
336
-
337
-def process_version_added(version_added):
338
-    if not isinstance(version_added, string_types):
339
-        return version_added
340
-    if ':' not in version_added:
341
-        return version_added
342
-    # Strip tag from version_added. It suffices to do this here since
343
-    # this is only used for ansible-base, and there the only valid tag
344
-    # is `ansible.builtin:`.
345
-    return version_added[version_added.index(':') + 1:]
346
-
347
-
348
-def too_old(added):
349
-    if not added:
350
-        return False
351
-    try:
352
-        added_tokens = str(added).split(".")
353
-        readded = added_tokens[0] + "." + added_tokens[1]
354
-        added_float = float(readded)
355
-    except ValueError as e:
356
-        warnings.warn("Could not parse %s: %s" % (added, str(e)))
357
-        return False
358
-    return added_float < TOO_OLD_TO_BE_NOTABLE
359
-
360
-
361
-def process_options(module, options, full_key=None):
362
-    option_names = []
363
-    if full_key is None:
364
-        full_key = []
365
-
366
-    if options:
367
-        for (k, v) in iteritems(options):
368
-            # Make sure that "full key" is contained
369
-            full_key_k = full_key + [k]
370
-            v['full_key'] = full_key_k
371
-
372
-            # Error out if there's no description
373
-            if 'description' not in v:
374
-                raise AnsibleError("Missing required description for parameter '%s' in '%s' " % (k, module))
375
-
376
-            # Make sure description is a list of lines for later formatting
377
-            if isinstance(v['description'], string_types):
378
-                v['description'] = [v['description']]
379
-            elif not isinstance(v['description'], (list, tuple)):
380
-                raise AnsibleError("Invalid type for options['%s']['description']."
381
-                                   " Must be string or list of strings.  Got %s" %
382
-                                   (k, type(v['description'])))
383
-
384
-            # Error out if required isn't a boolean (people have been putting
385
-            # information on when something is required in here.  Those need
386
-            # to go in the description instead).
387
-            required_value = v.get('required', False)
388
-            if not isinstance(required_value, bool):
389
-                raise AnsibleError("Invalid required value '%s' for parameter '%s' in '%s' (must be truthy)" % (required_value, k, module))
390
-
391
-            # Strip old version_added information for options
392
-            if 'version_added' in v:
393
-                v['version_added'] = process_version_added(v['version_added'])
394
-                if too_old(v['version_added']):
395
-                    del v['version_added']
396
-
397
-            if 'suboptions' in v and v['suboptions']:
398
-                if isinstance(v['suboptions'], dict):
399
-                    process_options(module, v['suboptions'], full_key=full_key_k)
400
-                elif isinstance(v['suboptions'][0], dict):
401
-                    process_options(module, v['suboptions'][0], full_key=full_key_k)
402
-
403
-            option_names.append(k)
404
-
405
-    option_names.sort()
406
-
407
-    return option_names
408
-
409
-
410
-def process_returndocs(returndocs, full_key=None):
411
-    if full_key is None:
412
-        full_key = []
413
-
414
-    if returndocs:
415
-        for (k, v) in iteritems(returndocs):
416
-            # Make sure that "full key" is contained
417
-            full_key_k = full_key + [k]
418
-            v['full_key'] = full_key_k
419
-
420
-            # Strip old version_added information for options
421
-            if 'version_added' in v:
422
-                v['version_added'] = process_version_added(v['version_added'])
423
-                if too_old(v['version_added']):
424
-                    del v['version_added']
425
-
426
-            # Process suboptions
427
-            suboptions = v.get('contains')
428
-            if suboptions:
429
-                if isinstance(suboptions, dict):
430
-                    process_returndocs(suboptions, full_key=full_key_k)
431
-                elif is_sequence(suboptions):
432
-                    process_returndocs(suboptions[0], full_key=full_key_k)
433
-
434
-
435
-def process_plugins(module_map, templates, outputname, output_dir, ansible_version, plugin_type):
436
-    for module_index, module in enumerate(module_map):
437
-
438
-        show_progress(module_index)
439
-
440
-        fname = module_map[module]['path']
441
-        display.vvvvv(pp.pformat(('process_plugins info: ', module_map[module])))
442
-
443
-        # crash if module is missing documentation and not explicitly hidden from docs index
444
-        if module_map[module]['doc'] is None:
445
-            display.error("%s MISSING DOCUMENTATION" % (fname,))
446
-            _doc = {plugin_type: module,
447
-                    'version_added': '2.4',
448
-                    'filename': fname}
449
-            module_map[module]['doc'] = _doc
450
-            # continue
451
-
452
-        # Going to reference this heavily so make a short name to reference it by
453
-        doc = module_map[module]['doc']
454
-        display.vvvvv(pp.pformat(('process_plugins doc: ', doc)))
455
-
456
-        # add some defaults for plugins that dont have most of the info
457
-        doc['module'] = doc.get('module', module)
458
-        doc['version_added'] = process_version_added(doc.get('version_added', 'historical'))
459
-
460
-        doc['plugin_type'] = plugin_type
461
-
462
-        if module_map[module]['deprecated'] and 'deprecated' not in doc:
463
-            display.warning("%s PLUGIN MISSING DEPRECATION DOCUMENTATION: %s" % (fname, 'deprecated'))
464
-
465
-        required_fields = ('short_description',)
466
-        for field in required_fields:
467
-            if field not in doc:
468
-                display.warning("%s PLUGIN MISSING field '%s'" % (fname, field))
469
-
470
-        not_nullable_fields = ('short_description',)
471
-        for field in not_nullable_fields:
472
-            if field in doc and doc[field] in (None, ''):
473
-                print("%s: WARNING: MODULE field '%s' DOCUMENTATION is null/empty value=%s" % (fname, field, doc[field]))
474
-
475
-        if 'description' in doc:
476
-            if isinstance(doc['description'], string_types):
477
-                doc['description'] = [doc['description']]
478
-            elif not isinstance(doc['description'], (list, tuple)):
479
-                raise AnsibleError("Description must be a string or list of strings.  Got %s"
480
-                                   % type(doc['description']))
481
-        else:
482
-            doc['description'] = []
483
-
484
-        if 'version_added' not in doc:
485
-            # Will never happen, since it has been explicitly inserted above.
486
-            raise AnsibleError("*** ERROR: missing version_added in: %s ***\n" % module)
487
-
488
-        #
489
-        # The present template gets everything from doc so we spend most of this
490
-        # function moving data into doc for the template to reference
491
-        #
492
-
493
-        if module_map[module]['aliases']:
494
-            doc['aliases'] = module_map[module]['aliases']
495
-
496
-        # don't show version added information if it's too old to be called out
497
-        added = 0
498
-        if doc['version_added'] == 'historical':
499
-            del doc['version_added']
500
-        else:
501
-            added = doc['version_added']
502
-
503
-        # Strip old version_added for the module
504
-        if too_old(added):
505
-            del doc['version_added']
506
-
507
-        doc['option_keys'] = process_options(module, doc.get('options'))
508
-        doc['filename'] = fname
509
-        doc['source'] = module_map[module]['source']
510
-        doc['docuri'] = doc['module'].replace('_', '-')
511
-        doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d')
512
-        doc['ansible_version'] = ansible_version
513
-
514
-        # check the 'deprecated' field in doc. We expect a dict potentially with 'why', 'version', and 'alternative' fields
515
-        # examples = module_map[module]['examples']
516
-        # print('\n\n%s: type of examples: %s\n' % (module, type(examples)))
517
-        # if examples and not isinstance(examples, (str, unicode, list)):
518
-        #    raise TypeError('module %s examples is wrong type (%s): %s' % (module, type(examples), examples))
519
-
520
-        # use 'examples' for 'plainexamples' if 'examples' is a string
521
-        if isinstance(module_map[module]['examples'], string_types):
522
-            doc['plainexamples'] = module_map[module]['examples']  # plain text
523
-        else:
524
-            doc['plainexamples'] = ''
525
-
526
-        doc['metadata'] = module_map[module]['metadata']
527
-
528
-        display.vvvvv(pp.pformat(module_map[module]))
529
-        if module_map[module]['returndocs']:
530
-            doc['returndocs'] = module_map[module]['returndocs']
531
-            process_returndocs(doc['returndocs'])
532
-        else:
533
-            doc['returndocs'] = None
534
-
535
-        doc['author'] = doc.get('author', ['UNKNOWN'])
536
-        if isinstance(doc['author'], string_types):
537
-            doc['author'] = [doc['author']]
538
-
539
-        display.v('about to template %s' % module)
540
-        display.vvvvv(pp.pformat(doc))
541
-        try:
542
-            text = templates['plugin'].render(doc)
543
-        except Exception as e:
544
-            display.warning(msg="Could not parse %s due to %s" % (module, e))
545
-            continue
546
-
547
-        if LooseVersion(jinja2.__version__) < LooseVersion('2.10'):
548
-            # jinja2 < 2.10's indent filter indents blank lines.  Cleanup
549
-            text = re.sub(' +\n', '\n', text)
550
-
551
-        write_data(text, output_dir, outputname, module)
552
-
553
-        # Create deprecation stub pages for deprecated aliases
554
-        if module_map[module]['aliases']:
555
-            for alias in module_map[module]['aliases']:
556
-                if alias in module_map[module]['aliases_deprecated']:
557
-                    doc['alias'] = alias
558
-
559
-                    display.v('about to template %s (deprecation alias %s)' % (module, alias))
560
-                    display.vvvvv(pp.pformat(doc))
561
-                    try:
562
-                        text = templates['plugin_deprecation_stub'].render(doc)
563
-                    except Exception as e:
564
-                        display.warning(msg="Could not parse %s (deprecation alias %s) due to %s" % (module, alias, e))
565
-                        continue
566
-
567
-                    if LooseVersion(jinja2.__version__) < LooseVersion('2.10'):
568
-                        # jinja2 < 2.10's indent filter indents blank lines.  Cleanup
569
-                        text = re.sub(' +\n', '\n', text)
570
-
571
-                    write_data(text, output_dir, outputname, alias)
572
-
573
-
574
-def process_categories(plugin_info, categories, templates, output_dir, output_name, plugin_type):
575
-    # For some reason, this line is changing plugin_info:
576
-    # text = templates['list_of_CATEGORY_modules'].render(template_data)
577
-    # To avoid that, make a deepcopy of the data.
578
-    # We should track that down and fix it at some point in the future.
579
-    plugin_info = deepcopy(plugin_info)
580
-    for category in sorted(categories.keys()):
581
-        module_map = categories[category]
582
-        category_filename = output_name % category
583
-
584
-        display.display("*** recording category %s in %s ***" % (category, category_filename))
585
-
586
-        # start a new category file
587
-
588
-        category_name = category.replace("_", " ")
589
-        category_title = category_name.title()
590
-
591
-        subcategories = dict((k, v) for k, v in module_map.items() if k != '_modules')
592
-        template_data = {'title': category_title,
593
-                         'category_name': category_name,
594
-                         'category': module_map,
595
-                         'subcategories': subcategories,
596
-                         'module_info': plugin_info,
597
-                         'plugin_type': plugin_type
598
-                         }
599
-
600
-        text = templates['list_of_CATEGORY_modules'].render(template_data)
601
-        write_data(text, output_dir, category_filename)
602
-
603
-
604
-def process_support_levels(plugin_info, categories, templates, output_dir, plugin_type):
605
-    supported_by = {'Ansible Core Team': {'slug': 'core_supported',
606
-                                          'modules': [],
607
-                                          'output': 'core_maintained.rst',
608
-                                          'blurb': "These are :doc:`modules maintained by the"
609
-                                                   " Ansible Core Team<core_maintained>` and will always ship"
610
-                                                   " with Ansible itself."},
611
-                    'Ansible Network Team': {'slug': 'network_supported',
612
-                                             'modules': [],
613
-                                             'output': 'network_maintained.rst',
614
-                                             'blurb': "These are :doc:`modules maintained by the"
615
-                                                      " Ansible Network Team<network_maintained>` in"
616
-                                                      " a relationship similar to how the Ansible Core Team"
617
-                                                      " maintains the Core modules."},
618
-                    'Ansible Partners': {'slug': 'certified_supported',
619
-                                         'modules': [],
620
-                                         'output': 'partner_maintained.rst',
621
-                                         'blurb': """
622
-Some examples of :doc:`Certified Modules<partner_maintained>` are those submitted by other
623
-companies. Maintainers of these types of modules must watch for any issues reported or pull requests
624
-raised against the module.
625
-
626
-The Ansible Core Team will review all modules becoming certified.  Core committers will review
627
-proposed changes to existing Certified Modules once the community maintainers of the module have
628
-approved the changes. Core committers will also ensure that any issues that arise due to Ansible
629
-engine changes will be remediated.  Also, it is strongly recommended (but not presently required)
630
-for these types of modules to have unit tests.
631
-
632
-These modules are currently shipped with Ansible, but might be shipped separately in the future.
633
-"""},
634
-                    'Ansible Community': {'slug': 'community_supported',
635
-                                          'modules': [],
636
-                                          'output': 'community_maintained.rst',
637
-                                          'blurb': """
638
-These are :doc:`modules maintained by the Ansible Community<community_maintained>`.  They **are
639
-not** supported by the Ansible Core Team or by companies/partners associated to the module.
640
-
641
-They are still fully usable, but the response rate to issues is purely up to the community.  Best
642
-effort support will be provided but is not covered under any support contracts.
643
-
644
-These modules are currently shipped with Ansible, but will most likely be shipped separately in the future.
645
-                                          """},
646
-                    }
647
-
648
-    # only gen support pages for modules for now, need to split and namespace templates and generated docs
649
-    if plugin_type == 'plugins':
650
-        return
651
-    # Separate the modules by support_level
652
-    for module, info in plugin_info.items():
653
-        if not info.get('metadata', None):
654
-            display.warning('no metadata for %s' % module)
655
-            continue
656
-        if info['metadata']['supported_by'] == 'core':
657
-            supported_by['Ansible Core Team']['modules'].append(module)
658
-        elif info['metadata']['supported_by'] == 'network':
659
-            supported_by['Ansible Network Team']['modules'].append(module)
660
-        elif info['metadata']['supported_by'] == 'certified':
661
-            supported_by['Ansible Partners']['modules'].append(module)
662
-        elif info['metadata']['supported_by'] == 'community':
663
-            supported_by['Ansible Community']['modules'].append(module)
664
-        else:
665
-            raise AnsibleError('Unknown supported_by value: %s' % info['metadata']['supported_by'])
666
-
667
-    # Render the module lists based on category and subcategory
668
-    for maintainers, data in supported_by.items():
669
-        subcategories = {}
670
-        subcategories[''] = {}
671
-        for module in data['modules']:
672
-            new_cat = plugin_info[module]['sub_category']
673
-            category = plugin_info[module]['primary_category']
674
-            if category not in subcategories:
675
-                subcategories[category] = {}
676
-                subcategories[category][''] = {}
677
-                subcategories[category]['']['_modules'] = []
678
-            if new_cat not in subcategories[category]:
679
-                subcategories[category][new_cat] = {}
680
-                subcategories[category][new_cat]['_modules'] = []
681
-            subcategories[category][new_cat]['_modules'].append(module)
682
-
683
-        template_data = {'maintainers': maintainers,
684
-                         'subcategories': subcategories,
685
-                         'modules': data['modules'],
686
-                         'slug': data['slug'],
687
-                         'module_info': plugin_info,
688
-                         'plugin_type': plugin_type
689
-                         }
690
-        text = templates['support_list'].render(template_data)
691
-        write_data(text, output_dir, data['output'])
692
-
693
-
694
-def validate_options(options):
695
-    ''' validate option parser options '''
696
-
697
-    if not options.module_dir:
698
-        sys.exit("--module-dir is required")
699
-    if not os.path.exists(options.module_dir):
700
-        sys.exit("--module-dir does not exist: %s" % options.module_dir)
701
-    if not options.template_dir:
702
-        sys.exit("--template-dir must be specified")
703
-
704
-
705
-class DocumentPlugins(Command):
706
-    name = 'document-plugins'
707
-
708
-    @classmethod
709
-    def init_parser(cls, add_parser):
710
-        parser = add_parser(cls.name, description='Generate module documentation from metadata')
711
-
712
-        parser.add_argument("-A", "--ansible-version", action="store", dest="ansible_version",
713
-                            default="unknown", help="Ansible version number")
714
-        parser.add_argument("-M", "--module-dir", action="store", dest="module_dir",
715
-                            default=MODULEDIR, help="Ansible library path")
716
-        parser.add_argument("-P", "--plugin-type", action="store", dest="plugin_type",
717
-                            default='module', help="The type of plugin (module, lookup, etc)")
718
-        parser.add_argument("-T", "--template-dir", action="append", dest="template_dir",
719
-                            help="directory containing Jinja2 templates")
720
-        parser.add_argument("-t", "--type", action='store', dest='type', choices=['rst'],
721
-                            default='rst', help="Document type")
722
-        parser.add_argument("-o", "--output-dir", action="store", dest="output_dir", default=None,
723
-                            help="Output directory for module files")
724
-        parser.add_argument("-I", "--includes-file", action="store", dest="includes_file",
725
-                            default=None, help="Create a file containing list of processed modules")
726
-        parser.add_argument("-l", "--limit-to-modules", '--limit-to', action="store",
727
-                            dest="limit_to", default=None, help="Limit building module documentation"
728
-                            " to comma-separated list of plugins. Specify non-existing plugin name"
729
-                            " for no plugins.")
730
-        parser.add_argument('-V', action='version', help='Show version number and exit')
731
-        parser.add_argument('-v', '--verbose', dest='verbosity', default=0, action="count",
732
-                            help="verbose mode (increase number of 'v's for more)")
733
-
734
-    @staticmethod
735
-    def main(args):
736
-        if not args.template_dir:
737
-            args.template_dir = ["hacking/templates"]
738
-        validate_options(args)
739
-        display.verbosity = args.verbosity
740
-        plugin_type = args.plugin_type
741
-
742
-        display.display("Evaluating %s files..." % plugin_type)
743
-
744
-        # prep templating
745
-        templates = jinja2_environment(args.template_dir, args.type, plugin_type)
746
-
747
-        # set file/directory structure
748
-        if plugin_type == 'module':
749
-            # trim trailing s off of plugin_type for plugin_type=='modules'. ie 'copy_module.rst'
750
-            outputname = '%s_' + '%s.rst' % plugin_type
751
-            output_dir = args.output_dir
752
-        else:
753
-            # for plugins, just use 'ssh.rst' vs 'ssh_module.rst'
754
-            outputname = '%s.rst'
755
-            output_dir = '%s/plugins/%s' % (args.output_dir, plugin_type)
756
-
757
-        display.vv('output name: %s' % outputname)
758
-        display.vv('output dir: %s' % output_dir)
759
-
760
-        # Convert passed-in limit_to to None or list of modules.
761
-        if args.limit_to is not None:
762
-            args.limit_to = [s.lower() for s in args.limit_to.split(",")]
763
-
764
-        plugin_info, categories = get_plugin_info(args.module_dir, limit_to=args.limit_to, verbose=(args.verbosity > 0))
765
-
766
-        categories['all'] = {'_modules': plugin_info.keys()}
767
-
768
-        if display.verbosity >= 3:
769
-            display.vvv(pp.pformat(categories))
770
-        if display.verbosity >= 5:
771
-            display.vvvvv(pp.pformat(plugin_info))
772
-
773
-        # Transform the data
774
-        if args.type == 'rst':
775
-            display.v('Generating rst')
776
-            for key, record in plugin_info.items():
777
-                display.vv(key)
778
-                if display.verbosity >= 5:
779
-                    display.vvvvv(pp.pformat(('record', record)))
780
-                if record.get('doc', None):
781
-                    short_desc = record['doc']['short_description'].rstrip('.')
782
-                    if short_desc is None:
783
-                        display.warning('short_description for %s is None' % key)
784
-                        short_desc = ''
785
-                    record['doc']['short_description'] = rst_ify(short_desc)
786
-
787
-        if plugin_type == 'module':
788
-            display.v('Generating Categories')
789
-            # Write module master category list
790
-            category_list_text = templates['category_list'].render(categories=sorted(categories.keys()))
791
-            category_index_name = '%ss_by_category.rst' % plugin_type
792
-            write_data(category_list_text, output_dir, category_index_name)
793
-
794
-        # Render all the individual plugin pages
795
-        display.v('Generating plugin pages')
796
-        process_plugins(plugin_info, templates, outputname, output_dir, args.ansible_version, plugin_type)
797
-
798
-        # Render all the categories for modules
799
-        if plugin_type == 'module':
800
-            display.v('Generating Category lists')
801
-            category_list_name_template = 'list_of_%s_' + '%ss.rst' % plugin_type
802
-            process_categories(plugin_info, categories, templates, output_dir, category_list_name_template, plugin_type)
803
-
804
-            # Render all the categories for modules
805
-            process_support_levels(plugin_info, categories, templates, output_dir, plugin_type)
806
-
807
-        return 0
808 1
deleted file mode 100644
809 2
deleted file mode 100644
... ...
@@ -1,100 +0,0 @@
1
-# Copyright: (c) 2019, Ansible Project
2
-# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
3
-
4
-# Make coding more python3-ish
5
-from __future__ import (absolute_import, division, print_function)
6
-__metaclass__ = type
7
-
8
-import re
9
-
10
-try:
11
-    from html import escape as html_escape
12
-except ImportError:
13
-    # Python-3.2 or later
14
-    import cgi
15
-
16
-    def html_escape(text, quote=True):
17
-        return cgi.escape(text, quote)
18
-
19
-from jinja2.runtime import Undefined
20
-
21
-from ansible.errors import AnsibleError
22
-from ansible.module_utils._text import to_text
23
-from ansible.module_utils.six import string_types
24
-
25
-
26
-_ITALIC = re.compile(r"I\(([^)]+)\)")
27
-_BOLD = re.compile(r"B\(([^)]+)\)")
28
-_MODULE = re.compile(r"M\(([^)]+)\)")
29
-_URL = re.compile(r"U\(([^)]+)\)")
30
-_LINK = re.compile(r"L\(([^)]+), *([^)]+)\)")
31
-_CONST = re.compile(r"C\(([^)]+)\)")
32
-_RULER = re.compile(r"HORIZONTALLINE")
33
-
34
-
35
-def html_ify(text):
36
-    ''' convert symbols like I(this is in italics) to valid HTML '''
37
-
38
-    if not isinstance(text, string_types):
39
-        text = to_text(text)
40
-
41
-    t = html_escape(text)
42
-    t = _ITALIC.sub(r"<em>\1</em>", t)
43
-    t = _BOLD.sub(r"<b>\1</b>", t)
44
-    t = _MODULE.sub(r"<span class='module'>\1</span>", t)
45
-    t = _URL.sub(r"<a href='\1'>\1</a>", t)
46
-    t = _LINK.sub(r"<a href='\2'>\1</a>", t)
47
-    t = _CONST.sub(r"<code>\1</code>", t)
48
-    t = _RULER.sub(r"<hr/>", t)
49
-
50
-    return t.strip()
51
-
52
-
53
-def documented_type(text):
54
-    ''' Convert any python type to a type for documentation '''
55
-
56
-    if isinstance(text, Undefined):
57
-        return '-'
58
-    if text == 'str':
59
-        return 'string'
60
-    if text == 'bool':
61
-        return 'boolean'
62
-    if text == 'int':
63
-        return 'integer'
64
-    if text == 'dict':
65
-        return 'dictionary'
66
-    return text
67
-
68
-
69
-# The max filter was added in Jinja2-2.10.  Until we can require that version, use this
70
-def do_max(seq):
71
-    return max(seq)
72
-
73
-
74
-def rst_ify(text):
75
-    ''' convert symbols like I(this is in italics) to valid restructured text '''
76
-
77
-    try:
78
-        t = _ITALIC.sub(r"*\1*", text)
79
-        t = _BOLD.sub(r"**\1**", t)
80
-        t = _MODULE.sub(r":ref:`\1 <\1_module>`", t)
81
-        t = _LINK.sub(r"`\1 <\2>`_", t)
82
-        t = _URL.sub(r"\1", t)
83
-        t = _CONST.sub(r"``\1``", t)
84
-        t = _RULER.sub(r"------------", t)
85
-    except Exception as e:
86
-        raise AnsibleError("Could not process (%s) : %s" % (text, e))
87
-
88
-    return t
89
-
90
-
91
-def rst_fmt(text, fmt):
92
-    ''' helper for Jinja2 to do format strings '''
93
-
94
-    return fmt % (text)
95
-
96
-
97
-def rst_xline(width, char="="):
98
-    ''' return a restructured text line of a given length '''
99
-
100
-    return char * width
... ...
@@ -11,7 +11,7 @@ import sys
11 11
 def main():
12 12
     base_dir = os.getcwd() + os.path.sep
13 13
     docs_dir = os.path.abspath('docs/docsite')
14
-    cmd = ['make', 'singlehtmldocs']
14
+    cmd = ['make', 'base_singlehtmldocs']
15 15
 
16 16
     sphinx = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=docs_dir)
17 17
     stdout, stderr = sphinx.communicate()
... ...
@@ -3,3 +3,4 @@ pyyaml
3 3
 sphinx
4 4
 sphinx-notfound-page
5 5
 straight.plugin
6
+antsibull
... ...
@@ -29,9 +29,6 @@ hacking/build_library/build_ansible/command_plugins/dump_keywords.py compile-3.5
29 29
 hacking/build_library/build_ansible/command_plugins/generate_man.py compile-2.6!skip # docs build only, 3.6+ required
30 30
 hacking/build_library/build_ansible/command_plugins/generate_man.py compile-2.7!skip # docs build only, 3.6+ required
31 31
 hacking/build_library/build_ansible/command_plugins/generate_man.py compile-3.5!skip # docs build only, 3.6+ required
32
-hacking/build_library/build_ansible/command_plugins/plugin_formatter.py compile-2.6!skip # docs build only, 3.6+ required
33
-hacking/build_library/build_ansible/command_plugins/plugin_formatter.py compile-2.7!skip # docs build only, 3.6+ required
34
-hacking/build_library/build_ansible/command_plugins/plugin_formatter.py compile-3.5!skip # docs build only, 3.6+ required
35 32
 hacking/build_library/build_ansible/command_plugins/porting_guide.py compile-2.6!skip # release process only, 3.6+ required
36 33
 hacking/build_library/build_ansible/command_plugins/porting_guide.py compile-2.7!skip # release process only, 3.6+ required
37 34
 hacking/build_library/build_ansible/command_plugins/porting_guide.py compile-3.5!skip # release process only, 3.6+ required