Module creation

Much of cloud-init’s functionality is provided by modules. All modules follow a similar layout in order to provide consistent execution and documentation. Use the example provided here to create a new module.

Your Python module

Modules are located in the cloudinit/config/ directory, where the naming convention for modules is to use cc_<module_name> (with underscores as the separators).

The handle function

Your module must include a handle function. The arguments are:

  • name: The module name specified in the configuration.

  • cfg: A configuration object that is the result of the merging of cloud-config configuration with any datasource-provided configuration.

  • cloud: A cloud object that can be used to access various datasource and paths for the given distro and data provided by the various datasource instance types.

  • args: An argument list. This is usually empty and is only populated if the module is called independently from the command line or if the module definition in /etc/cloud/cloud.cfg[.d] has been modified to pass arguments to this module.

Schema definition

If your module introduces any new cloud-config keys, you must provide a schema definition in cloud-init-schema.json.

  • The meta variable must exist and be of type MetaSchema.

    • id: The module ID. In most cases this will be the filename without the .py extension.

    • distros: Defines the list of supported distros. It can contain any of the values (not keys) defined in the OSFAMILIES map or [ALL_DISTROS] if there is no distro restriction.

    • frequency: Defines how often module runs. It must be one of:

      • PER_ALWAYS: Runs on every boot.

      • ONCE: Runs only on first boot.

      • PER_INSTANCE: Runs once per instance. When exactly this happens is dependent on the datasource, but may be triggered any time there would be a significant change to the instance metadata. An example could be an instance being moved to a different subnet.

    • activate_by_schema_keys: Optional list of cloud-config keys that will activate this module. When this list not empty, the config module will be skipped unless one of the activate_by_schema_keys are present in merged cloud-config instance-data.

Example module.py file

# This file is part of cloud-init. See LICENSE file for license information.
"""Example Module: Shows how to create a module"""

import logging
from cloudinit.cloud import Cloud
from cloudinit.config import Config
from cloudinit.config.schema import MetaSchema
from cloudinit.distros import ALL_DISTROS
from cloudinit.settings import PER_INSTANCE

LOG = logging.getLogger(__name__)

meta: MetaSchema = {
    "id": "cc_example",
    "distros": [ALL_DISTROS],
    "frequency": PER_INSTANCE,
    "activate_by_schema_keys": ["example_key, example_other_key"],
} # type: ignore

def handle(
    name: str, cfg: Config, cloud: Cloud, args: list
) -> None:
    LOG.debug(f"Hi from module {name}")

Module documentation

Every module has a folder in the doc/module-docs/ directory, containing a data.yaml file, and one or more example*.yaml files.

  • The data.yaml file contains most of the documentation fields. At a minimum, your module should be provided with this file. Examples are not strictly required, but are helpful to readers of the documentation so it is preferred for at least one example to be included.

  • The example*.yaml files are illustrative demonstrations of using the module, but should be self-contained and in correctly-formatted YAML. These will be automatically tested against the defined schema.

Example data.yaml file

cc_module_name:
  description: >
    This module provides some functionality, which you can describe here.

    For straightforward text examples, use a greater-than (``>``) symbol
    next to ``description: `` to ensure proper rendering in the
    documentation. Empty lines will be respected, but line-breaks are
    folded together to create proper paragraphs.

    If you need to use call-outs or code blocks, use a pipe (``|``) symbol
    instead of ``>`` so that reStructuredText formatting (e.g. for
    directives, which take varying levels of indentation) is respected.
  examples:
  - comment: |
      Example 1: (optional) description of the expected behavior of the example
    file: cc_module_name/example1.yaml
  - comment: |
      Example 2: (optional) description of a second example.
    file: cc_module_name/example2.yaml
  name: Module Name
  title: Very brief (1 sentence) tag line describing what your module does

Rendering the module docs

The module documentation is auto-generated via the doc/rtd/reference/modules.rst file.

For your module documentation to be shown in the cloud-init docs, you will need to add an entry to this page. Modules are listed in alphabetical order. The entry should be in the following reStructuredText format:

.. datatemplate:yaml:: ../../module-docs/cc_ansible/data.yaml
   :template: modules.tmpl

The template pulls information from both your module.py file, and from its corresponding entry in the the module-docs directory.

Module execution

For a module to be run, it must be defined in a module run section in /etc/cloud/cloud.cfg or /etc/cloud/cloud.cfg.d on the launched instance. The three module sections are cloud_init_modules, cloud_config_modules, and cloud_final_modules, corresponding to the Network, Config, and Final boot stages respectively.

Add your module to cloud.cfg.tmpl under the appropriate module section. Each module gets run in the order listed, so ensure your module is defined in the correct location based on dependencies. If your module has no particular dependencies or is not necessary for a later boot stage, it should be placed in the cloud_final_modules section before the final-message module.

Benefits of including your config module in upstream cloud-init

Config modules included in upstream cloud-init benefit from ongoing maintenance, compatibility with the rest of the codebase, and security fixes by the upstream development team.

If this is not possible, one can add custom out-of-tree config modules to cloud-init.