summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlucasmoura <lucas.moura@canonical.com>2020-05-13 17:45:01 -0300
committerGitHub <noreply@github.com>2020-05-13 14:45:01 -0600
commit2e32c40a607250bc9e713c0daf360dc6617f4420 (patch)
treed1109d88a2fd02a1fbdfe01ed19448539444ddb7
parentc8f20b31cd57443b1bef17579dfceca432420c94 (diff)
downloadcloud-init-git-2e32c40a607250bc9e713c0daf360dc6617f4420.tar.gz
Add schema to apt configure config (#357)
Create a schema object for the `apt_configure` module and validate this schema in the `handle` function of the module. There are some considerations regarding this PR: * The `primary` and `security` keys have the exact same properties. I tried to eliminate this redundancy by moving their properties to a common place and then just referencing it for both security and primary. Similar to what is documented here: https://json-schema.org/understanding-json-schema/structuring.html under the `Reuse` paragraph. However, this approach does not work, because the `#` pointer goes to the beginning of the file, which is a python module instead of a json file, not allowing the pointer to find the correct definition. What I did was to create a separate dict for the mirror config and reuse it for primary and security, but maybe there are better approaches to do that. * There was no documentation for the config `debconf_selections`. I tried to infer what it supposed to do by looking at the code and the `debconf-set-selections` manpage, but my description may not be accurate or complete. * Add a _parse_description function to schema.py to render multi-line preformatted content instead of squashing all whitespace LP: #1858884
-rw-r--r--cloudinit/config/cc_apt_configure.py538
-rw-r--r--cloudinit/config/cc_ntp.py42
-rw-r--r--cloudinit/config/cc_write_files.py22
-rw-r--r--cloudinit/config/schema.py24
-rw-r--r--doc/examples/cloud-config-apt.txt2
-rw-r--r--doc/examples/cloud-config-chef-oneiric.txt63
-rw-r--r--doc/examples/cloud-config.txt9
-rw-r--r--tests/cloud_tests/testcases/examples/install_run_chef_recipes.yaml73
-rw-r--r--tests/unittests/test_handler/test_schema.py36
9 files changed, 507 insertions, 302 deletions
diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py
index ff58c062..9a33451d 100644
--- a/cloudinit/config/cc_apt_configure.py
+++ b/cloudinit/config/cc_apt_configure.py
@@ -6,228 +6,371 @@
#
# This file is part of cloud-init. See LICENSE file for license information.
-"""
-Apt Configure
--------------
-**Summary:** configure apt
-
-This module handles both configuration of apt options and adding source lists.
-There are configuration options such as ``apt_get_wrapper`` and
-``apt_get_command`` that control how cloud-init invokes apt-get.
-These configuration options are handled on a per-distro basis, so consult
-documentation for cloud-init's distro support for instructions on using
-these config options.
-
-.. note::
- To ensure that apt configuration is valid yaml, any strings containing
- special characters, especially ``:`` should be quoted.
-
-.. note::
- For more information about apt configuration, see the
- ``Additional apt configuration`` example.
-
-**Preserve sources.list:**
-
-By default, cloud-init will generate a new sources list in
-``/etc/apt/sources.list.d`` based on any changes specified in cloud config.
-To disable this behavior and preserve the sources list from the pristine image,
-set ``preserve_sources_list`` to ``true``.
-
-.. note::
- The ``preserve_sources_list`` option overrides all other config keys that
- would alter ``sources.list`` or ``sources.list.d``, **except** for
- additional sources to be added to ``sources.list.d``.
-
-**Disable source suites:**
-
-Entries in the sources list can be disabled using ``disable_suites``, which
-takes a list of suites to be disabled. If the string ``$RELEASE`` is present in
-a suite in the ``disable_suites`` list, it will be replaced with the release
-name. If a suite specified in ``disable_suites`` is not present in
-``sources.list`` it will be ignored. For convenience, several aliases are
-provided for ``disable_suites``:
-
- - ``updates`` => ``$RELEASE-updates``
- - ``backports`` => ``$RELEASE-backports``
- - ``security`` => ``$RELEASE-security``
- - ``proposed`` => ``$RELEASE-proposed``
- - ``release`` => ``$RELEASE``
-
-.. note::
- When a suite is disabled using ``disable_suites``, its entry in
- ``sources.list`` is not deleted; it is just commented out.
-
-**Configure primary and security mirrors:**
-
-The primary and security archive mirrors can be specified using the ``primary``
-and ``security`` keys, respectively. Both the ``primary`` and ``security`` keys
-take a list of configs, allowing mirrors to be specified on a per-architecture
-basis. Each config is a dictionary which must have an entry for ``arches``,
-specifying which architectures that config entry is for. The keyword
-``default`` applies to any architecture not explicitly listed. The mirror url
-can be specified with the ``uri`` key, or a list of mirrors to check can be
-provided in order, with the first mirror that can be resolved being selected.
-This allows the same configuration to be used in different environment, with
-different hosts used for a local apt mirror. If no mirror is provided by
-``uri`` or ``search``, ``search_dns`` may be used to search for dns names in
-the format ``<distro>-mirror`` in each of the following:
-
- - fqdn of this host per cloud metadata
- - localdomain
- - domains listed in ``/etc/resolv.conf``
-
-If there is a dns entry for ``<distro>-mirror``, then it is assumed that there
-is a distro mirror at ``http://<distro>-mirror.<domain>/<distro>``. If the
-``primary`` key is defined, but not the ``security`` key, then then
-configuration for ``primary`` is also used for ``security``. If ``search_dns``
-is used for the ``security`` key, the search pattern will be.
-``<distro>-security-mirror``.
-
-If no mirrors are specified, or all lookups fail, then default mirrors defined
-in the datasource are used. If none are present in the datasource either the
-following defaults are used:
-
- - primary: ``http://archive.ubuntu.com/ubuntu``
- - security: ``http://security.ubuntu.com/ubuntu``
-
-**Specify sources.list template:**
-
-A custom template for rendering ``sources.list`` can be specefied with
-``sources_list``. If no ``sources_list`` template is given, cloud-init will
-use sane default. Within this template, the following strings will be replaced
-with the appropriate values:
-
- - ``$MIRROR``
- - ``$RELEASE``
- - ``$PRIMARY``
- - ``$SECURITY``
-
-**Pass configuration to apt:**
-
-Apt configuration can be specified using ``conf``. Configuration is specified
-as a string. For multiline apt configuration, make sure to follow yaml syntax.
-
-**Configure apt proxy:**
-
-Proxy configuration for apt can be specified using ``conf``, but proxy config
-keys also exist for convenience. The proxy config keys, ``http_proxy``,
-``ftp_proxy``, and ``https_proxy`` may be used to specify a proxy for http, ftp
-and https protocols respectively. The ``proxy`` key also exists as an alias for
-``http_proxy``. Proxy url is specified in the format
-``<protocol>://[[user][:pass]@]host[:port]/``.
-
-**Add apt repos by regex:**
+"""Apt Configure: Configure apt for the user."""
-All source entries in ``apt-sources`` that match regex in
-``add_apt_repo_match`` will be added to the system using
-``add-apt-repository``. If ``add_apt_repo_match`` is not specified, it defaults
-to ``^[\\w-]+:\\w``
-
-**Add source list entries:**
-
-Source list entries can be specified as a dictionary under the ``sources``
-config key, with key in the dict representing a different source file. The key
-of each source entry will be used as an id that can be referenced in
-other config entries, as well as the filename for the source's configuration
-under ``/etc/apt/sources.list.d``. If the name does not end with ``.list``,
-it will be appended. If there is no configuration for a key in ``sources``, no
-file will be written, but the key may still be referred to as an id in other
-``sources`` entries.
-
-Each entry under ``sources`` is a dictionary which may contain any of the
-following optional keys:
-
- - ``source``: a sources.list entry (some variable replacements apply)
- - ``keyid``: a key to import via shortid or fingerprint
- - ``key``: a raw PGP key
- - ``keyserver``: alternate keyserver to pull ``keyid`` key from
-
-The ``source`` key supports variable replacements for the following strings:
-
- - ``$MIRROR``
- - ``$PRIMARY``
- - ``$SECURITY``
- - ``$RELEASE``
-
-**Internal name:** ``cc_apt_configure``
+import glob
+import os
+import re
+from textwrap import dedent
-**Module frequency:** per instance
+from cloudinit.config.schema import (
+ get_schema_doc, validate_cloudconfig_schema)
+from cloudinit import gpg
+from cloudinit import log as logging
+from cloudinit import templater
+from cloudinit import util
+from cloudinit.settings import PER_INSTANCE
-**Supported distros:** ubuntu, debian
+LOG = logging.getLogger(__name__)
-**Config keys**::
+# this will match 'XXX:YYY' (ie, 'cloud-archive:foo' or 'ppa:bar')
+ADD_APT_REPO_MATCH = r"^[\w-]+:\w"
- apt:
- preserve_sources_list: <true/false>
- disable_suites:
+frequency = PER_INSTANCE
+distros = ["ubuntu", "debian"]
+mirror_property = {
+ 'type': 'array',
+ 'item': {
+ 'type': 'object',
+ 'additionalProperties': False,
+ 'required': ['arches'],
+ 'properties': {
+ 'arches': {
+ 'type': 'array',
+ 'item': {
+ 'type': 'string'
+ },
+ 'minItems': 1
+ },
+ 'uri': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'search': {
+ 'type': 'array',
+ 'item': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'minItems': 1
+ },
+ 'search_dns': {
+ 'type': 'boolean',
+ }
+ }
+ }
+}
+schema = {
+ 'id': 'cc_apt_configure',
+ 'name': 'Apt Configure',
+ 'title': 'Configure apt for the user',
+ 'description': dedent("""\
+ This module handles both configuration of apt options and adding
+ source lists. There are configuration options such as
+ ``apt_get_wrapper`` and ``apt_get_command`` that control how
+ cloud-init invokes apt-get. These configuration options are
+ handled on a per-distro basis, so consult documentation for
+ cloud-init's distro support for instructions on using
+ these config options.
+
+ .. note::
+ To ensure that apt configuration is valid yaml, any strings
+ containing special characters, especially ``:`` should be quoted.
+
+ .. note::
+ For more information about apt configuration, see the
+ ``Additional apt configuration`` example."""),
+ 'distros': distros,
+ 'examples': [dedent("""\
+ apt:
+ preserve_sources_list: false
+ disable_suites:
- $RELEASE-updates
- backports
- $RELEASE
- mysuite
- primary:
+ primary:
- arches:
- amd64
- i386
- default
- uri: "http://us.archive.ubuntu.com/ubuntu"
+ uri: 'http://us.archive.ubuntu.com/ubuntu'
search:
- - "http://cool.but-sometimes-unreachable.com/ubuntu"
- - "http://us.archive.ubuntu.com/ubuntu"
+ - 'http://cool.but-sometimes-unreachable.com/ubuntu'
+ - 'http://us.archive.ubuntu.com/ubuntu'
search_dns: <true/false>
- arches:
- s390x
- arm64
- uri: "http://archive-to-use-for-arm64.example.com/ubuntu"
- security:
+ uri: 'http://archive-to-use-for-arm64.example.com/ubuntu'
+ security:
- arches:
- default
search_dns: true
- sources_list: |
- deb $MIRROR $RELEASE main restricted
- deb-src $MIRROR $RELEASE main restricted
- deb $PRIMARY $RELEASE universe restricted
- deb $SECURITY $RELEASE-security multiverse
- debconf_selections:
- set1: the-package the-package/some-flag boolean true
- conf: |
- APT {
- Get {
- Assume-Yes "true";
- Fix-Broken "true";
+ sources_list: |
+ deb $MIRROR $RELEASE main restricted
+ deb-src $MIRROR $RELEASE main restricted
+ deb $PRIMARY $RELEASE universe restricted
+ deb $SECURITY $RELEASE-security multiverse
+ debconf_selections:
+ set1: the-package the-package/some-flag boolean true
+ conf: |
+ APT {
+ Get {
+ Assume-Yes 'true';
+ Fix-Broken 'true';
+ }
+ }
+ proxy: 'http://[[user][:pass]@]host[:port]/'
+ http_proxy: 'http://[[user][:pass]@]host[:port]/'
+ ftp_proxy: 'ftp://[[user][:pass]@]host[:port]/'
+ https_proxy: 'https://[[user][:pass]@]host[:port]/'
+ sources:
+ source1:
+ keyid: 'keyid'
+ keyserver: 'keyserverurl'
+ source: 'deb http://<url>/ xenial main'
+ source2:
+ source: 'ppa:<ppa-name>'
+ source3:
+ source: 'deb $MIRROR $RELEASE multiverse'
+ key: |
+ ------BEGIN PGP PUBLIC KEY BLOCK-------
+ <key data>
+ ------END PGP PUBLIC KEY BLOCK-------""")],
+ 'frequency': frequency,
+ 'type': 'object',
+ 'properties': {
+ 'apt': {
+ 'type': 'object',
+ 'additionalProperties': False,
+ 'properties': {
+ 'preserve_sources_list': {
+ 'type': 'boolean',
+ 'default': False,
+ 'description': dedent("""\
+ By default, cloud-init will generate a new sources
+ list in ``/etc/apt/sources.list.d`` based on any
+ changes specified in cloud config. To disable this
+ behavior and preserve the sources list from the
+ pristine image, set ``preserve_sources_list``
+ to ``true``.
+
+ The ``preserve_sources_list`` option overrides
+ all other config keys that would alter
+ ``sources.list`` or ``sources.list.d``,
+ **except** for additional sources to be added
+ to ``sources.list.d``.""")
+ },
+ 'disable_suites': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'string'
+ },
+ 'uniqueItems': True,
+ 'description': dedent("""\
+ Entries in the sources list can be disabled using
+ ``disable_suites``, which takes a list of suites
+ to be disabled. If the string ``$RELEASE`` is
+ present in a suite in the ``disable_suites`` list,
+ it will be replaced with the release name. If a
+ suite specified in ``disable_suites`` is not
+ present in ``sources.list`` it will be ignored.
+ For convenience, several aliases are provided for
+ ``disable_suites``:
+
+ - ``updates`` => ``$RELEASE-updates``
+ - ``backports`` => ``$RELEASE-backports``
+ - ``security`` => ``$RELEASE-security``
+ - ``proposed`` => ``$RELEASE-proposed``
+ - ``release`` => ``$RELEASE``.
+
+ When a suite is disabled using ``disable_suites``,
+ its entry in ``sources.list`` is not deleted; it
+ is just commented out.""")
+ },
+ 'primary': {
+ **mirror_property,
+ 'description': dedent("""\
+ The primary and security archive mirrors can
+ be specified using the ``primary`` and
+ ``security`` keys, respectively. Both the
+ ``primary`` and ``security`` keys take a list
+ of configs, allowing mirrors to be specified
+ on a per-architecture basis. Each config is a
+ dictionary which must have an entry for
+ ``arches``, specifying which architectures
+ that config entry is for. The keyword
+ ``default`` applies to any architecture not
+ explicitly listed. The mirror url can be specified
+ with the ``uri`` key, or a list of mirrors to
+ check can be provided in order, with the first
+ mirror that can be resolved being selected. This
+ allows the same configuration to be used in
+ different environment, with different hosts used
+ for a local apt mirror. If no mirror is provided
+ by ``uri`` or ``search``, ``search_dns`` may be
+ used to search for dns names in the format
+ ``<distro>-mirror`` in each of the following:
+
+ - fqdn of this host per cloud metadata,
+ - localdomain,
+ - domains listed in ``/etc/resolv.conf``.
+
+ If there is a dns entry for ``<distro>-mirror``,
+ then it is assumed that there is a distro mirror
+ at ``http://<distro>-mirror.<domain>/<distro>``.
+ If the ``primary`` key is defined, but not the
+ ``security`` key, then then configuration for
+ ``primary`` is also used for ``security``.
+ If ``search_dns`` is used for the ``security``
+ key, the search pattern will be
+ ``<distro>-security-mirror``.
+
+ If no mirrors are specified, or all lookups fail,
+ then default mirrors defined in the datasource
+ are used. If none are present in the datasource
+ either the following defaults are used:
+
+ - ``primary`` => \
+ ``http://archive.ubuntu.com/ubuntu``.
+ - ``security`` => \
+ ``http://security.ubuntu.com/ubuntu``
+ """)},
+ 'security': {
+ **mirror_property,
+ 'description': dedent("""\
+ Please refer to the primary config documentation""")
+ },
+ 'add_apt_repo_match': {
+ 'type': 'string',
+ 'default': ADD_APT_REPO_MATCH,
+ 'description': dedent("""\
+ All source entries in ``apt-sources`` that match
+ regex in ``add_apt_repo_match`` will be added to
+ the system using ``add-apt-repository``. If
+ ``add_apt_repo_match`` is not specified, it
+ defaults to ``{}``""".format(ADD_APT_REPO_MATCH))
+ },
+ 'debconf_selections': {
+ 'type': 'object',
+ 'items': {'type': 'string'},
+ 'description': dedent("""\
+ Debconf additional configurations can be specified as a
+ dictionary under the ``debconf_selections`` config
+ key, with each key in the dict representing a
+ different set of configurations. The value of each key
+ must be a string containing all the debconf
+ configurations that must be applied. We will bundle
+ all of the values and pass them to
+ ``debconf-set-selections``. Therefore, each value line
+ must be a valid entry for ``debconf-set-selections``,
+ meaning that they must possess for distinct fields:
+
+ ``pkgname question type answer``
+
+ Where:
+
+ - ``pkgname`` is the name of the package.
+ - ``question`` the name of the questions.
+ - ``type`` is the type of question.
+ - ``answer`` is the value used to ansert the \
+ question.
+
+ For example: \
+ ``ippackage ippackage/ip string 127.0.01``
+ """)
+ },
+ 'sources_list': {
+ 'type': 'string',
+ 'description': dedent("""\
+ Specifies a custom template for rendering
+ ``sources.list`` . If no ``sources_list`` template
+ is given, cloud-init will use sane default. Within
+ this template, the following strings will be
+ replaced with the appropriate values:
+
+ - ``$MIRROR``
+ - ``$RELEASE``
+ - ``$PRIMARY``
+ - ``$SECURITY``""")
+ },
+ 'conf': {
+ 'type': 'string',
+ 'description': dedent("""\
+ Specify configuration for apt, such as proxy
+ configuration. This configuration is specified as a
+ string. For multiline apt configuration, make sure
+ to follow yaml syntax.""")
+ },
+ 'https_proxy': {
+ 'type': 'string',
+ 'description': dedent("""\
+ More convenient way to specify https apt proxy.
+ https proxy url is specified in the format
+ ``https://[[user][:pass]@]host[:port]/``.""")
+ },
+ 'http_proxy': {
+ 'type': 'string',
+ 'description': dedent("""\
+ More convenient way to specify http apt proxy.
+ http proxy url is specified in the format
+ ``http://[[user][:pass]@]host[:port]/``.""")
+ },
+ 'proxy': {
+ 'type': 'string',
+ 'description': 'Alias for defining a http apt proxy.'
+ },
+ 'ftp_proxy': {
+ 'type': 'string',
+ 'description': dedent("""\
+ More convenient way to specify ftp apt proxy.
+ ftp proxy url is specified in the format
+ ``ftp://[[user][:pass]@]host[:port]/``.""")
+ },
+ 'sources': {
+ 'type': 'object',
+ 'items': {'type': 'string'},
+ 'description': dedent("""\
+ Source list entries can be specified as a
+ dictionary under the ``sources`` config key, with
+ each key in the dict representing a different source
+ file. The key of each source entry will be used
+ as an id that can be referenced in other config
+ entries, as well as the filename for the source's
+ configuration under ``/etc/apt/sources.list.d``.
+ If the name does not end with ``.list``, it will
+ be appended. If there is no configuration for a
+ key in ``sources``, no file will be written, but
+ the key may still be referred to as an id in other
+ ``sources`` entries.
+
+ Each entry under ``sources`` is a dictionary which
+ may contain any of the following optional keys:
+
+ - ``source``: a sources.list entry \
+ (some variable replacements apply).
+ - ``keyid``: a key to import via shortid or \
+ fingerprint.
+ - ``key``: a raw PGP key.
+ - ``keyserver``: alternate keyserver to pull \
+ ``keyid`` key from.
+
+ The ``source`` key supports variable
+ replacements for the following strings:
+
+ - ``$MIRROR``
+ - ``$PRIMARY``
+ - ``$SECURITY``
+ - ``$RELEASE``""")
}
}
- proxy: "http://[[user][:pass]@]host[:port]/"
- http_proxy: "http://[[user][:pass]@]host[:port]/"
- ftp_proxy: "ftp://[[user][:pass]@]host[:port]/"
- https_proxy: "https://[[user][:pass]@]host[:port]/"
- sources:
- source1:
- keyid: "keyid"
- keyserver: "keyserverurl"
- source: "deb http://<url>/ xenial main"
- source2:
- source: "ppa:<ppa-name>"
- source3:
- source: "deb $MIRROR $RELEASE multiverse"
- key: |
- ------BEGIN PGP PUBLIC KEY BLOCK-------
- <key data>
- ------END PGP PUBLIC KEY BLOCK-------
-"""
-
-import glob
-import os
-import re
-
-from cloudinit import gpg
-from cloudinit import log as logging
-from cloudinit import templater
-from cloudinit import util
+ }
+ }
+}
-LOG = logging.getLogger(__name__)
+__doc__ = get_schema_doc(schema)
-# this will match 'XXX:YYY' (ie, 'cloud-archive:foo' or 'ppa:bar')
-ADD_APT_REPO_MATCH = r"^[\w-]+:\w"
# place where apt stores cached repository data
APT_LISTS = "/var/lib/apt/lists"
@@ -279,6 +422,7 @@ def handle(name, ocfg, cloud, log, _):
"Expected dictionary for 'apt' config, found {config_type}".format(
config_type=type(cfg)))
+ validate_cloudconfig_schema(cfg, schema)
apply_debconf_selections(cfg, target)
apply_apt(cfg, cloud, target)
diff --git a/cloudinit/config/cc_ntp.py b/cloudinit/config/cc_ntp.py
index 5498bbaa..3b2c2020 100644
--- a/cloudinit/config/cc_ntp.py
+++ b/cloudinit/config/cc_ntp.py
@@ -169,8 +169,8 @@ schema = {
'uniqueItems': True,
'description': dedent("""\
List of ntp pools. If both pools and servers are
- empty, 4 default pool servers will be provided of
- the format ``{0-3}.{distro}.pool.ntp.org``.""")
+ empty, 4 default pool servers will be provided of
+ the format ``{0-3}.{distro}.pool.ntp.org``.""")
},
'servers': {
'type': 'array',
@@ -181,46 +181,46 @@ schema = {
'uniqueItems': True,
'description': dedent("""\
List of ntp servers. If both pools and servers are
- empty, 4 default pool servers will be provided with
- the format ``{0-3}.{distro}.pool.ntp.org``.""")
+ empty, 4 default pool servers will be provided with
+ the format ``{0-3}.{distro}.pool.ntp.org``.""")
},
'ntp_client': {
'type': 'string',
'default': 'auto',
'description': dedent("""\
Name of an NTP client to use to configure system NTP.
- When unprovided or 'auto' the default client preferred
- by the distribution will be used. The following
- built-in client names can be used to override existing
- configuration defaults: chrony, ntp, ntpdate,
- systemd-timesyncd."""),
+ When unprovided or 'auto' the default client preferred
+ by the distribution will be used. The following
+ built-in client names can be used to override existing
+ configuration defaults: chrony, ntp, ntpdate,
+ systemd-timesyncd."""),
},
'enabled': {
'type': 'boolean',
'default': True,
'description': dedent("""\
Attempt to enable ntp clients if set to True. If set
- to False, ntp client will not be configured or
- installed"""),
+ to False, ntp client will not be configured or
+ installed"""),
},
'config': {
'description': dedent("""\
Configuration settings or overrides for the
- ``ntp_client`` specified."""),
+ ``ntp_client`` specified."""),
'type': ['object'],
'properties': {
'confpath': {
'type': 'string',
'description': dedent("""\
The path to where the ``ntp_client``
- configuration is written."""),
+ configuration is written."""),
},
'check_exe': {
'type': 'string',
'description': dedent("""\
The executable name for the ``ntp_client``.
- For example, ntp service ``check_exe`` is
- 'ntpd' because it runs the ntpd binary."""),
+ For example, ntp service ``check_exe`` is
+ 'ntpd' because it runs the ntpd binary."""),
},
'packages': {
'type': 'array',
@@ -230,22 +230,22 @@ schema = {
'uniqueItems': True,
'description': dedent("""\
List of packages needed to be installed for the
- selected ``ntp_client``."""),
+ selected ``ntp_client``."""),
},
'service_name': {
'type': 'string',
'description': dedent("""\
The systemd or sysvinit service name used to
- start and stop the ``ntp_client``
- service."""),
+ start and stop the ``ntp_client``
+ service."""),
},
'template': {
'type': 'string',
'description': dedent("""\
Inline template allowing users to define their
- own ``ntp_client`` configuration template.
- The value must start with '## template:jinja'
- to enable use of templating support.
+ own ``ntp_client`` configuration template.
+ The value must start with '## template:jinja'
+ to enable use of templating support.
"""),
},
},
diff --git a/cloudinit/config/cc_write_files.py b/cloudinit/config/cc_write_files.py
index 204cbfd6..8601e707 100644
--- a/cloudinit/config/cc_write_files.py
+++ b/cloudinit/config/cc_write_files.py
@@ -103,7 +103,7 @@ schema = {
'type': 'string',
'description': dedent("""\
Path of the file to which ``content`` is decoded
- and written
+ and written
"""),
},
'content': {
@@ -111,9 +111,9 @@ schema = {
'default': '',
'description': dedent("""\
Optional content to write to the provided ``path``.
- When content is present and encoding is not '%s',
- decode the content prior to writing. Default:
- **''**
+ When content is present and encoding is not '%s',
+ decode the content prior to writing. Default:
+ **''**
""" % UNKNOWN_ENC),
},
'owner': {
@@ -121,7 +121,7 @@ schema = {
'default': DEFAULT_OWNER,
'description': dedent("""\
Optional owner:group to chown on the file. Default:
- **{owner}**
+ **{owner}**
""".format(owner=DEFAULT_OWNER)),
},
'permissions': {
@@ -129,8 +129,8 @@ schema = {
'default': oct(DEFAULT_PERMS).replace('o', ''),
'description': dedent("""\
Optional file permissions to set on ``path``
- represented as an octal string '0###'. Default:
- **'{perms}'**
+ represented as an octal string '0###'. Default:
+ **'{perms}'**
""".format(perms=oct(DEFAULT_PERMS).replace('o', ''))),
},
'encoding': {
@@ -139,16 +139,16 @@ schema = {
'enum': supported_encoding_types,
'description': dedent("""\
Optional encoding type of the content. Default is
- **text/plain** and no content decoding is
- performed. Supported encoding types are:
- %s.""" % ", ".join(supported_encoding_types)),
+ **text/plain** and no content decoding is
+ performed. Supported encoding types are:
+ %s.""" % ", ".join(supported_encoding_types)),
},
'append': {
'type': 'boolean',
'default': False,
'description': dedent("""\
Whether to append ``content`` to existing file if
- ``path`` exists. Default: **false**.
+ ``path`` exists. Default: **false**.
"""),
},
},
diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py
index a295d63d..da00060a 100644
--- a/cloudinit/config/schema.py
+++ b/cloudinit/config/schema.py
@@ -306,6 +306,28 @@ def _get_property_type(property_dict):
return property_type
+def _parse_description(description, prefix):
+ """Parse description from the schema in a format that we can better
+ display in our docs. This parser does three things:
+
+ - Guarantee that a paragraph will be in a single line
+ - Guarantee that each new paragraph will be aligned with
+ the first paragraph
+ - Proper align lists of items
+
+ @param description: The original description in the schema.
+ @param prefix: The number of spaces used to align the current description
+ """
+ list_paragraph = prefix * 3
+ description = re.sub(r"(\S)\n(\S)", r"\1 \2", description)
+ description = re.sub(
+ r"\n\n", r"\n\n{}".format(prefix), description)
+ description = re.sub(
+ r"\n( +)-", r"\n{}-".format(list_paragraph), description)
+
+ return description
+
+
def _get_property_doc(schema, prefix=' '):
"""Return restructured text describing the supported schema properties."""
new_prefix = prefix + ' '
@@ -318,7 +340,7 @@ def _get_property_doc(schema, prefix=' '):
prefix=prefix,
prop_name=prop_key,
type=_get_property_type(prop_config),
- description=description.replace('\n', '')))
+ description=_parse_description(description, prefix)))
items = prop_config.get('items')
if items:
if isinstance(items, list):
diff --git a/doc/examples/cloud-config-apt.txt b/doc/examples/cloud-config-apt.txt
index 5a56cd1b..004894b7 100644
--- a/doc/examples/cloud-config-apt.txt
+++ b/doc/examples/cloud-config-apt.txt
@@ -142,7 +142,7 @@ apt:
# as above, allowing to have one config for different per arch mirrors
# security is optional, if not defined it is set to the same value as primary
security:
- uri: http://security.ubuntu.com/ubuntu
+ - uri: http://security.ubuntu.com/ubuntu
# If search_dns is set for security the searched pattern is:
# <distro>-security-mirror
diff --git a/doc/examples/cloud-config-chef-oneiric.txt b/doc/examples/cloud-config-chef-oneiric.txt
index ab2e2a33..241fbf9b 100644
--- a/doc/examples/cloud-config-chef-oneiric.txt
+++ b/doc/examples/cloud-config-chef-oneiric.txt
@@ -13,38 +13,39 @@
# Key from http://apt.opscode.com/packages@opscode.com.gpg.key
apt:
sources:
- - source: "deb http://apt.opscode.com/ $RELEASE-0.10 main"
- key: |
- -----BEGIN PGP PUBLIC KEY BLOCK-----
- Version: GnuPG v1.4.9 (GNU/Linux)
+ source1:
+ source: "deb http://apt.opscode.com/ $RELEASE-0.10 main"
+ key: |
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
+ Version: GnuPG v1.4.9 (GNU/Linux)
- mQGiBEppC7QRBADfsOkZU6KZK+YmKw4wev5mjKJEkVGlus+NxW8wItX5sGa6kdUu
- twAyj7Yr92rF+ICFEP3gGU6+lGo0Nve7KxkN/1W7/m3G4zuk+ccIKmjp8KS3qn99
- dxy64vcji9jIllVa+XXOGIp0G8GEaj7mbkixL/bMeGfdMlv8Gf2XPpp9vwCgn/GC
- JKacfnw7MpLKUHOYSlb//JsEAJqao3ViNfav83jJKEkD8cf59Y8xKia5OpZqTK5W
- ShVnNWS3U5IVQk10ZDH97Qn/YrK387H4CyhLE9mxPXs/ul18ioiaars/q2MEKU2I
- XKfV21eMLO9LYd6Ny/Kqj8o5WQK2J6+NAhSwvthZcIEphcFignIuobP+B5wNFQpe
- DbKfA/0WvN2OwFeWRcmmd3Hz7nHTpcnSF+4QX6yHRF/5BgxkG6IqBIACQbzPn6Hm
- sMtm/SVf11izmDqSsQptCrOZILfLX/mE+YOl+CwWSHhl+YsFts1WOuh1EhQD26aO
- Z84HuHV5HFRWjDLw9LriltBVQcXbpfSrRP5bdr7Wh8vhqJTPjrQnT3BzY29kZSBQ
- YWNrYWdlcyA8cGFja2FnZXNAb3BzY29kZS5jb20+iGAEExECACAFAkppC7QCGwMG
- CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRApQKupg++Caj8sAKCOXmdG36gWji/K
- +o+XtBfvdMnFYQCfTCEWxRy2BnzLoBBFCjDSK6sJqCu5Ag0ESmkLtBAIAIO2SwlR
- lU5i6gTOp42RHWW7/pmW78CwUqJnYqnXROrt3h9F9xrsGkH0Fh1FRtsnncgzIhvh
- DLQnRHnkXm0ws0jV0PF74ttoUT6BLAUsFi2SPP1zYNJ9H9fhhK/pjijtAcQwdgxu
- wwNJ5xCEscBZCjhSRXm0d30bK1o49Cow8ZIbHtnXVP41c9QWOzX/LaGZsKQZnaMx
- EzDk8dyyctR2f03vRSVyTFGgdpUcpbr9eTFVgikCa6ODEBv+0BnCH6yGTXwBid9g
- w0o1e/2DviKUWCC+AlAUOubLmOIGFBuI4UR+rux9affbHcLIOTiKQXv79lW3P7W8
- AAfniSQKfPWXrrcAAwUH/2XBqD4Uxhbs25HDUUiM/m6Gnlj6EsStg8n0nMggLhuN
- QmPfoNByMPUqvA7sULyfr6xCYzbzRNxABHSpf85FzGQ29RF4xsA4vOOU8RDIYQ9X
- Q8NqqR6pydprRFqWe47hsAN7BoYuhWqTtOLSBmnAnzTR5pURoqcquWYiiEavZixJ
- 3ZRAq/HMGioJEtMFrvsZjGXuzef7f0ytfR1zYeLVWnL9Bd32CueBlI7dhYwkFe+V
- Ep5jWOCj02C1wHcwt+uIRDJV6TdtbIiBYAdOMPk15+VBdweBXwMuYXr76+A7VeDL
- zIhi7tKFo6WiwjKZq0dzctsJJjtIfr4K4vbiD9Ojg1iISQQYEQIACQUCSmkLtAIb
- DAAKCRApQKupg++CauISAJ9CxYPOKhOxalBnVTLeNUkAHGg2gACeIsbobtaD4ZHG
- 0GLl8EkfA8uhluM=
- =zKAm
- -----END PGP PUBLIC KEY BLOCK-----
+ mQGiBEppC7QRBADfsOkZU6KZK+YmKw4wev5mjKJEkVGlus+NxW8wItX5sGa6kdUu
+ twAyj7Yr92rF+ICFEP3gGU6+lGo0Nve7KxkN/1W7/m3G4zuk+ccIKmjp8KS3qn99
+ dxy64vcji9jIllVa+XXOGIp0G8GEaj7mbkixL/bMeGfdMlv8Gf2XPpp9vwCgn/GC
+ JKacfnw7MpLKUHOYSlb//JsEAJqao3ViNfav83jJKEkD8cf59Y8xKia5OpZqTK5W
+ ShVnNWS3U5IVQk10ZDH97Qn/YrK387H4CyhLE9mxPXs/ul18ioiaars/q2MEKU2I
+ XKfV21eMLO9LYd6Ny/Kqj8o5WQK2J6+NAhSwvthZcIEphcFignIuobP+B5wNFQpe
+ DbKfA/0WvN2OwFeWRcmmd3Hz7nHTpcnSF+4QX6yHRF/5BgxkG6IqBIACQbzPn6Hm
+ sMtm/SVf11izmDqSsQptCrOZILfLX/mE+YOl+CwWSHhl+YsFts1WOuh1EhQD26aO
+ Z84HuHV5HFRWjDLw9LriltBVQcXbpfSrRP5bdr7Wh8vhqJTPjrQnT3BzY29kZSBQ
+ YWNrYWdlcyA8cGFja2FnZXNAb3BzY29kZS5jb20+iGAEExECACAFAkppC7QCGwMG
+ CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRApQKupg++Caj8sAKCOXmdG36gWji/K
+ +o+XtBfvdMnFYQCfTCEWxRy2BnzLoBBFCjDSK6sJqCu5Ag0ESmkLtBAIAIO2SwlR
+ lU5i6gTOp42RHWW7/pmW78CwUqJnYqnXROrt3h9F9xrsGkH0Fh1FRtsnncgzIhvh
+ DLQnRHnkXm0ws0jV0PF74ttoUT6BLAUsFi2SPP1zYNJ9H9fhhK/pjijtAcQwdgxu
+ wwNJ5xCEscBZCjhSRXm0d30bK1o49Cow8ZIbHtnXVP41c9QWOzX/LaGZsKQZnaMx
+ EzDk8dyyctR2f03vRSVyTFGgdpUcpbr9eTFVgikCa6ODEBv+0BnCH6yGTXwBid9g
+ w0o1e/2DviKUWCC+AlAUOubLmOIGFBuI4UR+rux9affbHcLIOTiKQXv79lW3P7W8
+ AAfniSQKfPWXrrcAAwUH/2XBqD4Uxhbs25HDUUiM/m6Gnlj6EsStg8n0nMggLhuN
+ QmPfoNByMPUqvA7sULyfr6xCYzbzRNxABHSpf85FzGQ29RF4xsA4vOOU8RDIYQ9X
+ Q8NqqR6pydprRFqWe47hsAN7BoYuhWqTtOLSBmnAnzTR5pURoqcquWYiiEavZixJ
+ 3ZRAq/HMGioJEtMFrvsZjGXuzef7f0ytfR1zYeLVWnL9Bd32CueBlI7dhYwkFe+V
+ Ep5jWOCj02C1wHcwt+uIRDJV6TdtbIiBYAdOMPk15+VBdweBXwMuYXr76+A7VeDL
+ zIhi7tKFo6WiwjKZq0dzctsJJjtIfr4K4vbiD9Ojg1iISQQYEQIACQUCSmkLtAIb
+ DAAKCRApQKupg++CauISAJ9CxYPOKhOxalBnVTLeNUkAHGg2gACeIsbobtaD4ZHG
+ 0GLl8EkfA8uhluM=
+ =zKAm
+ -----END PGP PUBLIC KEY BLOCK-----
chef:
diff --git a/doc/examples/cloud-config.txt b/doc/examples/cloud-config.txt
index 8c1e4bb0..20a0ce0d 100644
--- a/doc/examples/cloud-config.txt
+++ b/doc/examples/cloud-config.txt
@@ -203,13 +203,14 @@ ssh_import_id: [smoser]
#
# Default: none
#
-debconf_selections: | # Need to preserve newlines
+debconf_selections:
# Force debconf priority to critical.
- debconf debconf/priority select critical
+ set1: debconf debconf/priority select critical
# Override default frontend to readline, but allow user to select.
- debconf debconf/frontend select readline
- debconf debconf/frontend seen false
+ set2: |
+ debconf debconf/frontend select readline
+ debconf debconf/frontend seen false
# manage byobu defaults
# byobu_by_default:
diff --git a/tests/cloud_tests/testcases/examples/install_run_chef_recipes.yaml b/tests/cloud_tests/testcases/examples/install_run_chef_recipes.yaml
index 0bec305e..68ca95b5 100644
--- a/tests/cloud_tests/testcases/examples/install_run_chef_recipes.yaml
+++ b/tests/cloud_tests/testcases/examples/install_run_chef_recipes.yaml
@@ -8,43 +8,44 @@ cloud_config: |
#cloud-config
# Key from https://packages.chef.io/chef.asc
apt:
- source1:
- source: "deb http://packages.chef.io/repos/apt/stable $RELEASE main"
- key: |
- -----BEGIN PGP PUBLIC KEY BLOCK-----
- Version: GnuPG v1.4.12 (Darwin)
- Comment: GPGTools - http://gpgtools.org
+ sources:
+ source1:
+ source: "deb http://packages.chef.io/repos/apt/stable $RELEASE main"
+ key: |
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
+ Version: GnuPG v1.4.12 (Darwin)
+ Comment: GPGTools - http://gpgtools.org
- mQGiBEppC7QRBADfsOkZU6KZK+YmKw4wev5mjKJEkVGlus+NxW8wItX5sGa6kdUu
- twAyj7Yr92rF+ICFEP3gGU6+lGo0Nve7KxkN/1W7/m3G4zuk+ccIKmjp8KS3qn99
- dxy64vcji9jIllVa+XXOGIp0G8GEaj7mbkixL/bMeGfdMlv8Gf2XPpp9vwCgn/GC
- JKacfnw7MpLKUHOYSlb//JsEAJqao3ViNfav83jJKEkD8cf59Y8xKia5OpZqTK5W
- ShVnNWS3U5IVQk10ZDH97Qn/YrK387H4CyhLE9mxPXs/ul18ioiaars/q2MEKU2I
- XKfV21eMLO9LYd6Ny/Kqj8o5WQK2J6+NAhSwvthZcIEphcFignIuobP+B5wNFQpe
- DbKfA/0WvN2OwFeWRcmmd3Hz7nHTpcnSF+4QX6yHRF/5BgxkG6IqBIACQbzPn6Hm
- sMtm/SVf11izmDqSsQptCrOZILfLX/mE+YOl+CwWSHhl+YsFts1WOuh1EhQD26aO
- Z84HuHV5HFRWjDLw9LriltBVQcXbpfSrRP5bdr7Wh8vhqJTPjrQnT3BzY29kZSBQ
- YWNrYWdlcyA8cGFja2FnZXNAb3BzY29kZS5jb20+iGAEExECACAFAkppC7QCGwMG
- CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRApQKupg++Caj8sAKCOXmdG36gWji/K
- +o+XtBfvdMnFYQCfTCEWxRy2BnzLoBBFCjDSK6sJqCu0IENIRUYgUGFja2FnZXMg
- PHBhY2thZ2VzQGNoZWYuaW8+iGIEExECACIFAlQwYFECGwMGCwkIBwMCBhUIAgkK
- CwQWAgMBAh4BAheAAAoJEClAq6mD74JqX94An26z99XOHWpLN8ahzm7cp13t4Xid
- AJ9wVcgoUBzvgg91lKfv/34cmemZn7kCDQRKaQu0EAgAg7ZLCVGVTmLqBM6njZEd
- Zbv+mZbvwLBSomdiqddE6u3eH0X3GuwaQfQWHUVG2yedyDMiG+EMtCdEeeRebTCz
- SNXQ8Xvi22hRPoEsBSwWLZI8/XNg0n0f1+GEr+mOKO0BxDB2DG7DA0nnEISxwFkK
- OFJFebR3fRsrWjj0KjDxkhse2ddU/jVz1BY7Nf8toZmwpBmdozETMOTx3LJy1HZ/
- Te9FJXJMUaB2lRyluv15MVWCKQJro4MQG/7QGcIfrIZNfAGJ32DDSjV7/YO+IpRY
- IL4CUBQ65suY4gYUG4jhRH6u7H1p99sdwsg5OIpBe/v2Vbc/tbwAB+eJJAp89Zeu
- twADBQf/ZcGoPhTGFuzbkcNRSIz+boaeWPoSxK2DyfScyCAuG41CY9+g0HIw9Sq8
- DuxQvJ+vrEJjNvNE3EAEdKl/zkXMZDb1EXjGwDi845TxEMhhD1dDw2qpHqnJ2mtE
- WpZ7juGwA3sGhi6FapO04tIGacCfNNHmlRGipyq5ZiKIRq9mLEndlECr8cwaKgkS
- 0wWu+xmMZe7N5/t/TK19HXNh4tVacv0F3fYK54GUjt2FjCQV75USnmNY4KPTYLXA
- dzC364hEMlXpN21siIFgB04w+TXn5UF3B4FfAy5hevvr4DtV4MvMiGLu0oWjpaLC
- MpmrR3Ny2wkmO0h+vgri9uIP06ODWIhJBBgRAgAJBQJKaQu0AhsMAAoJEClAq6mD
- 74Jq4hIAoJ5KrYS8kCwj26SAGzglwggpvt3CAJ0bekyky56vNqoegB+y4PQVDv4K
- zA==
- =IxPr
- -----END PGP PUBLIC KEY BLOCK-----
+ mQGiBEppC7QRBADfsOkZU6KZK+YmKw4wev5mjKJEkVGlus+NxW8wItX5sGa6kdUu
+ twAyj7Yr92rF+ICFEP3gGU6+lGo0Nve7KxkN/1W7/m3G4zuk+ccIKmjp8KS3qn99
+ dxy64vcji9jIllVa+XXOGIp0G8GEaj7mbkixL/bMeGfdMlv8Gf2XPpp9vwCgn/GC
+ JKacfnw7MpLKUHOYSlb//JsEAJqao3ViNfav83jJKEkD8cf59Y8xKia5OpZqTK5W
+ ShVnNWS3U5IVQk10ZDH97Qn/YrK387H4CyhLE9mxPXs/ul18ioiaars/q2MEKU2I
+ XKfV21eMLO9LYd6Ny/Kqj8o5WQK2J6+NAhSwvthZcIEphcFignIuobP+B5wNFQpe
+ DbKfA/0WvN2OwFeWRcmmd3Hz7nHTpcnSF+4QX6yHRF/5BgxkG6IqBIACQbzPn6Hm
+ sMtm/SVf11izmDqSsQptCrOZILfLX/mE+YOl+CwWSHhl+YsFts1WOuh1EhQD26aO
+ Z84HuHV5HFRWjDLw9LriltBVQcXbpfSrRP5bdr7Wh8vhqJTPjrQnT3BzY29kZSBQ
+ YWNrYWdlcyA8cGFja2FnZXNAb3BzY29kZS5jb20+iGAEExECACAFAkppC7QCGwMG
+ CwkIBwMCBBUCCAMEFgIDAQIeAQIXgAAKCRApQKupg++Caj8sAKCOXmdG36gWji/K
+ +o+XtBfvdMnFYQCfTCEWxRy2BnzLoBBFCjDSK6sJqCu0IENIRUYgUGFja2FnZXMg
+ PHBhY2thZ2VzQGNoZWYuaW8+iGIEExECACIFAlQwYFECGwMGCwkIBwMCBhUIAgkK
+ CwQWAgMBAh4BAheAAAoJEClAq6mD74JqX94An26z99XOHWpLN8ahzm7cp13t4Xid
+ AJ9wVcgoUBzvgg91lKfv/34cmemZn7kCDQRKaQu0EAgAg7ZLCVGVTmLqBM6njZEd
+ Zbv+mZbvwLBSomdiqddE6u3eH0X3GuwaQfQWHUVG2yedyDMiG+EMtCdEeeRebTCz
+ SNXQ8Xvi22hRPoEsBSwWLZI8/XNg0n0f1+GEr+mOKO0BxDB2DG7DA0nnEISxwFkK
+ OFJFebR3fRsrWjj0KjDxkhse2ddU/jVz1BY7Nf8toZmwpBmdozETMOTx3LJy1HZ/
+ Te9FJXJMUaB2lRyluv15MVWCKQJro4MQG/7QGcIfrIZNfAGJ32DDSjV7/YO+IpRY
+ IL4CUBQ65suY4gYUG4jhRH6u7H1p99sdwsg5OIpBe/v2Vbc/tbwAB+eJJAp89Zeu
+ twADBQf/ZcGoPhTGFuzbkcNRSIz+boaeWPoSxK2DyfScyCAuG41CY9+g0HIw9Sq8
+ DuxQvJ+vrEJjNvNE3EAEdKl/zkXMZDb1EXjGwDi845TxEMhhD1dDw2qpHqnJ2mtE
+ WpZ7juGwA3sGhi6FapO04tIGacCfNNHmlRGipyq5ZiKIRq9mLEndlECr8cwaKgkS
+ 0wWu+xmMZe7N5/t/TK19HXNh4tVacv0F3fYK54GUjt2FjCQV75USnmNY4KPTYLXA
+ dzC364hEMlXpN21siIFgB04w+TXn5UF3B4FfAy5hevvr4DtV4MvMiGLu0oWjpaLC
+ MpmrR3Ny2wkmO0h+vgri9uIP06ODWIhJBBgRAgAJBQJKaQu0AhsMAAoJEClAq6mD
+ 74Jq4hIAoJ5KrYS8kCwj26SAGzglwggpvt3CAJ0bekyky56vNqoegB+y4PQVDv4K
+ zA==
+ =IxPr
+ -----END PGP PUBLIC KEY BLOCK-----
chef:
diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py
index 071b98bc..e19d13b8 100644
--- a/tests/unittests/test_handler/test_schema.py
+++ b/tests/unittests/test_handler/test_schema.py
@@ -24,6 +24,7 @@ class GetSchemaTest(CiTestCase):
schema = get_schema()
self.assertCountEqual(
[
+ 'cc_apt_configure',
'cc_bootcmd',
'cc_locale',
'cc_ntp',
@@ -289,6 +290,41 @@ class GetSchemaDocTest(CiTestCase):
"""),
get_schema_doc(full_schema))
+ def test_get_schema_doc_properly_parse_description(self):
+ """get_schema_doc description properly formatted"""
+ full_schema = copy(self.required_schema)
+ full_schema.update(
+ {'properties': {
+ 'p1': {
+ 'type': 'string',
+ 'description': dedent("""\
+ This item
+ has the
+ following options:
+
+ - option1
+ - option2
+ - option3
+
+ The default value is
+ option1""")
+ }
+ }}
+ )
+
+ self.assertIn(
+ dedent("""
+ **Config schema**:
+ **p1:** (string) This item has the following options:
+
+ - option1
+ - option2
+ - option3
+
+ The default value is option1
+ """),
+ get_schema_doc(full_schema))
+
def test_get_schema_doc_raises_key_errors(self):
"""get_schema_doc raises KeyErrors on missing keys."""
for key in self.required_schema: