From 801b5dcd04459146feff30665e534130aaf94160 Mon Sep 17 00:00:00 2001 From: John R Barker Date: Mon, 5 Mar 2018 14:40:14 +0000 Subject: [PATCH] [WIP] Backport/2.5/multiple docs (#36907) Backport/2.5/multiple docs --- docs/bin/plugin_formatter.py | 27 +- .../_themes/sphinx_rtd_theme/breadcrumbs.html | 8 +- .../_themes/sphinx_rtd_theme/footer.html | 4 +- .../sphinx_rtd_theme/static/css/theme.css | 5 +- docs/docsite/_themes/srtd/breadcrumbs.html | 2 +- docs/docsite/_themes/srtd/footer.html | 4 +- .../docsite/_themes/srtd/static/css/theme.css | 2 + .../rst/community/development_process.rst | 58 ++++ docs/docsite/rst/index.rst | 2 +- docs/docsite/rst/network/index.rst | 2 + .../user_guide/network_best_practices_2.5.rst | 2 +- .../docsite/rst/scenario_guides/guide_aci.rst | 270 ++++++++++++------ .../rst/scenario_guides/guide_azure.rst | 4 +- .../rst/user_guide/intro_getting_started.rst | 2 +- .../rst/user_guide/playbooks_conditionals.rst | 27 +- .../rst/user_guide/playbooks_environment.rst | 2 + .../user_guide/playbooks_filters_ipaddr.rst | 2 +- .../rst/user_guide/playbooks_vault.rst | 2 + docs/docsite/rst/user_guide/vault.rst | 2 +- .../templates/list_of_CATEGORY_modules.rst.j2 | 5 + .../templates/list_of_CATEGORY_plugins.rst.j2 | 7 +- docs/templates/plugin.rst.j2 | 140 +++++---- 22 files changed, 371 insertions(+), 208 deletions(-) diff --git a/docs/bin/plugin_formatter.py b/docs/bin/plugin_formatter.py index 603290e5077..6223a202621 100755 --- a/docs/bin/plugin_formatter.py +++ b/docs/bin/plugin_formatter.py @@ -48,7 +48,7 @@ from jinja2 import Environment, FileSystemLoader from six import iteritems, string_types from ansible.errors import AnsibleError -from ansible.module_utils._text import to_bytes +from ansible.module_utils._text import to_bytes, to_text from ansible.plugins.loader import fragment_loader from ansible.utils import plugin_docs from ansible.utils.display import Display @@ -87,13 +87,13 @@ def rst_ify(text): ''' convert symbols like I(this is in italics) to valid restructured text ''' try: - t = _ITALIC.sub(r'*' + r"\1" + r"*", text) - t = _BOLD.sub(r'**' + r"\1" + r"**", t) - t = _MODULE.sub(r':ref:`module_docs/' + r"\1 <\1>" + r"`", t) + t = _ITALIC.sub(r"*\1*", text) + t = _BOLD.sub(r"**\1**", t) + t = _MODULE.sub(r":ref:`\1 <\1>`", t) t = _URL.sub(r"\1", t) - t = _CONST.sub(r'``' + r"\1" + r"``", t) + t = _CONST.sub(r"`\1`", t) except Exception as e: - raise AnsibleError("Could not process (%s) : %s" % (str(text), str(e))) + raise AnsibleError("Could not process (%s) : %s" % (text, e)) return t @@ -101,12 +101,15 @@ def rst_ify(text): def html_ify(text): ''' convert symbols like I(this is in italics) to valid HTML ''' + if not isinstance(text, string_types): + text = to_text(text) + t = html_escape(text) - t = _ITALIC.sub("" + r"\1" + "", t) - t = _BOLD.sub("" + r"\1" + "", t) - t = _MODULE.sub("" + r"\1" + "", t) - t = _URL.sub("" + r"\1" + "", t) - t = _CONST.sub("" + r"\1" + "", t) + t = _ITALIC.sub(r"\1", t) + t = _BOLD.sub(r"\1", t) + t = _MODULE.sub(r"\1", t) + t = _URL.sub(r"\1", t) + t = _CONST.sub(r"\1", t) return t @@ -240,6 +243,7 @@ def get_plugin_info(module_dir, limit_to=None, verbose=False): # save all the information module_info[module] = {'path': module_path, + 'source': os.path.relpath(module_path, module_dir), 'deprecated': deprecated, 'aliases': set(), 'metadata': metadata, @@ -413,6 +417,7 @@ def process_plugins(module_map, templates, outputname, output_dir, ansible_versi doc['option_keys'] = option_names doc['filename'] = fname + doc['source'] = module_map[module]['source'] doc['docuri'] = doc['module'].replace('_', '-') doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') doc['ansible_version'] = ansible_version diff --git a/docs/docsite/_themes/sphinx_rtd_theme/breadcrumbs.html b/docs/docsite/_themes/sphinx_rtd_theme/breadcrumbs.html index c052c0bde1d..3dfe7e75318 100644 --- a/docs/docsite/_themes/sphinx_rtd_theme/breadcrumbs.html +++ b/docs/docsite/_themes/sphinx_rtd_theme/breadcrumbs.html @@ -1,9 +1,13 @@ diff --git a/docs/docsite/_themes/sphinx_rtd_theme/footer.html b/docs/docsite/_themes/sphinx_rtd_theme/footer.html index 55ca16a8483..c7e1e0cb636 100644 --- a/docs/docsite/_themes/sphinx_rtd_theme/footer.html +++ b/docs/docsite/_themes/sphinx_rtd_theme/footer.html @@ -22,7 +22,7 @@

- Copyright © 2017 Red Hat, Inc. + Copyright © 2018 Red Hat, Inc.
{%- if last_updated %} @@ -30,6 +30,6 @@ {%- endif %}

-Ansible docs are generated from GitHub sources using Sphinx using a theme provided by Read the Docs. {% if pagename.endswith("_module") %}Module documentation is not edited directly, but is generated from the source code for the modules. To submit an update to module docs, edit the 'DOCUMENTATION' metadata in the modules directory of the core source code repository. {% endif %} +Ansible docs are generated from GitHub sources using Sphinx using a theme provided by Read the Docs.

diff --git a/docs/docsite/_themes/sphinx_rtd_theme/static/css/theme.css b/docs/docsite/_themes/sphinx_rtd_theme/static/css/theme.css index 1eaf1b7e4ce..8fd2708bb32 100644 --- a/docs/docsite/_themes/sphinx_rtd_theme/static/css/theme.css +++ b/docs/docsite/_themes/sphinx_rtd_theme/static/css/theme.css @@ -1686,7 +1686,10 @@ code.code-large,.rst-content tt.code-large{font-size:90%} .wy-nav-top a{color:#fff;font-weight:bold} .wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980B9;padding:5px;border-radius:100%} .wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit} -.wy-nav-content-wrap{margin-top: 30px;margin-left: 300px;width: calc(100% - 310px);float:left;background:#fcfcfc;min-height:100%} +@media screen and (max-width: 768px){.wy-nav-content-wrap{margin-top: 30px;margin-left: 300px;width: 100%;float:left;background:#fcfcfc;min-height:100%} +} +@media screen and (min-width: 768px){.wy-nav-content-wrap{margin-top: 30px;margin-left: 300px;width: calc(100% - 300px);float:left;background:#fcfcfc;min-height:100%} +} .wy-nav-content{padding:1.618em 3.236em;height:100%;margin:auto;overflow-x: hidden;overflow-y: scroll} .wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,0.2);display:none;z-index:499} .wy-body-mask.on{display:block} diff --git a/docs/docsite/_themes/srtd/breadcrumbs.html b/docs/docsite/_themes/srtd/breadcrumbs.html index c052c0bde1d..8cb48a6f40b 100644 --- a/docs/docsite/_themes/srtd/breadcrumbs.html +++ b/docs/docsite/_themes/srtd/breadcrumbs.html @@ -3,7 +3,7 @@
  • {{ title }}
  • {% if not pagename.endswith('_module') and (not 'list_of' in pagename) and (not 'category' in pagename) %}
  • - Edit on GitHub + Edit on GitHub
  • {% endif %} diff --git a/docs/docsite/_themes/srtd/footer.html b/docs/docsite/_themes/srtd/footer.html index 55ca16a8483..c7e1e0cb636 100644 --- a/docs/docsite/_themes/srtd/footer.html +++ b/docs/docsite/_themes/srtd/footer.html @@ -22,7 +22,7 @@

    - Copyright © 2017 Red Hat, Inc. + Copyright © 2018 Red Hat, Inc.
    {%- if last_updated %} @@ -30,6 +30,6 @@ {%- endif %}

    -Ansible docs are generated from GitHub sources using Sphinx using a theme provided by Read the Docs. {% if pagename.endswith("_module") %}Module documentation is not edited directly, but is generated from the source code for the modules. To submit an update to module docs, edit the 'DOCUMENTATION' metadata in the modules directory of the core source code repository. {% endif %} +Ansible docs are generated from GitHub sources using Sphinx using a theme provided by Read the Docs.

    diff --git a/docs/docsite/_themes/srtd/static/css/theme.css b/docs/docsite/_themes/srtd/static/css/theme.css index 1f02e313c3d..fc4d036c6f5 100644 --- a/docs/docsite/_themes/srtd/static/css/theme.css +++ b/docs/docsite/_themes/srtd/static/css/theme.css @@ -4916,6 +4916,8 @@ table { .documentation-table { border-right: 1px solid #000; border-bottom: 1px solid #000; + width: 100%; + display: table; } .caption { diff --git a/docs/docsite/rst/community/development_process.rst b/docs/docsite/rst/community/development_process.rst index 2fed1509c43..beeeb4e344f 100644 --- a/docs/docsite/rst/community/development_process.rst +++ b/docs/docsite/rst/community/development_process.rst @@ -23,6 +23,64 @@ Ansible accepts code via **pull requests** ("PRs" for short). GitHub provides a Because Ansible receives many pull requests, we use an automated process to help us through the process of reviewing and merging pull requests. That process is managed by **Ansibullbot**. +Backport Pull Request Process +----------------------------- + +After the pull request submitted to Ansible for the ``devel`` branch is +accepted and merged, the following instructions will help you create a +pull request to backport the change to a previous stable branch. + +.. note:: + + These instructions assume that ``stable-2.5`` is the targeted release + branch for the backport. + +.. note:: + + These instructions assume that ``https://github.com/ansible/ansible.git`` + is configured as a ``git remote`` named ``upstream``. If you do not use + a ``git remote`` named ``upstream``, adjust the instructions accordingly. + +.. note:: + + These instructions assume that ``https://github.com//ansible.git`` + is configured as a ``git remote`` named ``origin``. If you do not use + a ``git remote`` named ``origin``, adjust the instructions accordingly. + +#. Prepare your devel, stable, and feature branches: + + :: + + git fetch upstream + git checkout -b backport/2.5/[PR_NUMBER_FROM_DEVEL] upstream/stable-2.5 + +#. Cherry pick the relevant commit SHA from the devel branch into your feature + branch, handling merge conflicts as necessary: + + :: + + git cherry-pick -x [SHA_FROM_DEVEL] + +#. Add a changelog entry for the change, and commit it. + +#. Push your feature branch to your fork on GitHub: + + :: + + git push origin backport/2.5/[PR_NUMBER_FROM_DEVEL] + +#. Submit the pull request for ``backport/2.5/[PR_NUMBER_FROM_DEVEL]`` + against the ``stable-2.5`` branch + +.. note:: + + The choice to use ``backport/2.5/[PR_NUMBER_FROM_DEVEL]`` as the + name for the feature branch is somewhat arbitrary, but conveys meaning + about the purpose of that branch. It is not required to use this format, + but it can be helpful, especially when making multiple backport PRs for + multiple stable branches. + + Ansibullbot =========== diff --git a/docs/docsite/rst/index.rst b/docs/docsite/rst/index.rst index ddfbeb60541..24e9b575d7f 100644 --- a/docs/docsite/rst/index.rst +++ b/docs/docsite/rst/index.rst @@ -49,12 +49,12 @@ Ansible, Inc. releases a new major release of Ansible approximately every two mo :maxdepth: 2 :caption: Scenario Guides - scenario_guides/guide_aci scenario_guides/guide_aws scenario_guides/guide_azure scenario_guides/guide_rax scenario_guides/guide_gce scenario_guides/guide_cloudstack + scenario_guides/guide_aci scenario_guides/guide_vagrant scenario_guides/guide_docker scenario_guides/guide_packet diff --git a/docs/docsite/rst/network/index.rst b/docs/docsite/rst/network/index.rst index ac48bca62d9..7ced8eca9c2 100644 --- a/docs/docsite/rst/network/index.rst +++ b/docs/docsite/rst/network/index.rst @@ -1,3 +1,5 @@ +.. _network_guide: + ****************************** Ansible for Network Automation ****************************** diff --git a/docs/docsite/rst/network/user_guide/network_best_practices_2.5.rst b/docs/docsite/rst/network/user_guide/network_best_practices_2.5.rst index 66d2aa29854..001d87db294 100644 --- a/docs/docsite/rst/network/user_guide/network_best_practices_2.5.rst +++ b/docs/docsite/rst/network/user_guide/network_best_practices_2.5.rst @@ -235,7 +235,7 @@ Next, create a playbook file called ``facts-demo.yml`` containing the following: var: hostvars['vyos01.example.net'] - name: Write facts to disk using a template - copy: + copy: content: | #jinja2: lstrip_blocks: True EOS device info: diff --git a/docs/docsite/rst/scenario_guides/guide_aci.rst b/docs/docsite/rst/scenario_guides/guide_aci.rst index af93c0ee218..616c85c8cb7 100644 --- a/docs/docsite/rst/scenario_guides/guide_aci.rst +++ b/docs/docsite/rst/scenario_guides/guide_aci.rst @@ -1,7 +1,10 @@ -Getting started with Cisco ACI -============================== +.. _aci_guide: -.. _aci_intro: +Cisco ACI Guide +=============== + + +.. _aci_guide_intro: What is Cisco ACI ? ------------------- @@ -10,14 +13,16 @@ Application Centric Infrastructure (ACI) ........................................ The Cisco Application Centric Infrastructure (ACI) allows application requirements to define the network. This architecture simplifies, optimizes, and accelerates the entire application deployment life cycle. + Application Policy Infrastructure Controller (APIC) ................................................... -The Cisco Application Policy Infrastructure Controller (APIC) API enables applications to directly connect with a secure, shared, high-performance resource pool that includes network, compute, and storage capabilities. - The APIC manages the scalable ACI multi-tenant fabric. The APIC provides a unified point of automation and management, policy programming, application deployment, and health monitoring for the fabric. The APIC, which is implemented as a replicated synchronized clustered controller, optimizes performance, supports any application anywhere, and provides unified operation of the physical and virtual infrastructure. The APIC enables network administrators to easily define the optimal network for applications. Data center operators can clearly see how applications consume network resources, easily isolate and troubleshoot application and infrastructure problems, and monitor and profile resource usage patterns. +The Cisco Application Policy Infrastructure Controller (APIC) API enables applications to directly connect with a secure, shared, high-performance resource pool that includes network, compute, and storage capabilities. + + ACI Fabric .......... The Cisco Application Centric Infrastructure (ACI) Fabric includes Cisco Nexus 9000 Series switches with the APIC to run in the leaf/spine ACI fabric mode. These switches form a "fat-tree" network by connecting each leaf node to each spine node; all other devices connect to the leaf nodes. The APIC manages the ACI fabric. @@ -30,6 +35,7 @@ All the switch nodes contain a complete copy of the concrete model. When an admi The APIC is responsible for fabric activation, switch firmware management, network policy configuration, and instantiation. While the APIC acts as the centralized policy and network management engine for the fabric, it is completely removed from the data path, including the forwarding topology. Therefore, the fabric can still forward traffic even when communication with the APIC is lost. + More information ................ Various resources exist to start learning ACI, here is a list of interesting articles from the community. @@ -39,6 +45,8 @@ Various resources exist to start learning ACI, here is a list of interesting art - `Cisco DevNet Learning Labs about ACI `_ +.. _aci_guide_modules: + Using the ACI modules --------------------- The Ansible ACI modules provide a user-friendly interface to managing your ACI environment using Ansible playbooks. @@ -57,34 +65,99 @@ For instance ensuring that a specific tenant exists, is done using the following description: Customer XYZ state: present -A complete list of existing ACI modules is available for `the latest stable release `_ as well as `the current development version `_. +A complete list of existing ACI modules is available for `the latest stable release `_ as well as `the current development version `_. + -Standard module parameters -.......................... +Common parameters +................. Every Ansible ACI module accepts the following parameters that influence the module's communication with the APIC REST API: -- ``host`` -- Hostname or IP address of the APIC -- ``port`` -- Port to use for communication (defaults to ``443`` for HTTPS, and ``80`` for HTTP) -- ``username`` -- User name used to log on to the APIC (defaults to ``admin``) -- ``password`` -- Password for ``username`` to log on to the APIC (using password-based authentication) -- ``private_key`` -- Private key for ``username`` to log on to APIC (using signature-based authentication) -- ``certificate_name`` -- Name of the certificate in the ACI Web GUI (defaults to ``private_key`` file base name) -- ``timeout`` -- Timeout value for socket-level communication -- ``use_proxy`` -- Use system proxy settings (defaults to ``yes``) -- ``use_ssl`` -- Use HTTPS or HTTP for APIC REST communication (defaults to ``yes``) -- ``validate_certs`` -- Validate certificate when using HTTPS communication (defaults to ``yes``) -- ``output_level`` -- Influence the level of detail ACI modules return to the user (one of ``normal``, ``info`` or ``debug``) - -Module return values -.................... -By default the ACI modules (excluding :ref:`aci_rest `) return the resulting state of the managed object in a key ``current``. + host + Hostname or IP address of the APIC. + + port + Port to use for communication. (Defaults to ``443`` for HTTPS, and ``80`` for HTTP) + + username + User name used to log on to the APIC. (Defaults to ``admin``) + + password + Password for ``username`` to log on to the APIC, using password-based authentication. + + private_key + Private key for ``username`` to log on to APIC, using signature-based authentication. *New in version 2.5* + + certificate_name + Name of the certificate in the ACI Web GUI. (Defaults to ``private_key`` file base name) *New in version 2.5* + + timeout + Timeout value for socket-level communication. + + use_proxy + Use system proxy settings. (Defaults to ``yes``) + + use_ssl + Use HTTPS or HTTP for APIC REST communication. (Defaults to ``yes``) + + validate_certs + Validate certificate when using HTTPS communication. (Defaults to ``yes``) + + output_level + Influence the level of detail ACI modules return to the user. (One of ``normal``, ``info`` or ``debug``) *New in version 2.5* + + +Proxy support +............. +By default, if an environment variable ``_proxy`` is set on the target host, requests will be sent through that proxy. This behaviour can be overridden by setting a variable for this task (see :ref:`playbooks_environment`), or by using the ``use_proxy`` module parameter. + +HTTP redirects can redirect from HTTP to HTTPS so you should be sure that your proxy environment for both protocols is correct. + +If you don't need proxy support, but the system may have it configured nevertheless, you can add this parameter setting: ``use_proxy: no`` to avoid accidental proxy usage. + +.. hint:: Selective proxy support using the ``no_proxy`` environment variable is also supported. + + +Return values +............. + +.. versionadded:: 2.5 + +The following values are always returned: + + current + The resulting state of the managed object. + +The following values are returned when ``output_level: info``: -By increasing the ``output_level`` to ``info``, the modules give access to the ``previous`` state of the object, but also the ``proposed`` and ``sent`` configuration payload. + previous + The original state of the managed object (before any change was made). -For troubleshooting purposes setting ``output_level: debug`` or defining environment variable ``ANSIBLE_DEBUG=1`` enables more detailed information on the actual APIC REST communication, incl. ``filter_string``, ``method``, ``response``, ``status`` and ``url``. + proposed + The proposed config payload, based on user-supplied values. + + sent + The sent config payload, based on user-supplied values and the existing configuration. + +The following values are returned when ``output_level: debug`` or ``ANSIBLE_DEBUG=1``: + + filter_string + The filter used for specific APIC queries. + + method + The HTTP method used for the sent payload. (Either ``GET`` for queries, ``DELETE`` or ``POST`` for changes) + + response + The HTTP response from the APIC. + + status + The HTTP status code for the request. + + url + The url used for the request. .. note:: The module return values are documented in detail as part of each module's documentation. + More information ................ Various resources exist to start learn more about ACI programmability, we recommend the following links: @@ -93,7 +166,7 @@ Various resources exist to start learn more about ACI programmability, we recomm - `Cisco DevNet Learning Labs about ACI and Ansible `_ -.. _aci_auth: +.. _aci_guide_auth: ACI authentication ------------------ @@ -113,12 +186,14 @@ Password-based authentication is very simple to work with, but it is not the mos .. warning:: Never store passwords in plain text. -The "Vault" feature of Ansible allows you to keep sensitive data such as passwords or keys in encrypted files, rather than as plain text in your playbooks or roles. These vault files can then be distributed or placed in source control. See :doc:`playbooks_vault` for more information. - +The "Vault" feature of Ansible allows you to keep sensitive data such as passwords or keys in encrypted files, rather than as plain text in your playbooks or roles. These vault files can then be distributed or placed in source control. See :ref:`playbooks_vault` for more information. Signature-based authentication using certificates ................................................. + +.. versionadded:: 2.5 + Using signature-based authentication is more efficient and more reliable than password-based authentication. Generate certificate and private key @@ -171,14 +246,14 @@ You need the following parameters with your ACI module(s) for it to work: private_key: pki/admin.key certificate_name: admin # This could be left out ! -.. note:: If you use a certificate name in ACI that matches the private key's basename, you can leave out the ``certificate_name`` parameter like the example above. +.. hint:: If you use a certificate name in ACI that matches the private key's basename, you can leave out the ``certificate_name`` parameter like the example above. More information ,,,,,,,,,,,,,,,, -More information about Signature-based Authentication is available from `Cisco APIC Signature-Based Transactions `_. +Detailed information about Signature-based Authentication is available from `Cisco APIC Signature-Based Transactions `_. -.. _aci_rest: +.. _aci_guide_rest: Using ACI REST with Ansible --------------------------- @@ -186,15 +261,21 @@ While already a lot of ACI modules exists in the Ansible distribution, and the m The :ref:`aci_rest ` module provides you with direct access to the APIC REST API and enables you to perform any task not already covered by the existing modules. This may seem like a complex undertaking, but you can generate the needed REST payload for any action performed in the ACI web interface effortlessly. -Using the aci-rest module + +Built-in idempotency +.................... +Because the APIC REST API is intrinsically idempotent and can report whether a change was made, the :ref:`aci_rest ` module automatically inherits both capabilities and is a first-class solution for automating your ACI infrastructure. As a result, users that require more powerful low-level access to their ACI infrastructure don't have to give up on idempotency and don't have to guess whether a change was performed when using the :ref:`aci_rest ` module. + + +Using the aci_rest module ......................... -The :ref:`aci_rest ` module accepts the native XML and JSON payloads, but additionally accepts inline YAML payload (structured like JSON). The XML payload requires you to use a path ending with ``.xml`` whereas JSON or YAML require path to end with ``.json``. +The :ref:`aci_rest ` module accepts the native XML and JSON payloads, but additionally accepts inline YAML payload (structured like JSON). The XML payload requires you to use a path ending with ``.xml`` whereas JSON or YAML require the path to end with ``.json``. When you're making modifications, you can use the POST or DELETE methods, whereas doing just queries require the GET method. -For instance, if you would like to ensure a specific tenant exists on ACI, these below four examples are identical: +For instance, if you would like to ensure a specific tenant exists on ACI, these below four examples are functionally identical: -**XML** (Native ACI) +**XML** (Native ACI REST) .. code-block:: yaml @@ -207,7 +288,7 @@ For instance, if you would like to ensure a specific tenant exists on ACI, these content: | -**JSON** (Native ACI) +**JSON** (Native ACI REST) .. code-block:: yaml @@ -227,7 +308,7 @@ For instance, if you would like to ensure a specific tenant exists on ACI, these } } -**YAML** (Ansible-style) +**YAML** (Ansible-style REST) .. code-block:: yaml @@ -255,21 +336,28 @@ For instance, if you would like to ensure a specific tenant exists on ACI, these description: Customer XYZ state: present + +.. hint:: The XML format is more practical when there is a need to template the REST payload (inline), but the YAML format is more convenient for maintaing your infrastructure-as-code and feels more naturely integrated with Ansible playbooks. The dedicated modules offer a more simple, abstracted, but also a more limited experience. Use what feels best for your use-case. + + More information ................ Plenty of resources exist to learn about ACI's APIC REST interface, we recommend the links below: -- `The apic_rest Ansible module `_ -- `APIC REST API Configuration Guide `_ +- :ref:`The aci_rest module documentation ` +- `APIC REST API Configuration Guide `_ -- Detailed guide on how the APIC REST API is designed and used, incl. many examples +- `APIC Management Information Model reference `_ -- Complete reference of the APIC object model - `Cisco DevNet Learning Labs about ACI and REST `_ -.. _aci_ops: +.. _aci_guide_ops: Operational examples -------------------- Here is a small overview of useful operational tasks to reuse in your playbooks. -Feel free to contribute more snippets that are useful to others. + +Feel free to contribute more useful snippets. + Waiting for all controllers to be ready ....................................... @@ -280,16 +368,15 @@ You can use the below task after you started to build your APICs and configured - name: Waiting for all controllers to be ready aci_rest: host: '{{ apic_ip }}' - username: '{{ apic_username }}' private_key: pki/admin.key method: get path: /api/node/class/topSystem.json?query-target-filter=eq(topSystem.role,"controller") - changed_when: no - register: aci_ready - until: aci_ready|success and aci_ready.totalCount|int >= groups['apic']|count + register: topsystem + until: topsystem|success and topsystem.totalCount|int >= groups['apic']|count >= 3 retries: 20 delay: 30 + Waiting for cluster to be fully-fit ................................... The below example waits until the cluster is fully-fit. In this example you know the number of APICs in the cluster and you verify each APIC reports a 'fully-fit' status. @@ -299,90 +386,91 @@ The below example waits until the cluster is fully-fit. In this example you know - name: Waiting for cluster to be fully-fit aci_rest: host: '{{ apic_ip }}' - username: '{{ apic_username }}' private_key: pki/admin.key method: get path: /api/node/class/infraWiNode.json?query-target-filter=wcard(infraWiNode.dn,"topology/pod-1/node-1/av") - changed_when: no - register: aci_fit + register: infrawinode until: > - aci_fit|success and - aci_fit.totalCount|int >= groups['apic']|count >= 3 and - aci_fit.imdata[0].infraWiNode.attributes.health == 'fully-fit' and - aci_fit.imdata[1].infraWiNode.attributes.health == 'fully-fit' and - aci_fit.imdata[2].infraWiNode.attributes.health == 'fully-fit' - # all(apic.infraWiNode.attributes.health == 'fully-fit' for apic in aci_fit.imdata) + infrawinode|success and + infrawinode.totalCount|int >= groups['apic']|count >= 3 and + infrawinode.imdata[0].infraWiNode.attributes.health == 'fully-fit' and + infrawinode.imdata[1].infraWiNode.attributes.health == 'fully-fit' and + infrawinode.imdata[2].infraWiNode.attributes.health == 'fully-fit' + # all(apic.infraWiNode.attributes.health == 'fully-fit' for apic in infrawinode.imdata) retries: 30 delay: 30 -.. _aci_errors: +.. _aci_guide_errors: APIC error messages ------------------- -The following error messages may occur and this section can help you understand what exactly is going on. +The following error messages may occur and this section can help you understand what exactly is going on and how to fix/avoid them. -- **APIC Error 122: unknown managed object class 'polUni'** + APIC Error 122: unknown managed object class 'polUni' + In case you receive this error while you are certain your :ref:`aci_rest ` payload and object classes are seemingly correct, the issue might be that your payload is not in fact correct JSON (e.g. the sent payload is using single quotes, rather than double quotes), and as a result the APIC is not correctly parsing your object classes from the payload. One way to avoid this is by using a YAML or an XML formatted payload, which are easier to construct correctly and modify later. - In case you receive this error while you are certain your :ref:`aci_rest ` payload and object classes are seemingly correct, the issue might be that your payload is not in fact correct JSON (e.g. the sent payload is using single quotes, rather than double quotes), and as a result the APIC is not correctly parsing your object classes from the payload. One way to avoid this is by using a YAML or an XML formatted payload. + APIC Error 400: invalid data at line '1'. Attributes are missing, tag 'attributes' must be specified first, before any other tag + Although the JSON specification allows unordered elements, the APIC REST API requires that the JSON ``attributes`` element precede the ``children`` array or other elements. So you need to ensure that your payload conforms to this requirement. Sorting your dictionary keys will do the trick just fine. If you don't have any attributes, it may be necessary to add: ``attributes: {}`` as the APIC does expect the entry to precede any ``children``. -- **APIC Error 400: invalid data at line '1'. Attributes are missing, tag 'attributes' must be specified first, before any other tag** - While JSON does not care about the order of dictionary keys, the APIC is very strict in accepting only ``attributes`` before ``children``. So you need to ensure that your payload conforms to this requirement. Sorting your dictionary keys will do the trick just fine. + APIC Error 801: property descr of uni/tn-TENANT/ap-AP failed validation for value 'A "legacy" network' + Some values in the APIC have strict format-rules to comply to, and the internal APIC validation check for the provided value failed. In the above case, the ``description`` parameter (internally known as ``descr``) only accepts values conforming to `Regex: [a-zA-Z0-9\\!#$%()*,-./:;@ _{|}~?&+]+ `_, in general it must not include quotes or square brackets. -- **APIC Error 801: property descr of uni/tn-TENANT/ap-AP failed validation for value 'A "legacy" network'** - - Some values in the APIC have strict format-rules to comply to, and the internal APIC validation check for the provided value failed. In the above case, the ``description`` parameter (internally known as ``descr``) only accepts values conforming to `Regex: [a-zA-Z0-9\\!#$%()*,-./:;@ _{|}~?&+]+ `_ so it must not include quotes. - - -.. _aci_issues: +.. _aci_guide_known_issues: Known issues ------------ -The :ref:`aci_rest ` module is a wrapper around the APIC REST API. As a result any issues related to the APIC will be reflected in the use of the :ref:`aci_rest ` module. - -All below issues either have been reported to the vendor, or can simply be avoided. +The :ref:`aci_rest ` module is a wrapper around the APIC REST API. As a result any issues related to the APIC will be reflected in the use of this module. -- **Too many consecutive API calls may result in connection throttling** +All below issues either have been reported to the vendor, and most can simply be avoided. - Starting with ACI v3.1 the APIC will actively throttle password-based authenticated connection rates over a specific treshold. This is as part of an anti-DDOS measure but can act up when using Ansible with ACI using password-based authentication. Currently, one solution is to increase this treshold within the nginx configuration, but using signature-based authentication is recommended. + Too many consecutive API calls may result in connection throttling + Starting with ACI v3.1 the APIC will actively throttle password-based authenticated connection rates over a specific treshold. This is as part of an anti-DDOS measure but can act up when using Ansible with ACI using password-based authentication. Currently, one solution is to increase this treshold within the nginx configuration, but using signature-based authentication is recommended. - **NOTE:** It is advisable to use signature-based authentication with ACI as it not only prevents connection-throttling, but also improves general performance when using the ACI modules. + **NOTE:** It is advisable to use signature-based authentication with ACI as it not only prevents connection-throttling, but also improves general performance when using the ACI modules. -- **Specific requests may not reflect changes correctly** + Specific requests may not reflect changes correctly (`#35401 `_) + There is a known issue where specific requests to the APIC do not properly reflect changed in the resulting output, even when we request those changes explicitly from the APIC. In one instance using the path ``api/node/mo/uni/infra.xml`` fails, where ``api/node/mo/uni/infra/.xml`` does work correctly. - There is a known issue where specific requests to the APIC do not properly reflect changed in the resulting output, even when we request those changes explicitly from the APIC. In one instance using the path ``api/node/mo/uni/infra.xml`` fails, where ``api/node/mo/uni/infra/.xml`` does work correctly. + **NOTE:** A workaround is to register the task return values (e.g. ``register: this``) and influence when the task should report a change by adding: ``changed_when: this.imdata != []``. - More information from: `#35401 aci_rest: change not detected `_ - **NOTE:** Fortunately the behaviour is consistent, so if you have a working example you can trust that it will keep on working. + Specific requests are known to not be idempotent (`#35050 `_) + The behaviour of the APIC is inconsistent to the use of ``status="created"`` and ``status="deleted"``. The result is that when you use ``status="created"`` in your payload the resulting tasks are not idempotent and creation will fail when the object was already created. However this is not the case with ``status="deleted"`` where such call to an non-existing object does not cause any failure whatsoever. + **NOTE:** A workaround is to avoid using ``status="created"`` and instead use ``status="modified"`` when idempotency is essential to your workflow.. -- **Specific requests are known to not be idempotent** - The behaviour of the APIC is inconsistent to the use of ``status="created"`` and ``status="deleted"``. The result is that when you use ``status="created"`` in your payload the resulting tasks are not idempotent and creation will fail when the object was already created. However this is not the case with ``status="deleted"`` where such call to an non-existing object does not cause any failure whatsoever. + Setting user password is not idempotent (`#35544 `_) + Due to an inconsistency in the APIC REST API, a task that sets the password of a locally-authenticated user is not idempotent. The APIC will complain with message ``Password history check: user dag should not use previous 5 passwords``. - More information from: `#35050 aci_rest: Using status="created" behaves differently than status="deleted" `_ + **NOTE:** There is no workaround for this issue. - **NOTE:** A workaround is to avoid using ``status="created"`` and instead use ``status="modified"`` when idempotency is essential to your workflow.. - -- **Setting user password is not idempotent** - - Due to an inconsistency in the APIC REST API, a task that sets the password of a locally-authenticated user is not idempotent. The APIC will complain with message ``Password history check: user dag should not use previous 5 passwords``. - - More information from: `#35544 aci_aaa_user: Setting user password is not idempotent `_ - - **NOTE:** There is no workaround for this issue. - - -.. _aci_community: +.. _aci_guide_community: ACI Ansible community --------------------- If you have specific issues with the ACI modules, or a feature request, or you like to contribute to the ACI project by proposing changes or documentation updates, look at the Ansible Community wiki ACI page at: https://github.com/ansible/community/wiki/Network:-ACI You will find our roadmap, an overview of open ACI issues and pull-requests and more information about who we are. If you have an interest in using ACI with Ansible, feel free to join ! We occasionally meet online to track progress and prepare for new Ansible releases. + + +.. seealso:: + + :ref:`network_guide` + A detailed guide on how to use Ansible for automating network infrastructure. + :ref:`List of ACI modules ` + A complete list of supported ACI modules. + `ACI community `_ + The Ansible ACI community wiki page, includes roadmap, ideas and development documentation. + `Network Working Group `_ + The Ansible Network community page, includes contact information and meeting information. + `#ansible-network `_ + The #ansible-network IRC chat channel on Freenode.net. + `User Mailing List `_ + Have a question? Stop by the google group! diff --git a/docs/docsite/rst/scenario_guides/guide_azure.rst b/docs/docsite/rst/scenario_guides/guide_azure.rst index 3c06ad5b364..5e1e8466261 100644 --- a/docs/docsite/rst/scenario_guides/guide_azure.rst +++ b/docs/docsite/rst/scenario_guides/guide_azure.rst @@ -1,5 +1,5 @@ -Getting Started with Azure -========================== +Microsoft Azure Guide +===================== Ansible includes a suite of modules for interacting with Azure Resource Manager, giving you the tools to easily create and orchestrate infrastructure on the Microsoft Azure Cloud. diff --git a/docs/docsite/rst/user_guide/intro_getting_started.rst b/docs/docsite/rst/user_guide/intro_getting_started.rst index fc75260ea0a..87a6a7a002c 100644 --- a/docs/docsite/rst/user_guide/intro_getting_started.rst +++ b/docs/docsite/rst/user_guide/intro_getting_started.rst @@ -8,7 +8,7 @@ Getting Started Foreword ```````` -Now that you've read :doc:`../installation-guide/intro_installation` and installed Ansible, it's time to get +Now that you've read :doc:`../installation_guide/intro_installation` and installed Ansible, it's time to get started with some ad-hoc commands. What we are showing first are not the powerful configuration/deployment/orchestration features of Ansible. diff --git a/docs/docsite/rst/user_guide/playbooks_conditionals.rst b/docs/docsite/rst/user_guide/playbooks_conditionals.rst index ed567735219..f0264e1a2d2 100644 --- a/docs/docsite/rst/user_guide/playbooks_conditionals.rst +++ b/docs/docsite/rst/user_guide/playbooks_conditionals.rst @@ -220,30 +220,19 @@ but it is easily handled with a minimum of syntax in an Ansible Playbook:: As a reminder, the various YAML files contain just keys and values:: --- - # for vars/CentOS.yml + # for vars/RedHat.yml apache: httpd somethingelse: 42 -How does this work? If the operating system was 'CentOS', the first file Ansible would try to import -would be 'vars/CentOS.yml', followed by '/vars/os_defaults.yml' if that file -did not exist. If no files in the list were found, an error would be raised. -On Debian, it would instead first look towards 'vars/Debian.yml' instead of 'vars/CentOS.yml', before -falling back on 'vars/os_defaults.yml'. Pretty simple. +How does this work? For Red Hat operating systems ('CentOS', for example), the first file Ansible tries to import +is 'vars/RedHat.yml'. If that file does not exist, Ansible attempts to load 'vars/os_defaults.yml'. If no files in +the list were found, an error is raised. -To use this conditional import feature, you'll need facter or ohai installed prior to running the playbook, but -you can of course push this out with Ansible if you like:: +On Debian, Ansible first looks for 'vars/Debian.yml' instead of 'vars/RedHat.yml', before +falling back on 'vars/os_defaults.yml'. - # for facter - ansible -m yum -a "pkg=facter state=present" - ansible -m yum -a "pkg=ruby-json state=present" - - # for ohai - ansible -m yum -a "pkg=ohai state=present" - -Ansible's approach to configuration -- separating variables from tasks, keeps your playbooks -from turning into arbitrary code with ugly nested ifs, conditionals, and so on - and results -in more streamlined & auditable configuration rules -- especially because there are a -minimum of decision points to track. +Ansible's approach to configuration -- separating variables from tasks, keeping your playbooks +from turning into arbitrary code with nested conditionals - results in more streamlined and auditable configuration rules because there are fewer decision points to track. Selecting Files And Templates Based On Variables ```````````````````````````````````````````````` diff --git a/docs/docsite/rst/user_guide/playbooks_environment.rst b/docs/docsite/rst/user_guide/playbooks_environment.rst index 0dba163869e..f4d6ca83710 100644 --- a/docs/docsite/rst/user_guide/playbooks_environment.rst +++ b/docs/docsite/rst/user_guide/playbooks_environment.rst @@ -1,3 +1,5 @@ +.. _playbooks_environment: + Setting the Environment (and Working With Proxies) ================================================== diff --git a/docs/docsite/rst/user_guide/playbooks_filters_ipaddr.rst b/docs/docsite/rst/user_guide/playbooks_filters_ipaddr.rst index 89c0de9b7ae..081ec70094b 100644 --- a/docs/docsite/rst/user_guide/playbooks_filters_ipaddr.rst +++ b/docs/docsite/rst/user_guide/playbooks_filters_ipaddr.rst @@ -239,7 +239,7 @@ Here's an example set of two host prefixes (with some "control" values):: First, let's make sure that we only work with correct host/prefix values, not just subnets or single IP addresses:: - # {{ test_list | ipaddr('host/prefix') }} + # {{ host_prefix | ipaddr('host/prefix') }} ['2001:db8:deaf:be11::ef3/64', '192.0.2.48/24'] In Debian-based systems, network configuration stored in ``/etc/network/interfaces`` file uses combination of IP address, network address, netmask and broadcast address to configure IPv4 network interface. We can get these values from a single 'host/prefix' combination: diff --git a/docs/docsite/rst/user_guide/playbooks_vault.rst b/docs/docsite/rst/user_guide/playbooks_vault.rst index 1cdc9867816..228241b9206 100644 --- a/docs/docsite/rst/user_guide/playbooks_vault.rst +++ b/docs/docsite/rst/user_guide/playbooks_vault.rst @@ -1,3 +1,5 @@ +.. _playbooks_vault: + Using Vault in playbooks ======================== diff --git a/docs/docsite/rst/user_guide/vault.rst b/docs/docsite/rst/user_guide/vault.rst index 079fb935544..3841b9cfab6 100644 --- a/docs/docsite/rst/user_guide/vault.rst +++ b/docs/docsite/rst/user_guide/vault.rst @@ -179,7 +179,7 @@ Output:: Reading plaintext input from stdin. (ctrl-d to end input) -User enters 'hunter42' and hits ctrl-d. +User enters 'hunter2' and hits ctrl-d. Result:: diff --git a/docs/templates/list_of_CATEGORY_modules.rst.j2 b/docs/templates/list_of_CATEGORY_modules.rst.j2 index 8ce6c5d43e2..301449e2e7b 100644 --- a/docs/templates/list_of_CATEGORY_modules.rst.j2 +++ b/docs/templates/list_of_CATEGORY_modules.rst.j2 @@ -1,3 +1,5 @@ +.. _@{ title.lower() + '_' + plugin_type + 's' }@: + @{ title }@ @{ plugin_type + 's' }@ @{ '`' * title | length }@```````` @@ -14,6 +16,9 @@ {% endif %} {% for name, info in subcategories.items() | sort %} + +.. _@{ name.lower() + '_' + title.lower() + '_' + plugin_type + 's' }@: + @{ name.title() }@ @{ '-' * name | length }@ diff --git a/docs/templates/list_of_CATEGORY_plugins.rst.j2 b/docs/templates/list_of_CATEGORY_plugins.rst.j2 index 27aaea09917..fba836c68b6 100644 --- a/docs/templates/list_of_CATEGORY_plugins.rst.j2 +++ b/docs/templates/list_of_CATEGORY_plugins.rst.j2 @@ -1,3 +1,5 @@ +.. _@{ title.lower() + '_' + plugin_type + 's' }@: + @{ title }@ @{ plugin_type }@ @{ '`' * title | length }@```````` @@ -14,13 +16,16 @@ {% endif %} {% for name, info in subcategories.items() | sort %} + +.. _@{ name.lower() + '_' + title.lower() + '_' + plugin_type + 's' }@: + @{ name.title() }@ @{ '-' * name | length }@ .. toctree:: :maxdepth: 1 {% for module in info['_modules'] | sort %} - :ref:`@{ module }@`{% if module_info[module]['deprecated'] %} **(D)**{% endif%} @{ module_info[module]['doc']['short_description'] }@ + :ref:`@{ module }@`{% if module_info[module]['deprecated'] %} **(D)**{% endif%} -- @{ module_info[module]['doc']['short_description'] }@ {% endfor %} {% endfor %} diff --git a/docs/templates/plugin.rst.j2 b/docs/templates/plugin.rst.j2 index 8f25b26bdc2..e3c07604d6c 100644 --- a/docs/templates/plugin.rst.j2 +++ b/docs/templates/plugin.rst.j2 @@ -1,14 +1,15 @@ +:source: @{ source }@ + .. _@{ module }@: {% if short_description %} -{% set title = module + ' - ' + short_description|convert_symbols_to_format %} +{% set title = module + ' - ' + short_description|convert_symbols_to_format %} {% else %} -{% set title = module %} +{% set title = module %} {% endif %} -{% set title_len = title|length %} @{ title }@ -@{ '+' * title_len }@ +@{ '+' * title|length }@ {% if version_added is defined and version_added != '' -%} .. versionadded:: @{ version_added | default('') }@ @@ -46,13 +47,13 @@ Synopsis {% if description %} -{% if description is string -%} +{% if description is string -%} * @{ description | convert_symbols_to_format }@ -{% else %} -{% for desc in description -%} +{% else %} +{% for desc in description -%} * @{ desc | convert_symbols_to_format }@ -{% endfor %} -{% endif %} +{% endfor %} +{% endif %} {% endif %} @@ -64,17 +65,16 @@ Aliases: @{ ','.join(aliases) }@ {% endif %} {% if requirements %} -{% set req = 'Requirements' %} -{% if plugin_type == 'module' %} -{% set req = req + ' (on host that executes module)' %} -{% endif %} -{% set req_len = req|length %} -@{ req }@ -@{ '-' * req_len }@ +{% set req_title = 'Requirements' %} +{% if plugin_type == 'module' %} +{% set req_title = req_title + ' (on host that executes module)' %} +{% endif %} +@{ req_title }@ +@{ '-' * req_title|length }@ -{% for req in requirements %} - * @{ req | convert_symbols_to_format }@ -{% endfor %} +{% for req in requirements %} +* @{ req | convert_symbols_to_format }@ +{% endfor %} {% endif %} @@ -89,14 +89,13 @@ Options {# Header of the documentation #} - - - - + + + {% if plugin_type != 'module' %} - + {% endif %} - + {% for key, value in options|dictsort recursive %} @@ -108,14 +107,14 @@ Options {% endfor %}
    - @{ key }@
    {% if value.version_added %} (added in @{value.version_added}@){% endif %}
    + @{ key }@ + {% if value.get('required', False) %}
    required
    {% endif %} + {% if value.version_added %}
    (added in @{value.version_added}@)
    {% endif %}
    - {# required #} -
    {# default value #} - + {# choices #}
    parameter
    required
    default
    choices
    Parameter
    Default
    Choices
    configuration
    Configuration
    comments
    Comments
    {% if value.get('required', False) %}yes{% else %}no{% endif %}
    {% if value.default %}@{ value.default }@{% endif %}
    {% if value.default %}@{ value.default | html_ify }@{% endif %}
    @@ -186,6 +185,19 @@ Options

    +{% endif %} +{% if notes -%} + + +Notes +----- + +.. note:: +{% for note in notes %} + - @{ note | convert_symbols_to_format }@ +{% endfor %} + + {% endif %} {% if examples or plainexamples -%} @@ -195,11 +207,11 @@ Examples .. code-block:: yaml -{% for example in examples %} -{% if example['description'] %}@{ example['description'] | indent(4, True) }@{% endif %} +{% for example in examples %} +{% if example['description'] %}@{ example['description'] | indent(4, True) }@{% endif %} @{ example['code'] | escape | indent(4, True) }@ -{% endfor %} -{% if plainexamples %}@{ plainexamples | indent(4, True) }@{% endif %} +{% endfor %} +{% if plainexamples %}@{ plainexamples | indent(4, True) }@{% endif %} {% endif %} @@ -209,17 +221,17 @@ Examples Return Values ------------- -Common return values are documented :ref:`here `, the following are the fields unique to this {{plugin_type}}: +Common return values are documented :ref:`here `, the following are the fields unique to this @{ plugin_type }@: .. raw:: html - - - - - + + + + + {% for key, value in returndocs|dictsort recursive %} @@ -230,7 +242,7 @@ Common return values are documented :ref:`here `, the foll {% endfor %}
    - @{ key }@ + @{ key }@
    @@ -243,9 +255,9 @@ Common return values are documented :ref:`here `, the foll {% endfor %} {% endif %} -
    - - + + + {# --------------------------------------------------------- # sadly we cannot blindly iterate through the child dicts, @@ -265,48 +277,35 @@ Common return values are documented :ref:`here `, the foll {% endif %} -{% if notes -%} - - -Notes ------ - -.. note:: -{% for note in notes %} - - @{ note | convert_symbols_to_format }@ -{% endfor %} - - -{% endif %} {% if author is defined -%} Author ~~~~~~ -{% for author_name in author %} - * @{ author_name }@ -{% endfor %} +{% for author_name in author %} +* @{ author_name }@ +{% endfor %} {% endif %} {% if not deprecated %} -{% set support = { 'core': 'The Ansible Core Team', 'network': 'The Ansible Network Team', 'certified': 'an Ansible Partner', 'community': 'The Ansible Community', 'curated': 'A Third Party'} %} -{% set module_states = { 'preview': 'it is not guaranteed to have a backwards compatible interface', 'stableinterface': 'the maintainers for this module guarantee that no backward incompatible interface changes will be made'} %} -{% if metadata %} -{% if metadata.status %} +{% set support = { 'core': 'The Ansible Core Team', 'network': 'The Ansible Network Team', 'certified': 'an Ansible Partner', 'community': 'The Ansible Community', 'curated': 'A Third Party'} %} +{% set module_states = { 'preview': 'it is not guaranteed to have a backwards compatible interface', 'stableinterface': 'the maintainers for this module guarantee that no backward incompatible interface changes will be made'} %} +{% if metadata %} +{% if metadata.status %} Status ~~~~~~ -{% for cur_state in metadata.status %} +{% for cur_state in metadata.status %} This module is flagged as **@{cur_state}@** which means that @{module_states[cur_state]}@. -{% endfor %} +{% endfor %} -{% endif %} -{% if metadata.supported_by in ('core', 'network') %} +{% endif %} +{% if metadata.supported_by in ('core', 'network') %} Maintenance Info @@ -315,9 +314,8 @@ Maintenance Info For more information about Red Hat's support of this @{ plugin_type }@, please refer to this `Knowledge Base article `_ -{% endif %} -{% endif %} +{% endif %} +{% endif %} {% endif %} -If you want to help with development, please read :doc:`../../community/index`, -:doc:`../../dev_guide/testing` and {% if plugin_type == 'module' %}:doc:`../../dev_guide/developing_modules`{% else %}:doc:`../../dev_guide/developing_plugins`{% endif %}. +If you notice any issues in this documentation you can `edit this document `_ to improve it.
    name
    description
    returned
    type
    sample
    Name
    Description
    Returned
    Type
    Sample
    @{ value.returned }@
    @{ value.type }@
    @{ value.sample | replace('\n', '\n ') | html_ify }@
    @{ value.returned }@
    @{ value.type }@
    @{ value.sample | replace('\n', '\n ') | html_ify }@